From aad15ccc6e838552994ab5404a11f59fbfcc7519 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Dec 2019 15:57:46 +1100 Subject: [PATCH 01/68] better v3 --- examples/instruction_sample.yaml | 7 +- go.mod | 3 +- go.sum | 5 + pkg/yqlib/data_navigator.go | 751 ++++++++++++++++++------------- pkg/yqlib/lib.go | 52 +-- yq.go | 556 ++++++++++++----------- 6 files changed, 753 insertions(+), 621 deletions(-) diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml index ee2f8e8d..b7864db4 100644 --- a/examples/instruction_sample.yaml +++ b/examples/instruction_sample.yaml @@ -1,2 +1,7 @@ -b.c: cat +- command: update + path: b.c + value: + #great + things: frog # wow! + b.e[+].name: Mike Farah diff --git a/go.mod b/go.mod index 0b008769..efc8ffd2 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/mikefarah/yq/v2 +module github.com/mikefarah/yq/v3 require ( github.com/mikefarah/yaml/v2 v2.4.0 @@ -7,6 +7,7 @@ require ( golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 // indirect gopkg.in/imdario/mergo.v0 v0.3.7 gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 + gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 ) go 1.13 diff --git a/go.sum b/go.sum index 40ff65e5..307ebb49 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,11 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mikefarah/yaml v2.1.0+incompatible h1:nu2cqmzk4WlWJNgnevY88faMcdrDzYGcsUjYFxEpB7Y= github.com/mikefarah/yaml/v2 v2.4.0 h1:eYqfooY0BnvKTJxr7+ABJs13n3dg9n347GScDaU2Lww= github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU= +github.com/mikefarah/yq v2.4.0+incompatible h1:oBxbWy8R9hI3BIUUxEf0CzikWa2AgnGrGhvGQt5jgjk= +github.com/mikefarah/yq/v2 v2.4.1 h1:tajDonaFK6WqitSZExB6fKlWQy/yCkptqxh2AXEe3N4= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -48,3 +51,5 @@ gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 203eb6c0..66741598 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -1,19 +1,20 @@ package yqlib import ( - "fmt" - "reflect" "strconv" - "strings" - yaml "github.com/mikefarah/yaml/v2" logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) +type WriteCommand struct { + // Command string TODO + Value yaml.Node +} + type DataNavigator interface { - ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) - UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} - DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) + Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error) + Update(rootNode *yaml.Node, remainingPath []string, writeCommand WriteCommand) error } type navigator struct { @@ -26,351 +27,451 @@ func NewDataNavigator(l *logging.Logger) DataNavigator { } } -func (n *navigator) ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) { - if len(remainingPaths) == 0 { - return child, nil +func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { + realValue := value + if realValue.Kind == yaml.DocumentNode { + realValue = value.Content[0] } - return n.recurse(child, remainingPaths[0], remainingPaths[1:]) + if len(path) > 0 { + n.log.Debug("diving into %v", path[0]) + return n.recurse(realValue, path[0], path[1:]) + } + return realValue, nil } -func (n *navigator) UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { - if len(remainingPaths) == 0 { - return value +func (n *navigator) guessKind(tail []string) yaml.Kind { + n.log.Debug("tail %v", tail) + if len(tail) == 0 { + n.log.Debug("scalar") + return yaml.ScalarNode } - n.log.Debugf("UpdatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value) - n.log.Debugf("type of child is %v", reflect.TypeOf(child)) - - switch child := child.(type) { - case nil: - if remainingPaths[0] == "+" || remainingPaths[0] == "*" { - return n.writeArray(child, remainingPaths, value) - } - case []interface{}: - _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64) - arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*" - if arrayCommand { - return n.writeArray(child, remainingPaths, value) - } + var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64) + if tail[0] == "*" || tail[0] == "+" || errorParsingInt == nil { + return yaml.SequenceNode } - return n.writeMap(child, remainingPaths, value) + return yaml.MappingNode } -func (n *navigator) DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) { - n.log.Debugf("DeleteChildValue for %v for %v\n", remainingPaths, child) - if len(remainingPaths) == 0 { - return child, nil +func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { + // expected is a scalar when we reach the end of the path + // no need to clobber the original because: + // when reading, it should deal with the original kind + // when writing, it will clobber the kind anyway + if original.Kind != expectedKind && (expectedKind != yaml.ScalarNode) { + return &yaml.Node{Kind: expectedKind} } - var head = remainingPaths[0] - var tail = remainingPaths[1:] - switch child := child.(type) { - case yaml.MapSlice: - return n.deleteMap(child, remainingPaths) - case []interface{}: - if head == "*" { - return n.deleteArraySplat(child, tail) - } - index, err := strconv.ParseInt(head, 10, 64) - if err != nil { - return nil, fmt.Errorf("error accessing array: %v", err) - } - return n.deleteArray(child, remainingPaths, index) - } - return child, nil + return original } -func (n *navigator) recurse(value interface{}, head string, tail []string) (interface{}, error) { - switch value := value.(type) { - case []interface{}: - if head == "*" { - return n.readArraySplat(value, tail) - } - index, err := strconv.ParseInt(head, 10, 64) - if err != nil { - return nil, fmt.Errorf("error accessing array: %v", err) - } - return n.readArray(value, index, tail) - case yaml.MapSlice: - return n.readMap(value, head, tail) - default: - return nil, nil - } -} - -func (n *navigator) matchesKey(key string, actual interface{}) bool { - var actualString = fmt.Sprintf("%v", actual) - var prefixMatch = strings.TrimSuffix(key, "*") - if prefixMatch != key { - return strings.HasPrefix(actualString, prefixMatch) - } - return actualString == key -} - -func (n *navigator) entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { - var matches = make([]*yaml.MapItem, 0) - for idx := range context { - var entry = &context[idx] - if n.matchesKey(key, entry.Key) { - matches = append(matches, entry) - } - } - return matches -} - -func (n *navigator) getMapSlice(context interface{}) yaml.MapSlice { - var mapSlice yaml.MapSlice - switch context := context.(type) { - case yaml.MapSlice: - mapSlice = context - default: - mapSlice = make(yaml.MapSlice, 0) - } - return mapSlice -} - -func (n *navigator) getArray(context interface{}) (array []interface{}, ok bool) { - switch context := context.(type) { - case []interface{}: - array = context - ok = true - default: - array = make([]interface{}, 0) - ok = false - } - return -} - -func (n *navigator) writeMap(context interface{}, paths []string, value interface{}) interface{} { - n.log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value) - - mapSlice := n.getMapSlice(context) - - if len(paths) == 0 { - return context - } - - children := n.entriesInSlice(mapSlice, paths[0]) - - if len(children) == 0 && paths[0] == "*" { - n.log.Debugf("\tNo matches, return map as is") - return context - } - - if len(children) == 0 { - newChild := yaml.MapItem{Key: paths[0]} - mapSlice = append(mapSlice, newChild) - children = n.entriesInSlice(mapSlice, paths[0]) - n.log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice) - } - - remainingPaths := paths[1:] - for _, child := range children { - child.Value = n.UpdatedChildValue(child.Value, remainingPaths, value) - } - n.log.Debugf("\tReturning mapSlice %v\n", mapSlice) - return mapSlice -} - -func (n *navigator) writeArray(context interface{}, paths []string, value interface{}) []interface{} { - n.log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value) - array, _ := n.getArray(context) - - if len(paths) == 0 { - return array - } - - n.log.Debugf("\tarray %v\n", array) - - rawIndex := paths[0] - remainingPaths := paths[1:] - var index int64 - // the append array indicator - if rawIndex == "+" { - index = int64(len(array)) - } else if rawIndex == "*" { - for index, oldChild := range array { - array[index] = n.UpdatedChildValue(oldChild, remainingPaths, value) - } - return array - } else { - index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint - // writeArray is only called by UpdatedChildValue which handles parsing the - // index, as such this renders this dead code. - } - - for index >= int64(len(array)) { - array = append(array, nil) - } - currentChild := array[index] - - n.log.Debugf("\tcurrentChild %v\n", currentChild) - - array[index] = n.UpdatedChildValue(currentChild, remainingPaths, value) - n.log.Debugf("\tReturning array %v\n", array) - return array -} - -func (n *navigator) readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) { - n.log.Debugf("readingMap %v with key %v\n", context, head) - if head == "*" { - return n.readMapSplat(context, tail) - } - - entries := n.entriesInSlice(context, head) - if len(entries) == 1 { - return n.calculateValue(entries[0].Value, tail) - } else if len(entries) == 0 { - return nil, nil - } - var errInIdx error - values := make([]interface{}, len(entries)) - for idx, entry := range entries { - values[idx], errInIdx = n.calculateValue(entry.Value, tail) - if errInIdx != nil { - n.log.Errorf("Error updating index %v in %v", idx, context) - return nil, errInIdx - } - - } - return values, nil -} - -func (n *navigator) readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) { - var newArray = make([]interface{}, len(context)) - var i = 0 - for _, entry := range context { - if len(tail) > 0 { - val, err := n.recurse(entry.Value, tail[0], tail[1:]) - if err != nil { - return nil, err +func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml.Node, error) { + switch value.Kind { + case yaml.MappingNode: + n.log.Debug("its a map with %v entries", len(value.Content)/2) + for index, content := range value.Content { + // value.Content is a concatenated array of key, value, + // so keys are in the even indexes, values in odd. + if index%2 == 1 || content.Value != head { + continue } - newArray[i] = val - } else { - newArray[i] = entry.Value + value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail)) + return n.Get(value.Content[index+1], tail) } - i++ - } - return newArray, nil -} + value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) + mapEntryValue := yaml.Node{Kind: n.guessKind(tail)} + value.Content = append(value.Content, &mapEntryValue) + n.log.Debug("adding new node %v", value.Content) + return n.Get(&mapEntryValue, tail) + case yaml.SequenceNode: + n.log.Debug("its a sequence of %v things!", len(value.Content)) + if head == "*" { + var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} + newNode.Content = make([]*yaml.Node, len(value.Content)) -func (n *navigator) readArray(array []interface{}, head int64, tail []string) (interface{}, error) { - if head >= int64(len(array)) { - return nil, nil - } + for index, value := range value.Content { + value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail)) + var nestedValue, err = n.Get(value.Content[index], tail) + if err != nil { + return nil, err + } + newNode.Content[index] = nestedValue + } + return &newNode, nil + } else if head == "+" { - value := array[head] - return n.calculateValue(value, tail) -} - -func (n *navigator) readArraySplat(array []interface{}, tail []string) (interface{}, error) { - var newArray = make([]interface{}, len(array)) - for index, value := range array { - val, err := n.calculateValue(value, tail) + var newNode = yaml.Node{Kind: n.guessKind(tail)} + value.Content = append(value.Content, &newNode) + n.log.Debug("appending a new node, %v", value.Content) + return n.Get(&newNode, tail) + } + var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { return nil, err } - newArray[index] = val - } - return newArray, nil -} - -func (n *navigator) calculateValue(value interface{}, tail []string) (interface{}, error) { - if len(tail) > 0 { - return n.recurse(value, tail[0], tail[1:]) - } - return value, nil -} - -func (n *navigator) deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { - n.log.Debugf("deleteMap for %v for %v\n", paths, context) - - mapSlice := n.getMapSlice(context) - - if len(paths) == 0 { - return mapSlice, nil - } - - var index int - var child yaml.MapItem - for index, child = range mapSlice { - if n.matchesKey(paths[0], child.Key) { - n.log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) - var badDelete error - mapSlice, badDelete = n.deleteEntryInMap(mapSlice, child, index, paths) - if badDelete != nil { - return nil, badDelete - } + if index >= int64(len(value.Content)) { + return nil, nil } + value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail)) + return n.Get(value.Content[index], tail) + default: + return nil, nil } - - return mapSlice, nil - } -func (n *navigator) deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) { - remainingPaths := paths[1:] - - var newSlice yaml.MapSlice - if len(remainingPaths) > 0 { - newChild := yaml.MapItem{Key: child.Key} - var errorDeleting error - newChild.Value, errorDeleting = n.DeleteChildValue(child.Value, remainingPaths) - if errorDeleting != nil { - return nil, errorDeleting - } - - newSlice = make(yaml.MapSlice, len(original)) - for i := range original { - item := original[i] - if i == index { - item = newChild - } - newSlice[i] = item - } - } else { - // Delete item from slice at index - newSlice = append(original[:index], original[index+1:]...) - n.log.Debugf("\tDeleted item index %d from original", index) +func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, writeCommand WriteCommand) error { + nodeToUpdate, errorRecursing := n.Get(dataBucket, remainingPath) + if errorRecursing != nil { + return errorRecursing } + // later, support ability to execute other commands - n.log.Debugf("\tReturning original %v\n", original) - return newSlice, nil + changesToApply := writeCommand.Value + + nodeToUpdate.Value = changesToApply.Value + nodeToUpdate.Tag = changesToApply.Tag + nodeToUpdate.Kind = changesToApply.Kind + nodeToUpdate.Style = changesToApply.Style + nodeToUpdate.Content = changesToApply.Content + nodeToUpdate.HeadComment = changesToApply.HeadComment + nodeToUpdate.LineComment = changesToApply.LineComment + nodeToUpdate.FootComment = changesToApply.FootComment + return nil } -func (n *navigator) deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { - n.log.Debugf("deleteArraySplat for %v for %v\n", tail, array) - var newArray = make([]interface{}, len(array)) - for index, value := range array { - val, err := n.DeleteChildValue(value, tail) - if err != nil { - return nil, err - } - newArray[index] = val - } - return newArray, nil -} +// func matchesKey(key string, actual interface{}) bool { +// var actualString = fmt.Sprintf("%v", actual) +// var prefixMatch = strings.TrimSuffix(key, "*") +// if prefixMatch != key { +// return strings.HasPrefix(actualString, prefixMatch) +// } +// return actualString == key +// } -func (n *navigator) deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { - n.log.Debugf("deleteArray for %v for %v\n", paths, array) +// func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { +// var matches = make([]*yaml.MapItem, 0) +// for idx := range context { +// var entry = &context[idx] +// if matchesKey(key, entry.Key) { +// matches = append(matches, entry) +// } +// } +// return matches +// } - if index >= int64(len(array)) { - return array, nil - } +// func getMapSlice(context interface{}) yaml.MapSlice { +// var mapSlice yaml.MapSlice +// switch context := context.(type) { +// case yaml.MapSlice: +// mapSlice = context +// default: +// mapSlice = make(yaml.MapSlice, 0) +// } +// return mapSlice +// } - remainingPaths := paths[1:] - if len(remainingPaths) > 0 { - // recurse into the array element at index - var errorDeleting error - array[index], errorDeleting = n.deleteMap(array[index], remainingPaths) - if errorDeleting != nil { - return nil, errorDeleting - } +// func getArray(context interface{}) (array []interface{}, ok bool) { +// switch context := context.(type) { +// case []interface{}: +// array = context +// ok = true +// default: +// array = make([]interface{}, 0) +// ok = false +// } +// return +// } - } else { - // Delete the array element at index - array = append(array[:index], array[index+1:]...) - n.log.Debugf("\tDeleted item index %d from array, leaving %v", index, array) - } +// func writeMap(context interface{}, paths []string, value interface{}) interface{} { +// log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value) - n.log.Debugf("\tReturning array: %v\n", array) - return array, nil -} +// mapSlice := getMapSlice(context) + +// if len(paths) == 0 { +// return context +// } + +// children := entriesInSlice(mapSlice, paths[0]) + +// if len(children) == 0 && paths[0] == "*" { +// log.Debugf("\tNo matches, return map as is") +// return context +// } + +// if len(children) == 0 { +// newChild := yaml.MapItem{Key: paths[0]} +// mapSlice = append(mapSlice, newChild) +// children = entriesInSlice(mapSlice, paths[0]) +// log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice) +// } + +// remainingPaths := paths[1:] +// for _, child := range children { +// child.Value = updatedChildValue(child.Value, remainingPaths, value) +// } +// log.Debugf("\tReturning mapSlice %v\n", mapSlice) +// return mapSlice +// } + +// func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { +// if len(remainingPaths) == 0 { +// return value +// } +// log.Debugf("updatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value) +// log.Debugf("type of child is %v", reflect.TypeOf(child)) + +// switch child := child.(type) { +// case nil: +// if remainingPaths[0] == "+" || remainingPaths[0] == "*" { +// return writeArray(child, remainingPaths, value) +// } +// case []interface{}: +// _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64) +// arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*" +// if arrayCommand { +// return writeArray(child, remainingPaths, value) +// } +// } +// return writeMap(child, remainingPaths, value) +// } + +// func writeArray(context interface{}, paths []string, value interface{}) []interface{} { +// log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value) +// array, _ := getArray(context) + +// if len(paths) == 0 { +// return array +// } + +// log.Debugf("\tarray %v\n", array) + +// rawIndex := paths[0] +// remainingPaths := paths[1:] +// var index int64 +// // the append array indicator +// if rawIndex == "+" { +// index = int64(len(array)) +// } else if rawIndex == "*" { +// for index, oldChild := range array { +// array[index] = updatedChildValue(oldChild, remainingPaths, value) +// } +// return array +// } else { +// index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint +// // writeArray is only called by updatedChildValue which handles parsing the +// // index, as such this renders this dead code. +// } + +// for index >= int64(len(array)) { +// array = append(array, nil) +// } +// currentChild := array[index] + +// log.Debugf("\tcurrentChild %v\n", currentChild) + +// array[index] = updatedChildValue(currentChild, remainingPaths, value) +// log.Debugf("\tReturning array %v\n", array) +// return array +// } + +// func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) { +// log.Debugf("readingMap %v with key %v\n", context, head) +// if head == "*" { +// return readMapSplat(context, tail) +// } + +// entries := entriesInSlice(context, head) +// if len(entries) == 1 { +// return calculateValue(entries[0].Value, tail) +// } else if len(entries) == 0 { +// return nil, nil +// } +// var errInIdx error +// values := make([]interface{}, len(entries)) +// for idx, entry := range entries { +// values[idx], errInIdx = calculateValue(entry.Value, tail) +// if errInIdx != nil { +// log.Errorf("Error updating index %v in %v", idx, context) +// return nil, errInIdx +// } + +// } +// return values, nil +// } + +// func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) { +// var newArray = make([]interface{}, len(context)) +// var i = 0 +// for _, entry := range context { +// if len(tail) > 0 { +// val, err := recurse(entry.Value, tail[0], tail[1:]) +// if err != nil { +// return nil, err +// } +// newArray[i] = val +// } else { +// newArray[i] = entry.Value +// } +// i++ +// } +// return newArray, nil +// } + +// func recurse(value interface{}, head string, tail []string) (interface{}, error) { +// switch value := value.(type) { +// case []interface{}: +// if head == "*" { +// return readArraySplat(value, tail) +// } +// index, err := strconv.ParseInt(head, 10, 64) +// if err != nil { +// return nil, fmt.Errorf("error accessing array: %v", err) +// } +// return readArray(value, index, tail) +// case yaml.MapSlice: +// return readMap(value, head, tail) +// default: +// return nil, nil +// } +// } + +// func readArray(array []interface{}, head int64, tail []string) (interface{}, error) { +// if head >= int64(len(array)) { +// return nil, nil +// } + +// value := array[head] +// return calculateValue(value, tail) +// } + +// func readArraySplat(array []interface{}, tail []string) (interface{}, error) { +// var newArray = make([]interface{}, len(array)) +// for index, value := range array { +// val, err := calculateValue(value, tail) +// if err != nil { +// return nil, err +// } +// newArray[index] = val +// } +// return newArray, nil +// } + +// func calculateValue(value interface{}, tail []string) (interface{}, error) { +// if len(tail) > 0 { +// return recurse(value, tail[0], tail[1:]) +// } +// return value, nil +// } + +// func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { +// log.Debugf("deleteMap for %v for %v\n", paths, context) + +// mapSlice := getMapSlice(context) + +// if len(paths) == 0 { +// return mapSlice, nil +// } + +// var index int +// var child yaml.MapItem +// for index, child = range mapSlice { +// if matchesKey(paths[0], child.Key) { +// log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) +// var badDelete error +// mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths) +// if badDelete != nil { +// return nil, badDelete +// } +// } +// } + +// return mapSlice, nil + +// } + +// func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) { +// remainingPaths := paths[1:] + +// var newSlice yaml.MapSlice +// if len(remainingPaths) > 0 { +// newChild := yaml.MapItem{Key: child.Key} +// var errorDeleting error +// newChild.Value, errorDeleting = deleteChildValue(child.Value, remainingPaths) +// if errorDeleting != nil { +// return nil, errorDeleting +// } + +// newSlice = make(yaml.MapSlice, len(original)) +// for i := range original { +// item := original[i] +// if i == index { +// item = newChild +// } +// newSlice[i] = item +// } +// } else { +// // Delete item from slice at index +// newSlice = append(original[:index], original[index+1:]...) +// log.Debugf("\tDeleted item index %d from original", index) +// } + +// log.Debugf("\tReturning original %v\n", original) +// return newSlice, nil +// } + +// func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { +// log.Debugf("deleteArraySplat for %v for %v\n", tail, array) +// var newArray = make([]interface{}, len(array)) +// for index, value := range array { +// val, err := deleteChildValue(value, tail) +// if err != nil { +// return nil, err +// } +// newArray[index] = val +// } +// return newArray, nil +// } + +// func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { +// log.Debugf("deleteArray for %v for %v\n", paths, array) + +// if index >= int64(len(array)) { +// return array, nil +// } + +// remainingPaths := paths[1:] +// if len(remainingPaths) > 0 { +// // Recurse into the array element at index +// var errorDeleting error +// array[index], errorDeleting = deleteMap(array[index], remainingPaths) +// if errorDeleting != nil { +// return nil, errorDeleting +// } + +// } else { +// // Delete the array element at index +// array = append(array[:index], array[index+1:]...) +// log.Debugf("\tDeleted item index %d from array, leaving %v", index, array) +// } + +// log.Debugf("\tReturning array: %v\n", array) +// return array, nil +// } + +// func deleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) { +// log.Debugf("deleteChildValue for %v for %v\n", remainingPaths, child) +// var head = remainingPaths[0] +// var tail = remainingPaths[1:] +// switch child := child.(type) { +// case yaml.MapSlice: +// return deleteMap(child, remainingPaths) +// case []interface{}: +// if head == "*" { +// return deleteArraySplat(child, tail) +// } +// index, err := strconv.ParseInt(head, 10, 64) +// if err != nil { +// return nil, fmt.Errorf("error accessing array: %v", err) +// } +// return deleteArray(child, remainingPaths, index) +// } +// return child, nil +// } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 8d330063..a477b8dc 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -1,16 +1,13 @@ package yqlib import ( - mergo "gopkg.in/imdario/mergo.v0" logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) type YqLib interface { - ReadPath(dataBucket interface{}, path string) (interface{}, error) - WritePath(dataBucket interface{}, path string, value interface{}) interface{} - PrefixPath(dataBucket interface{}, prefix string) interface{} - DeletePath(dataBucket interface{}, path string) (interface{}, error) - Merge(dst interface{}, src interface{}, overwrite bool, append bool) error + Get(rootNode *yaml.Node, path string) (*yaml.Node, error) + Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error } type lib struct { @@ -25,44 +22,15 @@ func NewYqLib(l *logging.Logger) YqLib { } } -func (l *lib) ReadPath(dataBucket interface{}, path string) (interface{}, error) { - var paths = l.parser.ParsePath(path) - return l.navigator.ReadChildValue(dataBucket, paths) -} - -func (l *lib) WritePath(dataBucket interface{}, path string, value interface{}) interface{} { - var paths = l.parser.ParsePath(path) - return l.navigator.UpdatedChildValue(dataBucket, paths, value) -} - -func (l *lib) PrefixPath(dataBucket interface{}, prefix string) interface{} { - var paths = l.parser.ParsePath(prefix) - - // Inverse order - for i := len(paths)/2 - 1; i >= 0; i-- { - opp := len(paths) - 1 - i - paths[i], paths[opp] = paths[opp], paths[i] +func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { + if path == "" { + return rootNode, nil } - - var mapDataBucket = dataBucket - for _, key := range paths { - singlePath := []string{key} - mapDataBucket = l.navigator.UpdatedChildValue(nil, singlePath, mapDataBucket) - } - - return mapDataBucket -} - -func (l *lib) DeletePath(dataBucket interface{}, path string) (interface{}, error) { var paths = l.parser.ParsePath(path) - return l.navigator.DeleteChildValue(dataBucket, paths) + return l.navigator.Get(rootNode, paths) } -func (l *lib) Merge(dst interface{}, src interface{}, overwrite bool, append bool) error { - if overwrite { - return mergo.Merge(dst, src, mergo.WithOverride) - } else if append { - return mergo.Merge(dst, src, mergo.WithAppendSlice) - } - return mergo.Merge(dst, src) +func (l *lib) Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error { + var paths = l.parser.ParsePath(path) + return l.navigator.Update(rootNode, paths, writeCommand) } diff --git a/yq.go b/yq.go index 345706fa..f3b0c24d 100644 --- a/yq.go +++ b/yq.go @@ -6,20 +6,19 @@ import ( "io" "io/ioutil" "os" - "reflect" "strconv" - "strings" - "github.com/mikefarah/yq/v2/pkg/marshal" - "github.com/mikefarah/yq/v2/pkg/yqlib" + "github.com/mikefarah/yq/v3/pkg/marshal" + "github.com/mikefarah/yq/v3/pkg/yqlib" errors "github.com/pkg/errors" - yaml "github.com/mikefarah/yaml/v2" "github.com/spf13/cobra" logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) +var rawOutput = false var trimOutput = true var writeInplace = false var writeScript = "" @@ -45,7 +44,6 @@ func main() { } func newCommandCLI() *cobra.Command { - yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) var rootCmd = &cobra.Command{ Use: "yq", Short: "yq is a lightweight and portable command-line YAML processor.", @@ -83,10 +81,10 @@ func newCommandCLI() *cobra.Command { rootCmd.AddCommand( createReadCmd(), createWriteCmd(), - createPrefixCmd(), - createDeleteCmd(), - createNewCmd(), - createMergeCmd(), + // createPrefixCmd(), + // createDeleteCmd(), + // createNewCmd(), + // createMergeCmd(), ) rootCmd.SetOutput(os.Stdout) @@ -110,6 +108,7 @@ yq r -- things.yaml --key-starting-with-dashes RunE: readProperty, } cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + cmdRead.PersistentFlags().BoolVarP(&rawOutput, "raw", "r", false, "raw yaml output - prints out values instead of yaml") cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") return cmdRead } @@ -149,104 +148,104 @@ a.b.e: return cmdWrite } -func createPrefixCmd() *cobra.Command { - var cmdWrite = &cobra.Command{ - Use: "prefix [yaml_file] [path]", - Aliases: []string{"p"}, - Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq prefix things.yaml a.b.c -yq prefix --inplace things.yaml a.b.c -yq prefix --inplace -- things.yaml --key-starting-with-dash -yq p -i things.yaml a.b.c -yq p --doc 2 things.yaml a.b.d -yq p -d2 things.yaml a.b.d - `, - Long: `Prefixes w.r.t to the yaml file at the given path. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: prefixProperty, - } - cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdWrite -} +// func createPrefixCmd() *cobra.Command { +// var cmdWrite = &cobra.Command{ +// Use: "prefix [yaml_file] [path]", +// Aliases: []string{"p"}, +// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", +// Example: ` +// yq prefix things.yaml a.b.c +// yq prefix --inplace things.yaml a.b.c +// yq prefix --inplace -- things.yaml --key-starting-with-dash +// yq p -i things.yaml a.b.c +// yq p --doc 2 things.yaml a.b.d +// yq p -d2 things.yaml a.b.d +// `, +// Long: `Prefixes w.r.t to the yaml file at the given path. +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// `, +// RunE: prefixProperty, +// } +// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// return cmdWrite +// } -func createDeleteCmd() *cobra.Command { - var cmdDelete = &cobra.Command{ - Use: "delete [yaml_file] [path]", - Aliases: []string{"d"}, - Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq delete things.yaml a.b.c -yq delete --inplace things.yaml a.b.c -yq delete --inplace -- things.yaml --key-starting-with-dash -yq d -i things.yaml a.b.c -yq d things.yaml a.b.c - `, - Long: `Deletes the given path from the YAML file. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: deleteProperty, - } - cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdDelete -} +// func createDeleteCmd() *cobra.Command { +// var cmdDelete = &cobra.Command{ +// Use: "delete [yaml_file] [path]", +// Aliases: []string{"d"}, +// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", +// Example: ` +// yq delete things.yaml a.b.c +// yq delete --inplace things.yaml a.b.c +// yq delete --inplace -- things.yaml --key-starting-with-dash +// yq d -i things.yaml a.b.c +// yq d things.yaml a.b.c +// `, +// Long: `Deletes the given path from the YAML file. +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// `, +// RunE: deleteProperty, +// } +// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// return cmdDelete +// } -func createNewCmd() *cobra.Command { - var cmdNew = &cobra.Command{ - Use: "new [path] [value]", - Aliases: []string{"n"}, - Short: "yq n [--script/-s script_file] a.b.c newValue", - Example: ` -yq new a.b.c cat -yq n a.b.c cat -yq n -- --key-starting-with-dash cat -yq n --script create_script.yaml - `, - Long: `Creates a new yaml w.r.t the given path and value. -Outputs to STDOUT +// func createNewCmd() *cobra.Command { +// var cmdNew = &cobra.Command{ +// Use: "new [path] [value]", +// Aliases: []string{"n"}, +// Short: "yq n [--script/-s script_file] a.b.c newValue", +// Example: ` +// yq new a.b.c cat +// yq n a.b.c cat +// yq n -- --key-starting-with-dash cat +// yq n --script create_script.yaml +// `, +// Long: `Creates a new yaml w.r.t the given path and value. +// Outputs to STDOUT -Create Scripts: -Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. -`, - RunE: newProperty, - } - cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") - return cmdNew -} +// Create Scripts: +// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. +// `, +// RunE: newProperty, +// } +// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") +// return cmdNew +// } -func createMergeCmd() *cobra.Command { - var cmdMerge = &cobra.Command{ - Use: "merge [initial_yaml_file] [additional_yaml_file]...", - Aliases: []string{"m"}, - Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", - Example: ` -yq merge things.yaml other.yaml -yq merge --inplace things.yaml other.yaml -yq m -i things.yaml other.yaml -yq m --overwrite things.yaml other.yaml -yq m -i -x things.yaml other.yaml -yq m -i -a things.yaml other.yaml - `, - Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// func createMergeCmd() *cobra.Command { +// var cmdMerge = &cobra.Command{ +// Use: "merge [initial_yaml_file] [additional_yaml_file]...", +// Aliases: []string{"m"}, +// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", +// Example: ` +// yq merge things.yaml other.yaml +// yq merge --inplace things.yaml other.yaml +// yq m -i things.yaml other.yaml +// yq m --overwrite things.yaml other.yaml +// yq m -i -x things.yaml other.yaml +// yq m -i -a things.yaml other.yaml +// `, +// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. -If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. +// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. +// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. -Note that if you set both flags only overwrite will take effect. -`, - RunE: mergeProperties, - } - cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") - cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") - cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") - cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdMerge -} +// Note that if you set both flags only overwrite will take effect. +// `, +// RunE: mergeProperties, +// } +// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") +// cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") +// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") +// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// return cmdMerge +// } func readProperty(cmd *cobra.Command, args []string) error { var path = "" @@ -261,8 +260,9 @@ func readProperty(cmd *cobra.Command, args []string) error { if errorParsingDocIndex != nil { return errorParsingDocIndex } - var mappedDocs []interface{} - var dataBucket interface{} + + var mappedDocs []*yaml.Node + var dataBucket yaml.Node var currentIndex = 0 var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error { for { @@ -274,21 +274,14 @@ func readProperty(cmd *cobra.Command, args []string) error { } return nil } - log.Debugf("processing %v - requested index %v", currentIndex, docIndexInt) + log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) if updateAll || currentIndex == docIndexInt { - log.Debugf("reading %v in index %v", path, currentIndex) - if path == "" { - log.Debug("no path") - log.Debugf("%v", dataBucket) - mappedDocs = append(mappedDocs, dataBucket) - } else { - mappedDoc, errorParsing := lib.ReadPath(dataBucket, path) - log.Debugf("%v", mappedDoc) - if errorParsing != nil { - return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } - mappedDocs = append(mappedDocs, mappedDoc) + log.Debugf("reading %v in document %v", path, currentIndex) + mappedDoc, errorParsing := lib.Get(&dataBucket, path) + if errorParsing != nil { + return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) } + mappedDocs = append(mappedDocs, mappedDoc) } currentIndex = currentIndex + 1 } @@ -298,56 +291,64 @@ func readProperty(cmd *cobra.Command, args []string) error { return errorReadingStream } - if !updateAll { - dataBucket = mappedDocs[0] - } else { - dataBucket = mappedDocs - } + var encoder = yaml.NewEncoder(cmd.OutOrStdout()) + encoder.SetIndent(2) + var err error - dataStr, err := toString(dataBucket) + if rawOutput { + for _, mappedDoc := range mappedDocs { + if mappedDoc != nil { + cmd.Println(mappedDoc.Value) + } + } + } else if !updateAll { + err = encoder.Encode(mappedDocs[0]) + } else { + err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs}) + } if err != nil { return err } - cmd.Println(dataStr) + encoder.Close() return nil } -func newProperty(cmd *cobra.Command, args []string) error { - updatedData, err := newYaml(args) - if err != nil { - return err - } - dataStr, err := toString(updatedData) - if err != nil { - return err - } - cmd.Println(dataStr) - return nil -} +// func newProperty(cmd *cobra.Command, args []string) error { +// updatedData, err := newYaml(args) +// if err != nil { +// return err +// } +// dataStr, err := toString(updatedData) +// if err != nil { +// return err +// } +// cmd.Println(dataStr) +// return nil +// } -func newYaml(args []string) (interface{}, error) { - var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide ") - if writeCommandsError != nil { - return nil, writeCommandsError - } +// func newYaml(args []string) (interface{}, error) { +// var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide ") +// if writeCommandsError != nil { +// return nil, writeCommandsError +// } - var dataBucket interface{} - var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[") - if isArray { - dataBucket = make([]interface{}, 0) - } else { - dataBucket = make(yaml.MapSlice, 0) - } +// var dataBucket interface{} +// var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[") +// if isArray { +// dataBucket = make([]interface{}, 0) +// } else { +// dataBucket = make(yaml.MapSlice, 0) +// } - for _, entry := range writeCommands { - path := entry.Key.(string) - value := entry.Value - log.Debugf("setting %v to %v", path, value) - dataBucket = lib.WritePath(dataBucket, path, value) - } +// for _, entry := range writeCommands { +// path := entry.Key.(string) +// value := entry.Value +// log.Debugf("setting %v to %v", path, value) +// dataBucket = lib.WritePath(dataBucket, path, value) +// } - return dataBucket, nil -} +// return dataBucket, nil +// } func parseDocumentIndex() (bool, int, error) { if docIndex == "*" { @@ -360,11 +361,11 @@ func parseDocumentIndex() (bool, int, error) { return false, int(docIndexInt64), nil } -type updateDataFn func(dataBucket interface{}, currentIndex int) (interface{}, error) +type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn { return func(decoder *yaml.Decoder) error { - var dataBucket interface{} + var dataBucket yaml.Node var errorReading error var errorWriting error var errorUpdating error @@ -387,12 +388,12 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF } else if errorReading != nil { return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) } - dataBucket, errorUpdating = updateData(dataBucket, currentIndex) + errorUpdating = updateData(&dataBucket, currentIndex) if errorUpdating != nil { return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) } - errorWriting = encoder.Encode(dataBucket) + errorWriting = encoder.Encode(&dataBucket) if errorWriting != nil { return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) @@ -412,43 +413,47 @@ func writeProperty(cmd *cobra.Command, args []string) error { return errorParsingDocIndex } - var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { + var updateData = func(dataBucket *yaml.Node, currentIndex int) error { if updateAll || currentIndex == docIndexInt { log.Debugf("Updating doc %v", currentIndex) for _, entry := range writeCommands { - path := entry.Key.(string) - value := entry.Value - log.Debugf("setting %v to %v", path, value) - dataBucket = lib.WritePath(dataBucket, path, value) + path := entry.Key + changesToApply := entry.Value + var paths = parsePath(path) + + errorUpdating := updateChild(dataBucket, paths, changesToApply) + if errorUpdating != nil { + return errorUpdating + } } } - return dataBucket, nil + return nil } return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) } -func prefixProperty(cmd *cobra.Command, args []string) error { - if len(args) != 2 { - return errors.New("Must provide ") - } - prefixPath := args[1] +// func prefixProperty(cmd *cobra.Command, args []string) error { +// if len(args) != 2 { +// return errors.New("Must provide ") +// } +// prefixPath := args[1] - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } +// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() +// if errorParsingDocIndex != nil { +// return errorParsingDocIndex +// } - var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { +// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) - var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath) - return mapDataBucket, nil - } - return dataBucket, nil - } - return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -} +// if updateAll || currentIndex == docIndexInt { +// log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) +// var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath) +// return mapDataBucket, nil +// } +// return dataBucket, nil +// } +// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +// } func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { var destination io.Writer @@ -479,90 +484,137 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) defer safelyFlush(writer) } var encoder = yaml.NewEncoder(destination) + encoder.SetIndent(2) log.Debugf("Writing to %v from %v", destinationName, inputFile) return readStream(inputFile, mapYamlDecoder(updateData, encoder)) } -func deleteProperty(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide ") - } - var deletePath = args[1] - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } +// func deleteProperty(cmd *cobra.Command, args []string) error { +// if len(args) < 2 { +// return errors.New("Must provide ") +// } +// var deletePath = args[1] +// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() +// if errorParsingDocIndex != nil { +// return errorParsingDocIndex +// } - var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Deleting path in doc %v", currentIndex) - return lib.DeletePath(dataBucket, deletePath) - } - return dataBucket, nil - } +// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { +// if updateAll || currentIndex == docIndexInt { +// log.Debugf("Deleting path in doc %v", currentIndex) +// return lib.DeletePath(dataBucket, deletePath) +// } +// return dataBucket, nil +// } - return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +// } + +// func mergeProperties(cmd *cobra.Command, args []string) error { +// if len(args) < 2 { +// return errors.New("Must provide at least 2 yaml files") +// } +// var input = args[0] +// var filesToMerge = args[1:] +// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() +// if errorParsingDocIndex != nil { +// return errorParsingDocIndex +// } + +// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { +// if updateAll || currentIndex == docIndexInt { +// log.Debugf("Merging doc %v", currentIndex) +// var mergedData map[interface{}]interface{} +// // merge only works for maps, so put everything in a temporary +// // map +// var mapDataBucket = make(map[interface{}]interface{}) +// mapDataBucket["root"] = dataBucket +// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { +// return nil, err +// } +// for _, f := range filesToMerge { +// var fileToMerge interface{} +// if err := readData(f, 0, &fileToMerge); err != nil { +// if allowEmptyFlag && err == io.EOF { +// continue +// } +// return nil, err +// } +// mapDataBucket["root"] = fileToMerge +// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { +// return nil, err +// } +// } +// return mergedData["root"], nil +// } +// return dataBucket, nil +// } +// return readAndUpdate(cmd.OutOrStdout(), input, updateData) +// } + +type rawWriteCommand struct { + // Command string TODO + Key string + Value yaml.Node } -func mergeProperties(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide at least 2 yaml files") - } - var input = args[0] - var filesToMerge = args[1:] - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Merging doc %v", currentIndex) - var mergedData map[interface{}]interface{} - // merge only works for maps, so put everything in a temporary - // map - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = dataBucket - if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { - return nil, err - } - for _, f := range filesToMerge { - var fileToMerge interface{} - if err := readData(f, 0, &fileToMerge); err != nil { - if allowEmptyFlag && err == io.EOF { - continue - } - return nil, err - } - mapDataBucket["root"] = fileToMerge - if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { - return nil, err - } - } - return mergedData["root"], nil - } - return dataBucket, nil - } - yaml.DefaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) - defer func() { yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) }() - return readAndUpdate(cmd.OutOrStdout(), input, updateData) -} - -func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) (yaml.MapSlice, error) { - var writeCommands yaml.MapSlice +func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) ([]rawWriteCommand, error) { + var writeCommands []rawWriteCommand if writeScript != "" { - if err := readData(writeScript, 0, &writeCommands); err != nil { + var rawCommands yaml.Node + if err := readData(writeScript, 0, &rawCommands); err != nil { return nil, err } + log.Debugf("Read write commands file '%v'", rawCommands) + var key string + for index, content := range rawCommands.Content[0].Content { + if index%2 == 0 { // must be the key + key = content.Value + } else { // its the value + writeCommands = append(writeCommands, rawWriteCommand{Key: key, Value: *content}) + } + } + log.Debugf("Read write commands '%v'", writeCommands) } else if len(args) < expectedArgs { return nil, errors.New(badArgsMessage) } else { - writeCommands = make(yaml.MapSlice, 1) - writeCommands[0] = yaml.MapItem{Key: args[expectedArgs-2], Value: valueParser.ParseValue(args[expectedArgs-1])} + writeCommands = make([]rawWriteCommand, 1) + writeCommands[0] = rawWriteCommand{Key: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])} } return writeCommands, nil } +func parseValue(argument string) yaml.Node { + var err interface{} + var tag = customTag + + var inQuotes = len(argument) > 0 && argument[0] == '"' + if tag == "" && !inQuotes { + + _, err = strconv.ParseBool(argument) + if err == nil { + tag = "!!bool" + } + _, err = strconv.ParseFloat(argument, 64) + if err == nil { + tag = "!!float" + } + _, err = strconv.ParseInt(argument, 10, 64) + if err == nil { + tag = "!!int" + } + + if argument == "null" { + tag = "!!null" + } + if argument == "[]" { + return yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} + } + } + log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag) + return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} +} + func toString(context interface{}) (string, error) { if outputToJSON { return jsonConverter.JsonToString(context) @@ -572,7 +624,7 @@ func toString(context interface{}) (string, error) { func safelyRenameFile(from string, to string) { if renameError := os.Rename(from, to); renameError != nil { - log.Debugf("Error renaming from %v to %v, attemting to copy contents", from, to) + log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) log.Debug(renameError.Error()) // can't do this rename when running in docker to a file targeted in a mounted volume, // so gracefully degrade to copying the entire contents. From 972e2b957561cf5353397cc074c12ecaf7db9be8 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Dec 2019 16:36:42 +1100 Subject: [PATCH 02/68] wip --- examples/instruction_sample.yaml | 9 ++++-- pkg/yqlib/data_navigator.go | 15 ++------- pkg/yqlib/lib.go | 29 +++++++++++++++--- yq.go | 52 ++++++++++---------------------- 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml index b7864db4..9834c942 100644 --- a/examples/instruction_sample.yaml +++ b/examples/instruction_sample.yaml @@ -1,7 +1,10 @@ -- command: update +- command: update path: b.c value: #great things: frog # wow! - -b.e[+].name: Mike Farah +- command: update + path: b.e[+].name + value: Mike Farah +- command: delete + path: a \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 66741598..744cdbcb 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -7,14 +7,9 @@ import ( yaml "gopkg.in/yaml.v3" ) -type WriteCommand struct { - // Command string TODO - Value yaml.Node -} - type DataNavigator interface { Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error) - Update(rootNode *yaml.Node, remainingPath []string, writeCommand WriteCommand) error + Update(rootNode *yaml.Node, remainingPath []string, changesToApply yaml.Node) error } type navigator struct { @@ -33,7 +28,7 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { realValue = value.Content[0] } if len(path) > 0 { - n.log.Debug("diving into %v", path[0]) + n.log.Debugf("diving into %v", path[0]) return n.recurse(realValue, path[0], path[1:]) } return realValue, nil @@ -117,15 +112,11 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml } } -func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, writeCommand WriteCommand) error { +func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, changesToApply yaml.Node) error { nodeToUpdate, errorRecursing := n.Get(dataBucket, remainingPath) if errorRecursing != nil { return errorRecursing } - // later, support ability to execute other commands - - changesToApply := writeCommand.Value - nodeToUpdate.Value = changesToApply.Value nodeToUpdate.Tag = changesToApply.Tag nodeToUpdate.Kind = changesToApply.Kind diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index a477b8dc..5b630e82 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -1,24 +1,34 @@ package yqlib import ( + "fmt" + logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" ) +type UpdateCommand struct { + Command string + Path string + Value yaml.Node +} + type YqLib interface { Get(rootNode *yaml.Node, path string) (*yaml.Node, error) - Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error + Update(rootNode *yaml.Node, updateCommand UpdateCommand) error } type lib struct { navigator DataNavigator parser PathParser + log *logging.Logger } func NewYqLib(l *logging.Logger) YqLib { return &lib{ navigator: NewDataNavigator(l), parser: NewPathParser(), + log: l, } } @@ -30,7 +40,18 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { return l.navigator.Get(rootNode, paths) } -func (l *lib) Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error { - var paths = l.parser.ParsePath(path) - return l.navigator.Update(rootNode, paths, writeCommand) +func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { + // later - support other command types + l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) + switch updateCommand.Command { + case "update": + var paths = l.parser.ParsePath(updateCommand.Path) + return l.navigator.Update(rootNode, paths, updateCommand.Value) + case "delete": + l.log.Debugf("need to implement delete") + return nil + default: + return fmt.Errorf("Unknown command %v", updateCommand.Command) + } + } diff --git a/yq.go b/yq.go index f3b0c24d..5a21169c 100644 --- a/yq.go +++ b/yq.go @@ -19,7 +19,7 @@ import ( ) var rawOutput = false -var trimOutput = true +var customTag = "" var writeInplace = false var writeScript = "" var outputToJSON = false @@ -74,7 +74,6 @@ func newCommandCLI() *cobra.Command { }, } - rootCmd.PersistentFlags().BoolVarP(&trimOutput, "trim", "t", true, "trim yaml output") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") @@ -144,6 +143,7 @@ a.b.e: } cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") + cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") return cmdWrite } @@ -327,7 +327,7 @@ func readProperty(cmd *cobra.Command, args []string) error { // } // func newYaml(args []string) (interface{}, error) { -// var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide ") +// var writeCommands, writeCommandsError = readUpdateCommands(args, 2, "Must provide ") // if writeCommandsError != nil { // return nil, writeCommandsError // } @@ -404,9 +404,9 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF } func writeProperty(cmd *cobra.Command, args []string) error { - var writeCommands, writeCommandsError = readWriteCommands(args, 3, "Must provide ") - if writeCommandsError != nil { - return writeCommandsError + var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ") + if updateCommandsError != nil { + return updateCommandsError } var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() if errorParsingDocIndex != nil { @@ -416,12 +416,8 @@ func writeProperty(cmd *cobra.Command, args []string) error { var updateData = func(dataBucket *yaml.Node, currentIndex int) error { if updateAll || currentIndex == docIndexInt { log.Debugf("Updating doc %v", currentIndex) - for _, entry := range writeCommands { - path := entry.Key - changesToApply := entry.Value - var paths = parsePath(path) - - errorUpdating := updateChild(dataBucket, paths, changesToApply) + for _, updateCommand := range updateCommands { + errorUpdating := lib.Update(dataBucket, updateCommand) if errorUpdating != nil { return errorUpdating } @@ -552,36 +548,20 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) // return readAndUpdate(cmd.OutOrStdout(), input, updateData) // } -type rawWriteCommand struct { - // Command string TODO - Key string - Value yaml.Node -} - -func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) ([]rawWriteCommand, error) { - var writeCommands []rawWriteCommand +func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) if writeScript != "" { - var rawCommands yaml.Node - if err := readData(writeScript, 0, &rawCommands); err != nil { + if err := readData(writeScript, 0, &updateCommands); err != nil { return nil, err } - log.Debugf("Read write commands file '%v'", rawCommands) - var key string - for index, content := range rawCommands.Content[0].Content { - if index%2 == 0 { // must be the key - key = content.Value - } else { // its the value - writeCommands = append(writeCommands, rawWriteCommand{Key: key, Value: *content}) - } - } - log.Debugf("Read write commands '%v'", writeCommands) + log.Debugf("Read write commands file '%v'", updateCommands) } else if len(args) < expectedArgs { return nil, errors.New(badArgsMessage) } else { - writeCommands = make([]rawWriteCommand, 1) - writeCommands[0] = rawWriteCommand{Key: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])} + updateCommands = make([]yqlib.UpdateCommand, 1) + updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])} } - return writeCommands, nil + return updateCommands, nil } func parseValue(argument string) yaml.Node { @@ -619,7 +599,7 @@ func toString(context interface{}) (string, error) { if outputToJSON { return jsonConverter.JsonToString(context) } - return yamlConverter.YamlToString(context, trimOutput) + return yamlConverter.YamlToString(context, true) } func safelyRenameFile(from string, to string) { From 676fc63219634c0bf6850a69d4de1a36d99e5194 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Dec 2019 16:41:21 +1100 Subject: [PATCH 03/68] remove json conversion for now --- pkg/marshal/json_converter.go | 54 ------------------------------ pkg/marshal/json_converter_test.go | 48 -------------------------- pkg/marshal/yaml_converter.go | 43 ------------------------ pkg/marshal/yaml_converter_test.go | 52 ---------------------------- yq.go | 10 ------ 5 files changed, 207 deletions(-) delete mode 100644 pkg/marshal/json_converter.go delete mode 100644 pkg/marshal/json_converter_test.go delete mode 100644 pkg/marshal/yaml_converter.go delete mode 100644 pkg/marshal/yaml_converter_test.go diff --git a/pkg/marshal/json_converter.go b/pkg/marshal/json_converter.go deleted file mode 100644 index 5ca9961d..00000000 --- a/pkg/marshal/json_converter.go +++ /dev/null @@ -1,54 +0,0 @@ -package marshal - -import ( - "encoding/json" - "fmt" - "strconv" - - yaml "github.com/mikefarah/yaml/v2" -) - -type JsonConverter interface { - JsonToString(context interface{}) (string, error) -} - -type jsonConverter struct{} - -func NewJsonConverter() JsonConverter { - return &jsonConverter{} -} - -func (j *jsonConverter) JsonToString(context interface{}) (string, error) { - out, err := json.Marshal(j.toJSON(context)) - if err != nil { - return "", fmt.Errorf("error printing yaml as json: %v", err) - } - return string(out), nil -} - -func (j *jsonConverter) toJSON(context interface{}) interface{} { - switch context := context.(type) { - case []interface{}: - oldArray := context - newArray := make([]interface{}, len(oldArray)) - for index, value := range oldArray { - newArray[index] = j.toJSON(value) - } - return newArray - case yaml.MapSlice: - oldMap := context - newMap := make(map[string]interface{}) - for _, entry := range oldMap { - if str, ok := entry.Key.(string); ok { - newMap[str] = j.toJSON(entry.Value) - } else if i, ok := entry.Key.(int); ok { - newMap[strconv.Itoa(i)] = j.toJSON(entry.Value) - } else if b, ok := entry.Key.(bool); ok { - newMap[strconv.FormatBool(b)] = j.toJSON(entry.Value) - } - } - return newMap - default: - return context - } -} diff --git a/pkg/marshal/json_converter_test.go b/pkg/marshal/json_converter_test.go deleted file mode 100644 index e49fbd28..00000000 --- a/pkg/marshal/json_converter_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package marshal - -import ( - "testing" - - "github.com/mikefarah/yq/v2/test" -) - -func TestJsonToString(t *testing.T) { - var data = test.ParseData(` ---- -b: - c: 2 -`) - got, _ := NewJsonConverter().JsonToString(data) - test.AssertResult(t, "{\"b\":{\"c\":2}}", got) -} - -func TestJsonToString_withIntKey(t *testing.T) { - var data = test.ParseData(` ---- -b: - 2: c -`) - got, _ := NewJsonConverter().JsonToString(data) - test.AssertResult(t, `{"b":{"2":"c"}}`, got) -} - -func TestJsonToString_withBoolKey(t *testing.T) { - var data = test.ParseData(` ---- -b: - false: c -`) - got, _ := NewJsonConverter().JsonToString(data) - test.AssertResult(t, `{"b":{"false":"c"}}`, got) -} - -func TestJsonToString_withArray(t *testing.T) { - var data = test.ParseData(` ---- -b: - - item: one - - item: two -`) - got, _ := NewJsonConverter().JsonToString(data) - test.AssertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got) -} diff --git a/pkg/marshal/yaml_converter.go b/pkg/marshal/yaml_converter.go deleted file mode 100644 index b5826da8..00000000 --- a/pkg/marshal/yaml_converter.go +++ /dev/null @@ -1,43 +0,0 @@ -package marshal - -import ( - "strings" - - yaml "github.com/mikefarah/yaml/v2" - errors "github.com/pkg/errors" -) - -type YamlConverter interface { - YamlToString(context interface{}, trimOutput bool) (string, error) -} - -type yamlConverter struct{} - -func NewYamlConverter() YamlConverter { - return &yamlConverter{} -} - -func (y *yamlConverter) YamlToString(context interface{}, trimOutput bool) (string, error) { - switch context := context.(type) { - case string: - return context, nil - default: - return y.marshalContext(context, trimOutput) - } -} - -func (y *yamlConverter) marshalContext(context interface{}, trimOutput bool) (string, error) { - out, err := yaml.Marshal(context) - - if err != nil { - return "", errors.Wrap(err, "error printing yaml") - } - - outStr := string(out) - // trim the trailing new line as it's easier for a script to add - // it in if required than to remove it - if trimOutput { - return strings.Trim(outStr, "\n "), nil - } - return outStr, nil -} diff --git a/pkg/marshal/yaml_converter_test.go b/pkg/marshal/yaml_converter_test.go deleted file mode 100644 index 35e9a4d1..00000000 --- a/pkg/marshal/yaml_converter_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package marshal - -import ( - "testing" - - "github.com/mikefarah/yq/v2/test" -) - -func TestYamlToString(t *testing.T) { - var raw = `b: - c: 2 -` - var data = test.ParseData(raw) - got, _ := NewYamlConverter().YamlToString(data, false) - test.AssertResult(t, raw, got) -} - -func TestYamlToString_withTrim(t *testing.T) { - var raw = `b: - c: 2` - var data = test.ParseData(raw) - got, _ := NewYamlConverter().YamlToString(data, true) - test.AssertResult(t, raw, got) -} - -func TestYamlToString_withIntKey(t *testing.T) { - var raw = `b: - 2: c -` - var data = test.ParseData(raw) - got, _ := NewYamlConverter().YamlToString(data, false) - test.AssertResult(t, raw, got) -} - -func TestYamlToString_withBoolKey(t *testing.T) { - var raw = `b: - false: c -` - var data = test.ParseData(raw) - got, _ := NewYamlConverter().YamlToString(data, false) - test.AssertResult(t, raw, got) -} - -func TestYamlToString_withArray(t *testing.T) { - var raw = `b: -- item: one -- item: two -` - var data = test.ParseData(raw) - got, _ := NewYamlConverter().YamlToString(data, false) - test.AssertResult(t, raw, got) -} diff --git a/yq.go b/yq.go index 5a21169c..c0d576c4 100644 --- a/yq.go +++ b/yq.go @@ -8,7 +8,6 @@ import ( "os" "strconv" - "github.com/mikefarah/yq/v3/pkg/marshal" "github.com/mikefarah/yq/v3/pkg/yqlib" errors "github.com/pkg/errors" @@ -31,8 +30,6 @@ var version = false var docIndex = "0" var log = logging.MustGetLogger("yq") var lib = yqlib.NewYqLib(log) -var jsonConverter = marshal.NewJsonConverter() -var yamlConverter = marshal.NewYamlConverter() var valueParser = yqlib.NewValueParser() func main() { @@ -595,13 +592,6 @@ func parseValue(argument string) yaml.Node { return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} } -func toString(context interface{}) (string, error) { - if outputToJSON { - return jsonConverter.JsonToString(context) - } - return yamlConverter.YamlToString(context, true) -} - func safelyRenameFile(from string, to string) { if renameError := os.Rename(from, to); renameError != nil { log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) From dad61ec615a1ace97cd2505fb3308a0584463317 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Dec 2019 16:52:00 +1100 Subject: [PATCH 04/68] remove json conversion for now --- yq.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/yq.go b/yq.go index c0d576c4..d61dc7e1 100644 --- a/yq.go +++ b/yq.go @@ -21,7 +21,6 @@ var rawOutput = false var customTag = "" var writeInplace = false var writeScript = "" -var outputToJSON = false var overwriteFlag = false var allowEmptyFlag = false var appendFlag = false @@ -105,7 +104,6 @@ yq r -- things.yaml --key-starting-with-dashes } cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdRead.PersistentFlags().BoolVarP(&rawOutput, "raw", "r", false, "raw yaml output - prints out values instead of yaml") - cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") return cmdRead } From d97f1d8be2fd3cf0d1b0be6f658aab9ea42a9b84 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 8 Dec 2019 15:37:30 +1100 Subject: [PATCH 05/68] recurse --- pkg/yqlib/data_navigator.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 744cdbcb..53160a3c 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -1,6 +1,7 @@ package yqlib import ( + "bytes" "strconv" logging "gopkg.in/op/go-logging.v1" @@ -29,6 +30,7 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { } if len(path) > 0 { n.log.Debugf("diving into %v", path[0]) + n.debugNode(value) return n.recurse(realValue, path[0], path[1:]) } return realValue, nil @@ -37,7 +39,7 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { func (n *navigator) guessKind(tail []string) yaml.Kind { n.log.Debug("tail %v", tail) if len(tail) == 0 { - n.log.Debug("scalar") + n.log.Debug("end of path, must be a scalar") return yaml.ScalarNode } var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64) @@ -53,11 +55,23 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y // when reading, it should deal with the original kind // when writing, it will clobber the kind anyway if original.Kind != expectedKind && (expectedKind != yaml.ScalarNode) { + n.log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind) return &yaml.Node{Kind: expectedKind} } return original } +func (n *navigator) debugNode(value *yaml.Node) { + if n.log.IsEnabledFor(logging.DEBUG) { + buf := new(bytes.Buffer) + encoder := yaml.NewEncoder(buf) + encoder.Encode(value) + encoder.Close() + n.log.Debug("Tag: %v", value.Tag) + n.log.Debug("%v", buf.String()) + } +} + func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml.Node, error) { switch value.Kind { case yaml.MappingNode: @@ -77,20 +91,24 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml n.log.Debug("adding new node %v", value.Content) return n.Get(&mapEntryValue, tail) case yaml.SequenceNode: - n.log.Debug("its a sequence of %v things!", len(value.Content)) + n.log.Debug("its a sequence of %v things!, %v", len(value.Content)) if head == "*" { - var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} - newNode.Content = make([]*yaml.Node, len(value.Content)) + originalContent := value.Content + value.Content = make([]*yaml.Node, len(value.Content)) - for index, value := range value.Content { - value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail)) - var nestedValue, err = n.Get(value.Content[index], tail) + for index, childValue := range originalContent { + n.log.Debug("processing") + n.debugNode(childValue) + childValue = n.getOrReplace(childValue, n.guessKind(tail)) + var nestedValue, err = n.Get(childValue, tail) + n.log.Debug("nestedValue") + n.debugNode(nestedValue) if err != nil { return nil, err } - newNode.Content[index] = nestedValue + value.Content[index] = nestedValue } - return &newNode, nil + return value, nil } else if head == "+" { var newNode = yaml.Node{Kind: n.guessKind(tail)} From 8da9a8170276be238b01ed1cb30310869e21f8af Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 8 Dec 2019 15:59:24 +1100 Subject: [PATCH 06/68] visitor! --- pkg/yqlib/data_navigator.go | 71 ++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 53160a3c..a49dd6f7 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -17,6 +17,12 @@ type navigator struct { log *logging.Logger } +type VisitorFn func(*yaml.Node) (*yaml.Node, error) + +func identityVisitor(value *yaml.Node) (*yaml.Node, error) { + return value, nil +} + func NewDataNavigator(l *logging.Logger) DataNavigator { return &navigator{ log: l, @@ -24,6 +30,29 @@ func NewDataNavigator(l *logging.Logger) DataNavigator { } func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { + return n.Visit(value, path, identityVisitor) +} + +func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.Node) error { + _, errorVisiting := n.Visit(value, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { + n.log.Debug("going to update") + n.debugNode(nodeToUpdate) + n.log.Debug("with") + n.debugNode(&changesToApply) + nodeToUpdate.Value = changesToApply.Value + nodeToUpdate.Tag = changesToApply.Tag + nodeToUpdate.Kind = changesToApply.Kind + nodeToUpdate.Style = changesToApply.Style + nodeToUpdate.Content = changesToApply.Content + nodeToUpdate.HeadComment = changesToApply.HeadComment + nodeToUpdate.LineComment = changesToApply.LineComment + nodeToUpdate.FootComment = changesToApply.FootComment + return nodeToUpdate, nil + }) + return errorVisiting +} + +func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) { realValue := value if realValue.Kind == yaml.DocumentNode { realValue = value.Content[0] @@ -31,9 +60,9 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { if len(path) > 0 { n.log.Debugf("diving into %v", path[0]) n.debugNode(value) - return n.recurse(realValue, path[0], path[1:]) + return n.recurse(realValue, path[0], path[1:], visitor) } - return realValue, nil + return visitor(realValue) } func (n *navigator) guessKind(tail []string) yaml.Kind { @@ -72,7 +101,7 @@ func (n *navigator) debugNode(value *yaml.Node) { } } -func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml.Node, error) { +func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { switch value.Kind { case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) @@ -83,38 +112,38 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml continue } value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail)) - return n.Get(value.Content[index+1], tail) + return n.Visit(value.Content[index+1], tail, visitor) } value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) mapEntryValue := yaml.Node{Kind: n.guessKind(tail)} value.Content = append(value.Content, &mapEntryValue) n.log.Debug("adding new node %v", value.Content) - return n.Get(&mapEntryValue, tail) + return n.Visit(&mapEntryValue, tail, visitor) case yaml.SequenceNode: n.log.Debug("its a sequence of %v things!, %v", len(value.Content)) if head == "*" { - originalContent := value.Content - value.Content = make([]*yaml.Node, len(value.Content)) + var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} + newNode.Content = make([]*yaml.Node, len(value.Content)) - for index, childValue := range originalContent { + for index, childValue := range value.Content { n.log.Debug("processing") n.debugNode(childValue) childValue = n.getOrReplace(childValue, n.guessKind(tail)) - var nestedValue, err = n.Get(childValue, tail) + var nestedValue, err = n.Visit(childValue, tail, visitor) n.log.Debug("nestedValue") n.debugNode(nestedValue) if err != nil { return nil, err } - value.Content[index] = nestedValue + newNode.Content[index] = nestedValue } - return value, nil + return &newNode, nil } else if head == "+" { var newNode = yaml.Node{Kind: n.guessKind(tail)} value.Content = append(value.Content, &newNode) n.log.Debug("appending a new node, %v", value.Content) - return n.Get(&newNode, tail) + return n.Visit(&newNode, tail, visitor) } var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { @@ -124,28 +153,12 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml return nil, nil } value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail)) - return n.Get(value.Content[index], tail) + return n.Visit(value.Content[index], tail, visitor) default: return nil, nil } } -func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, changesToApply yaml.Node) error { - nodeToUpdate, errorRecursing := n.Get(dataBucket, remainingPath) - if errorRecursing != nil { - return errorRecursing - } - nodeToUpdate.Value = changesToApply.Value - nodeToUpdate.Tag = changesToApply.Tag - nodeToUpdate.Kind = changesToApply.Kind - nodeToUpdate.Style = changesToApply.Style - nodeToUpdate.Content = changesToApply.Content - nodeToUpdate.HeadComment = changesToApply.HeadComment - nodeToUpdate.LineComment = changesToApply.LineComment - nodeToUpdate.FootComment = changesToApply.FootComment - return nil -} - // func matchesKey(key string, actual interface{}) bool { // var actualString = fmt.Sprintf("%v", actual) // var prefixMatch = strings.TrimSuffix(key, "*") From 9771e7001c3f7ea9ecb88ff7ced976621d2f41f3 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 9 Dec 2019 13:44:53 +1100 Subject: [PATCH 07/68] splatting --- commands_test.go | 135 +++--- compare.sh | 13 + examples/sample.yaml | 13 +- pkg/yqlib/data_navigator.go | 54 ++- pkg/yqlib/data_navigator_test.go | 694 +++++++++++++++---------------- pkg/yqlib/lib.go | 8 +- pkg/yqlib/lib_test.go | 2 +- pkg/yqlib/path_parser.go | 3 + pkg/yqlib/path_parser_test.go | 2 +- pkg/yqlib/value_parser_test.go | 2 +- test/utils.go | 6 +- yq.go | 6 +- yq_test.go | 104 ++--- 13 files changed, 527 insertions(+), 515 deletions(-) create mode 100755 compare.sh diff --git a/commands_test.go b/commands_test.go index 90409cb8..49f12578 100644 --- a/commands_test.go +++ b/commands_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/mikefarah/yq/v2/test" + "github.com/mikefarah/yq/v3/test" "github.com/spf13/cobra" ) @@ -63,30 +63,6 @@ func TestRootCmd_VerboseShort(t *testing.T) { } } -func TestRootCmd_TrimLong(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "--trim") - if result.Error != nil { - t.Error(result.Error) - } - - if !trimOutput { - t.Error("Expected trimOutput to be true") - } -} - -func TestRootCmd_TrimShort(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "-t") - if result.Error != nil { - t.Error(result.Error) - } - - if !trimOutput { - t.Error("Expected trimOutput to be true") - } -} - func TestRootCmd_VersionShort(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "-V") @@ -191,7 +167,7 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { expectedOutput := `- become: true gather_facts: false hosts: lalaland - name: Apply smth + name: "Apply smth" roles: - lala - land @@ -209,7 +185,7 @@ func TestReadCmd_ArrayYaml_OneElement(t *testing.T) { expectedOutput := `become: true gather_facts: false hosts: lalaland -name: Apply smth +name: "Apply smth" roles: - lala - land @@ -227,7 +203,7 @@ func TestReadCmd_ArrayYaml_Splat(t *testing.T) { expectedOutput := `- become: true gather_facts: false hosts: lalaland - name: Apply smth + name: "Apply smth" roles: - lala - land @@ -252,19 +228,19 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { if result.Error == nil { t.Error("Expected command to fail due to invalid path") } - expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` + expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax` test.AssertResult(t, expectedOutput, result.Error.Error()) } -func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") +// if result.Error == nil { +// t.Error("Expected command to fail due to invalid path") +// } +// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } func TestReadCmd_Error(t *testing.T) { cmd := getRootCommand() @@ -301,27 +277,27 @@ func TestReadCmd_ErrorUnreadableFile(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -func TestReadCmd_ErrorBadPath(t *testing.T) { - content := `b: - d: - e: - - 3 - - 4 - f: - - 1 - - 2 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestReadCmd_ErrorBadPath(t *testing.T) { +// content := `b: +// d: +// e: +// - 3 +// - 4 +// f: +// - 1 +// - 2 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) - if result.Error == nil { - t.Fatal("Expected command to fail due to invalid path") - } - expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) +// if result.Error == nil { +// t.Fatal("Expected command to fail due to invalid path") +// } +// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } func TestReadCmd_Verbose(t *testing.T) { cmd := getRootCommand() @@ -332,32 +308,23 @@ func TestReadCmd_Verbose(t *testing.T) { test.AssertResult(t, "2\n", result.Output) } -func TestReadCmd_NoTrim(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "--trim=false read examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n\n", result.Output) -} +// func TestReadCmd_ToJson(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c") +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, "2\n", result.Output) +// } -func TestReadCmd_ToJson(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadCmd_ToJsonLong(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} +// func TestReadCmd_ToJsonLong(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c") +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, "2\n", result.Output) +// } func TestPrefixCmd(t *testing.T) { content := `b: @@ -851,7 +818,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) { t.Error(result.Error) } expectedOutput := `b: - c: thing + c: {} d: another thing ` test.AssertResult(t, expectedOutput, result.Output) diff --git a/compare.sh b/compare.sh new file mode 100755 index 00000000..fc8cd8bf --- /dev/null +++ b/compare.sh @@ -0,0 +1,13 @@ +GREEN='\033[0;32m' +NC='\033[0m' + +echo "${GREEN}---Old---${NC}" +yq $@ > /tmp/yq-old-output +cat /tmp/yq-old-output + +echo "${GREEN}---New---${NC}" +./yq $@ > /tmp/yq-new-output +cat /tmp/yq-new-output + +echo "${GREEN}---Diff---${NC}" +colordiff /tmp/yq-old-output /tmp/yq-new-output \ No newline at end of file diff --git a/examples/sample.yaml b/examples/sample.yaml index b26830e8..e847c407 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,9 +1,8 @@ a: Easy! as one two three b: - c: 2 - d: [3, 4] - e: - - name: fred - value: 3 - - name: sam - value: 4 \ No newline at end of file + c: + name: c1 + f: things + d: + name: d1 + f: other \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index a49dd6f7..3776d4ef 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -9,6 +9,7 @@ import ( ) type DataNavigator interface { + DebugNode(node *yaml.Node) Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error) Update(rootNode *yaml.Node, remainingPath []string, changesToApply yaml.Node) error } @@ -36,9 +37,9 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.Node) error { _, errorVisiting := n.Visit(value, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { n.log.Debug("going to update") - n.debugNode(nodeToUpdate) + n.DebugNode(nodeToUpdate) n.log.Debug("with") - n.debugNode(&changesToApply) + n.DebugNode(&changesToApply) nodeToUpdate.Value = changesToApply.Value nodeToUpdate.Tag = changesToApply.Tag nodeToUpdate.Kind = changesToApply.Kind @@ -55,26 +56,33 @@ func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml. func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) { realValue := value if realValue.Kind == yaml.DocumentNode { + n.log.Debugf("its a document! returning the first child") realValue = value.Content[0] } if len(path) > 0 { n.log.Debugf("diving into %v", path[0]) - n.debugNode(value) + n.DebugNode(value) return n.recurse(realValue, path[0], path[1:], visitor) } return visitor(realValue) } -func (n *navigator) guessKind(tail []string) yaml.Kind { +func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind { n.log.Debug("tail %v", tail) - if len(tail) == 0 { + if len(tail) == 0 && guess == 0 { n.log.Debug("end of path, must be a scalar") return yaml.ScalarNode + } else if len(tail) == 0 { + return guess } + var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64) - if tail[0] == "*" || tail[0] == "+" || errorParsingInt == nil { + if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } + if tail[0] == "*" && guess == yaml.SequenceNode || guess == yaml.MappingNode { + return guess + } return yaml.MappingNode } @@ -90,7 +98,7 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y return original } -func (n *navigator) debugNode(value *yaml.Node) { +func (n *navigator) DebugNode(value *yaml.Node) { if n.log.IsEnabledFor(logging.DEBUG) { buf := new(bytes.Buffer) encoder := yaml.NewEncoder(buf) @@ -105,17 +113,33 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito switch value.Kind { case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) + if head == "*" { + var newNode = yaml.Node{Kind: yaml.SequenceNode} + for index, content := range value.Content { + if index%2 == 0 { + continue + } + content = n.getOrReplace(content, n.guessKind(tail, content.Kind)) + var nestedValue, err = n.Visit(content, tail, visitor) + if err != nil { + return nil, err + } + newNode.Content = append(newNode.Content, nestedValue) + } + return &newNode, nil + } + for index, content := range value.Content { // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. - if index%2 == 1 || content.Value != head { + if index%2 == 1 || (content.Value != head) { continue } - value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail)) + value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind)) return n.Visit(value.Content[index+1], tail, visitor) } value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) - mapEntryValue := yaml.Node{Kind: n.guessKind(tail)} + mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)} value.Content = append(value.Content, &mapEntryValue) n.log.Debug("adding new node %v", value.Content) return n.Visit(&mapEntryValue, tail, visitor) @@ -127,11 +151,11 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito for index, childValue := range value.Content { n.log.Debug("processing") - n.debugNode(childValue) - childValue = n.getOrReplace(childValue, n.guessKind(tail)) + n.DebugNode(childValue) + childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind)) var nestedValue, err = n.Visit(childValue, tail, visitor) n.log.Debug("nestedValue") - n.debugNode(nestedValue) + n.DebugNode(nestedValue) if err != nil { return nil, err } @@ -140,7 +164,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito return &newNode, nil } else if head == "+" { - var newNode = yaml.Node{Kind: n.guessKind(tail)} + var newNode = yaml.Node{Kind: n.guessKind(tail, 0)} value.Content = append(value.Content, &newNode) n.log.Debug("appending a new node, %v", value.Content) return n.Visit(&newNode, tail, visitor) @@ -152,7 +176,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito if index >= int64(len(value.Content)) { return nil, nil } - value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail)) + value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind)) return n.Visit(value.Content[index], tail, visitor) default: return nil, nil diff --git a/pkg/yqlib/data_navigator_test.go b/pkg/yqlib/data_navigator_test.go index 614a4977..d537d665 100644 --- a/pkg/yqlib/data_navigator_test.go +++ b/pkg/yqlib/data_navigator_test.go @@ -1,397 +1,397 @@ package yqlib -import ( - "fmt" - "sort" - "testing" +// import ( +// "fmt" +// "sort" +// "testing" - "github.com/mikefarah/yq/v2/test" - logging "gopkg.in/op/go-logging.v1" -) +// "github.com/mikefarah/yq/v2/test" +// logging "gopkg.in/op/go-logging.v1" +// ) -func TestDataNavigator(t *testing.T) { - var log = logging.MustGetLogger("yq") - subject := NewDataNavigator(log) +// func TestDataNavigator(t *testing.T) { +// var log = logging.MustGetLogger("yq") +// subject := NewDataNavigator(log) - t.Run("TestReadMap_simple", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - c: 2 -`) - got, _ := subject.ReadChildValue(data, []string{"b", "c"}) - test.AssertResult(t, 2, got) - }) +// t.Run("TestReadMap_simple", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// c: 2 +// `) +// got, _ := subject.ReadChildValue(data, []string{"b", "c"}) +// test.AssertResult(t, 2, got) +// }) - t.Run("TestReadMap_numberKey", func(t *testing.T) { - var data = test.ParseData(` ---- -200: things -`) - got, _ := subject.ReadChildValue(data, []string{"200"}) - test.AssertResult(t, "things", got) - }) +// t.Run("TestReadMap_numberKey", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// 200: things +// `) +// got, _ := subject.ReadChildValue(data, []string{"200"}) +// test.AssertResult(t, "things", got) +// }) - t.Run("TestReadMap_splat", func(t *testing.T) { - var data = test.ParseData(` ---- -mapSplat: - item1: things - item2: whatever - otherThing: cat -`) - res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"}) - test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res)) - }) +// t.Run("TestReadMap_splat", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// mapSplat: +// item1: things +// item2: whatever +// otherThing: cat +// `) +// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"}) +// test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res)) +// }) - t.Run("TestReadMap_prefixSplat", func(t *testing.T) { - var data = test.ParseData(` ---- -mapSplat: - item1: things - item2: whatever - otherThing: cat -`) - res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"}) - test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res)) - }) +// t.Run("TestReadMap_prefixSplat", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// mapSplat: +// item1: things +// item2: whatever +// otherThing: cat +// `) +// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"}) +// test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res)) +// }) - t.Run("TestReadMap_deep_splat", func(t *testing.T) { - var data = test.ParseData(` ---- -mapSplatDeep: - item1: - cats: bananas - item2: - cats: apples -`) +// t.Run("TestReadMap_deep_splat", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// mapSplatDeep: +// item1: +// cats: bananas +// item2: +// cats: apples +// `) - res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"}) - result := res.([]interface{}) - var actual = []string{result[0].(string), result[1].(string)} - sort.Strings(actual) - test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual)) - }) +// res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"}) +// result := res.([]interface{}) +// var actual = []string{result[0].(string), result[1].(string)} +// sort.Strings(actual) +// test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual)) +// }) - t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - c: 2 -`) - got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"}) - test.AssertResult(t, nil, got) - }) +// t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// c: 2 +// `) +// got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"}) +// test.AssertResult(t, nil, got) +// }) - t.Run("TestReadMap_recurse_against_string", func(t *testing.T) { - var data = test.ParseData(` ---- -a: cat -`) - got, _ := subject.ReadChildValue(data, []string{"a", "b"}) - test.AssertResult(t, nil, got) - }) +// t.Run("TestReadMap_recurse_against_string", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// a: cat +// `) +// got, _ := subject.ReadChildValue(data, []string{"a", "b"}) +// test.AssertResult(t, nil, got) +// }) - t.Run("TestReadMap_with_array", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - d: - - 3 - - 4 -`) - got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"}) - test.AssertResult(t, 4, got) - }) +// t.Run("TestReadMap_with_array", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// d: +// - 3 +// - 4 +// `) +// got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"}) +// test.AssertResult(t, 4, got) +// }) - t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - d: - - 3 - - 4 -`) - _, err := subject.ReadChildValue(data, []string{"b", "d", "x"}) - if err == nil { - t.Fatal("Expected error due to invalid path") - } - expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, err.Error()) - }) +// t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// d: +// - 3 +// - 4 +// `) +// _, err := subject.ReadChildValue(data, []string{"b", "d", "x"}) +// if err == nil { +// t.Fatal("Expected error due to invalid path") +// } +// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` +// test.AssertResult(t, expectedOutput, err.Error()) +// }) - t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - d: - e: - - 3 - - 4 - f: - - 1 - - 2 -`) - _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"}) - if err == nil { - t.Fatal("Expected error due to invalid path") - } - expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, err.Error()) - }) +// t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// d: +// e: +// - 3 +// - 4 +// f: +// - 1 +// - 2 +// `) +// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"}) +// if err == nil { +// t.Fatal("Expected error due to invalid path") +// } +// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` +// test.AssertResult(t, expectedOutput, err.Error()) +// }) - t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - d: - - names: - - fred - - smith - - names: - - sam - - bo -`) - _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"}) - if err == nil { - t.Fatal("Expected error due to invalid path") - } - expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, err.Error()) - }) +// t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// d: +// - names: +// - fred +// - smith +// - names: +// - sam +// - bo +// `) +// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"}) +// if err == nil { +// t.Fatal("Expected error due to invalid path") +// } +// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` +// test.AssertResult(t, expectedOutput, err.Error()) +// }) - t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - d: - - 3 - - 4 -`) - got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"}) - test.AssertResult(t, nil, got) - }) +// t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// d: +// - 3 +// - 4 +// `) +// got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"}) +// test.AssertResult(t, nil, got) +// }) - t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - d: - - 3 - - 4 -`) - got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"}) - test.AssertResult(t, nil, got) - }) +// t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) { +// var data = test.ParseData(` +// --- +// b: +// d: +// - 3 +// - 4 +// `) +// got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"}) +// test.AssertResult(t, nil, got) +// }) - t.Run("TestReadMap_with_array_splat", func(t *testing.T) { - var data = test.ParseData(` -e: - - - name: Fred - thing: cat - - - name: Sam - thing: dog -`) - got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"}) - test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got)) - }) +// t.Run("TestReadMap_with_array_splat", func(t *testing.T) { +// var data = test.ParseData(` +// e: +// - +// name: Fred +// thing: cat +// - +// name: Sam +// thing: dog +// `) +// got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"}) +// test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got)) +// }) - t.Run("TestWrite_really_simple", func(t *testing.T) { - var data = test.ParseData(` -b: 2 -`) +// t.Run("TestWrite_really_simple", func(t *testing.T) { +// var data = test.ParseData(` +// b: 2 +// `) - updated := subject.UpdatedChildValue(data, []string{"b"}, "4") - test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b"}, "4") +// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_simple", func(t *testing.T) { - var data = test.ParseData(` -b: - c: 2 -`) +// t.Run("TestWrite_simple", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// c: 2 +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4") - test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4") +// test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_new", func(t *testing.T) { - var data = test.ParseData(` -b: - c: 2 -`) +// t.Run("TestWrite_new", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// c: 2 +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4") - test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4") +// test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_new_deep", func(t *testing.T) { - var data = test.ParseData(` -b: - c: 2 -`) +// t.Run("TestWrite_new_deep", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// c: 2 +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4") - test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4") +// test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_array", func(t *testing.T) { - var data = test.ParseData(` -b: - - aa -`) +// t.Run("TestWrite_array", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// - aa +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb") +// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb") - test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated)) - }) +// test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_new_array", func(t *testing.T) { - var data = test.ParseData(` -b: - c: 2 -`) +// t.Run("TestWrite_new_array", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// c: 2 +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4") - test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4") +// test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_new_array_deep", func(t *testing.T) { - var data = test.ParseData(` -a: apple -`) +// t.Run("TestWrite_new_array_deep", func(t *testing.T) { +// var data = test.ParseData(` +// a: apple +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4") - test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4") +// test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_new_map_array_deep", func(t *testing.T) { - var data = test.ParseData(` -b: - c: 2 -`) +// t.Run("TestWrite_new_map_array_deep", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// c: 2 +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4") - test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4") +// test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_add_to_array", func(t *testing.T) { - var data = test.ParseData(` -b: - - aa -`) +// t.Run("TestWrite_add_to_array", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// - aa +// `) - updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb") - test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated)) - }) +// updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb") +// test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWrite_with_no_tail", func(t *testing.T) { - var data = test.ParseData(` -b: - c: 2 -`) - updated := subject.UpdatedChildValue(data, []string{"b"}, "4") +// t.Run("TestWrite_with_no_tail", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// c: 2 +// `) +// updated := subject.UpdatedChildValue(data, []string{"b"}, "4") - test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) - }) +// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) +// }) - t.Run("TestWriteMap_no_paths", func(t *testing.T) { - var data = test.ParseData(` -b: 5 -`) - var new = test.ParseData(` -c: 4 -`) - result := subject.UpdatedChildValue(data, []string{}, new) - test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) - }) +// t.Run("TestWriteMap_no_paths", func(t *testing.T) { +// var data = test.ParseData(` +// b: 5 +// `) +// var new = test.ParseData(` +// c: 4 +// `) +// result := subject.UpdatedChildValue(data, []string{}, new) +// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) +// }) - t.Run("TestWriteArray_no_paths", func(t *testing.T) { - var data = make([]interface{}, 1) - data[0] = "mike" - var new = test.ParseData(` -c: 4 -`) - result := subject.UpdatedChildValue(data, []string{}, new) - test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) - }) +// t.Run("TestWriteArray_no_paths", func(t *testing.T) { +// var data = make([]interface{}, 1) +// data[0] = "mike" +// var new = test.ParseData(` +// c: 4 +// `) +// result := subject.UpdatedChildValue(data, []string{}, new) +// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) +// }) - t.Run("TestDelete_MapItem", func(t *testing.T) { - var data = test.ParseData(` -a: 123 -b: 456 -`) - var expected = test.ParseData(` -b: 456 -`) +// t.Run("TestDelete_MapItem", func(t *testing.T) { +// var data = test.ParseData(` +// a: 123 +// b: 456 +// `) +// var expected = test.ParseData(` +// b: 456 +// `) - result, _ := subject.DeleteChildValue(data, []string{"a"}) - test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) - }) +// result, _ := subject.DeleteChildValue(data, []string{"a"}) +// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) +// }) - // Ensure deleting an index into a string does nothing - t.Run("TestDelete_index_to_string", func(t *testing.T) { - var data = test.ParseData(` -a: mystring -`) - result, _ := subject.DeleteChildValue(data, []string{"a", "0"}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) - }) +// // Ensure deleting an index into a string does nothing +// t.Run("TestDelete_index_to_string", func(t *testing.T) { +// var data = test.ParseData(` +// a: mystring +// `) +// result, _ := subject.DeleteChildValue(data, []string{"a", "0"}) +// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) +// }) - t.Run("TestDelete_list_index", func(t *testing.T) { - var data = test.ParseData(` -a: [3, 4] -`) - var expected = test.ParseData(` -a: [3] -`) - result, _ := subject.DeleteChildValue(data, []string{"a", "1"}) - test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) - }) +// t.Run("TestDelete_list_index", func(t *testing.T) { +// var data = test.ParseData(` +// a: [3, 4] +// `) +// var expected = test.ParseData(` +// a: [3] +// `) +// result, _ := subject.DeleteChildValue(data, []string{"a", "1"}) +// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) +// }) - t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) { - var data = test.ParseData(` -a: [3, 4] -`) - result, _ := subject.DeleteChildValue(data, []string{"a", "5"}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) - }) +// t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) { +// var data = test.ParseData(` +// a: [3, 4] +// `) +// result, _ := subject.DeleteChildValue(data, []string{"a", "5"}) +// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) +// }) - t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) { - var data = test.ParseData(` -a: [3, 4] -`) - result, _ := subject.DeleteChildValue(data, []string{"a", "2"}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) - }) +// t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) { +// var data = test.ParseData(` +// a: [3, 4] +// `) +// result, _ := subject.DeleteChildValue(data, []string{"a", "2"}) +// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) +// }) - t.Run("TestDelete_no_paths", func(t *testing.T) { - var data = test.ParseData(` -a: [3, 4] -b: - - name: test -`) - result, _ := subject.DeleteChildValue(data, []string{}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) - }) +// t.Run("TestDelete_no_paths", func(t *testing.T) { +// var data = test.ParseData(` +// a: [3, 4] +// b: +// - name: test +// `) +// result, _ := subject.DeleteChildValue(data, []string{}) +// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) +// }) - t.Run("TestDelete_array_map_item", func(t *testing.T) { - var data = test.ParseData(` -b: -- name: fred - value: blah -- name: john - value: test -`) - var expected = test.ParseData(` -b: -- value: blah -- name: john - value: test -`) - result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"}) - test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) - }) -} +// t.Run("TestDelete_array_map_item", func(t *testing.T) { +// var data = test.ParseData(` +// b: +// - name: fred +// value: blah +// - name: john +// value: test +// `) +// var expected = test.ParseData(` +// b: +// - value: blah +// - name: john +// value: test +// `) +// result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"}) +// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) +// }) +// } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 5b630e82..10d30eec 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -14,6 +14,7 @@ type UpdateCommand struct { } type YqLib interface { + DebugNode(node *yaml.Node) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error } @@ -32,10 +33,11 @@ func NewYqLib(l *logging.Logger) YqLib { } } +func (l *lib) DebugNode(node *yaml.Node) { + l.navigator.DebugNode(node) +} + func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { - if path == "" { - return rootNode, nil - } var paths = l.parser.ParsePath(path) return l.navigator.Get(rootNode, paths) } diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index bcfac5ff..b9c3e4de 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/mikefarah/yq/v2/test" + "github.com/mikefarah/yq/v3/test" logging "gopkg.in/op/go-logging.v1" ) diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index f8163cca..83157054 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -11,6 +11,9 @@ func NewPathParser() PathParser { } func (p *pathParser) ParsePath(path string) []string { + if path == "" { + return []string{} + } return p.parsePathAccum([]string{}, path) } diff --git a/pkg/yqlib/path_parser_test.go b/pkg/yqlib/path_parser_test.go index c2d7fe26..460d3fe9 100644 --- a/pkg/yqlib/path_parser_test.go +++ b/pkg/yqlib/path_parser_test.go @@ -3,7 +3,7 @@ package yqlib import ( "testing" - "github.com/mikefarah/yq/v2/test" + "github.com/mikefarah/yq/v3/test" ) var parsePathsTests = []struct { diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go index 2246b398..24468e37 100644 --- a/pkg/yqlib/value_parser_test.go +++ b/pkg/yqlib/value_parser_test.go @@ -3,7 +3,7 @@ package yqlib import ( "testing" - "github.com/mikefarah/yq/v2/test" + "github.com/mikefarah/yq/v3/test" ) var parseValueTests = []struct { diff --git a/test/utils.go b/test/utils.go index 490ce543..25093e2f 100644 --- a/test/utils.go +++ b/test/utils.go @@ -9,8 +9,8 @@ import ( "strings" "testing" - yaml "github.com/mikefarah/yaml/v2" "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" ) type resulter struct { @@ -30,8 +30,8 @@ func RunCmd(c *cobra.Command, input string) resulter { return resulter{err, output, c} } -func ParseData(rawData string) yaml.MapSlice { - var parsedData yaml.MapSlice +func ParseData(rawData string) yaml.Node { + var parsedData yaml.Node err := yaml.Unmarshal([]byte(rawData), &parsedData) if err != nil { fmt.Printf("Error parsing yaml: %v\n", err) diff --git a/yq.go b/yq.go index d61dc7e1..7fec3c1c 100644 --- a/yq.go +++ b/yq.go @@ -257,11 +257,14 @@ func readProperty(cmd *cobra.Command, args []string) error { } var mappedDocs []*yaml.Node - var dataBucket yaml.Node + var currentIndex = 0 var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error { for { + var dataBucket yaml.Node errorReading := decoder.Decode(&dataBucket) + log.Debugf("decoded node for doc %v", currentIndex) + lib.DebugNode(&dataBucket) if errorReading == io.EOF { log.Debugf("done %v / %v", currentIndex, docIndexInt) if !updateAll && currentIndex <= docIndexInt { @@ -273,6 +276,7 @@ func readProperty(cmd *cobra.Command, args []string) error { if updateAll || currentIndex == docIndexInt { log.Debugf("reading %v in document %v", path, currentIndex) mappedDoc, errorParsing := lib.Get(&dataBucket, path) + lib.DebugNode(mappedDoc) if errorParsing != nil { return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) } diff --git a/yq_test.go b/yq_test.go index 677f7bd0..b131d01f 100644 --- a/yq_test.go +++ b/yq_test.go @@ -1,60 +1,60 @@ package main -import ( - "fmt" - "runtime" - "testing" +// import ( +// "fmt" +// "runtime" +// "testing" - "github.com/mikefarah/yq/v2/pkg/marshal" - "github.com/mikefarah/yq/v2/test" -) +// "github.com/mikefarah/yq/v2/pkg/marshal" +// "github.com/mikefarah/yq/v2/test" +// ) -func TestMultilineString(t *testing.T) { - testString := ` - abcd - efg` - formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false) - test.AssertResult(t, testString, formattedResult) -} +// func TestMultilineString(t *testing.T) { +// testString := ` +// abcd +// efg` +// formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false) +// test.AssertResult(t, testString, formattedResult) +// } -func TestNewYaml(t *testing.T) { - result, _ := newYaml([]string{"b.c", "3"}) - formattedResult := fmt.Sprintf("%v", result) - test.AssertResult(t, - "[{b [{c 3}]}]", - formattedResult) -} +// func TestNewYaml(t *testing.T) { +// result, _ := newYaml([]string{"b.c", "3"}) +// formattedResult := fmt.Sprintf("%v", result) +// test.AssertResult(t, +// "[{b [{c 3}]}]", +// formattedResult) +// } -func TestNewYamlArray(t *testing.T) { - result, _ := newYaml([]string{"[0].cat", "meow"}) - formattedResult := fmt.Sprintf("%v", result) - test.AssertResult(t, - "[[{cat meow}]]", - formattedResult) -} +// func TestNewYamlArray(t *testing.T) { +// result, _ := newYaml([]string{"[0].cat", "meow"}) +// formattedResult := fmt.Sprintf("%v", result) +// test.AssertResult(t, +// "[[{cat meow}]]", +// formattedResult) +// } -func TestNewYaml_WithScript(t *testing.T) { - writeScript = "examples/instruction_sample.yaml" - expectedResult := `b: - c: cat - e: - - name: Mike Farah` - result, _ := newYaml([]string{""}) - actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true) - test.AssertResult(t, expectedResult, actualResult) -} +// func TestNewYaml_WithScript(t *testing.T) { +// writeScript = "examples/instruction_sample.yaml" +// expectedResult := `b: +// c: cat +// e: +// - name: Mike Farah` +// result, _ := newYaml([]string{""}) +// actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true) +// test.AssertResult(t, expectedResult, actualResult) +// } -func TestNewYaml_WithUnknownScript(t *testing.T) { - writeScript = "fake-unknown" - _, err := newYaml([]string{""}) - if err == nil { - t.Error("Expected error due to unknown file") - } - var expectedOutput string - if runtime.GOOS == "windows" { - expectedOutput = `open fake-unknown: The system cannot find the file specified.` - } else { - expectedOutput = `open fake-unknown: no such file or directory` - } - test.AssertResult(t, expectedOutput, err.Error()) -} +// func TestNewYaml_WithUnknownScript(t *testing.T) { +// writeScript = "fake-unknown" +// _, err := newYaml([]string{""}) +// if err == nil { +// t.Error("Expected error due to unknown file") +// } +// var expectedOutput string +// if runtime.GOOS == "windows" { +// expectedOutput = `open fake-unknown: The system cannot find the file specified.` +// } else { +// expectedOutput = `open fake-unknown: no such file or directory` +// } +// test.AssertResult(t, expectedOutput, err.Error()) +// } From 586ffb833b2d93bc5a37813a78469fef4ba426a4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 9 Dec 2019 13:57:10 +1100 Subject: [PATCH 08/68] Refactoring --- examples/sample.yaml | 13 ++-- pkg/yqlib/data_navigator.go | 140 ++++++++++++++++++++---------------- 2 files changed, 84 insertions(+), 69 deletions(-) diff --git a/examples/sample.yaml b/examples/sample.yaml index e847c407..b26830e8 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,8 +1,9 @@ a: Easy! as one two three b: - c: - name: c1 - f: things - d: - name: d1 - f: other \ No newline at end of file + c: 2 + d: [3, 4] + e: + - name: fred + value: 3 + - name: sam + value: 4 \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 3776d4ef..9486fb98 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -87,11 +87,7 @@ func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind { } func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { - // expected is a scalar when we reach the end of the path - // no need to clobber the original because: - // when reading, it should deal with the original kind - // when writing, it will clobber the kind anyway - if original.Kind != expectedKind && (expectedKind != yaml.ScalarNode) { + if original.Kind != expectedKind { n.log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind) return &yaml.Node{Kind: expectedKind} } @@ -114,75 +110,93 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) if head == "*" { - var newNode = yaml.Node{Kind: yaml.SequenceNode} - for index, content := range value.Content { - if index%2 == 0 { - continue - } - content = n.getOrReplace(content, n.guessKind(tail, content.Kind)) - var nestedValue, err = n.Visit(content, tail, visitor) - if err != nil { - return nil, err - } - newNode.Content = append(newNode.Content, nestedValue) - } - return &newNode, nil + return n.splatMap(value, tail, visitor) } - - for index, content := range value.Content { - // value.Content is a concatenated array of key, value, - // so keys are in the even indexes, values in odd. - if index%2 == 1 || (content.Value != head) { - continue - } - value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind)) - return n.Visit(value.Content[index+1], tail, visitor) - } - value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) - mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)} - value.Content = append(value.Content, &mapEntryValue) - n.log.Debug("adding new node %v", value.Content) - return n.Visit(&mapEntryValue, tail, visitor) + return n.recurseMap(value, head, tail, visitor) case yaml.SequenceNode: n.log.Debug("its a sequence of %v things!, %v", len(value.Content)) if head == "*" { - var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} - newNode.Content = make([]*yaml.Node, len(value.Content)) - - for index, childValue := range value.Content { - n.log.Debug("processing") - n.DebugNode(childValue) - childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind)) - var nestedValue, err = n.Visit(childValue, tail, visitor) - n.log.Debug("nestedValue") - n.DebugNode(nestedValue) - if err != nil { - return nil, err - } - newNode.Content[index] = nestedValue - } - return &newNode, nil + return n.splatArray(value, tail, visitor) } else if head == "+" { - - var newNode = yaml.Node{Kind: n.guessKind(tail, 0)} - value.Content = append(value.Content, &newNode) - n.log.Debug("appending a new node, %v", value.Content) - return n.Visit(&newNode, tail, visitor) + return n.appendArray(value, tail, visitor) } - var index, err = strconv.ParseInt(head, 10, 64) // nolint - if err != nil { - return nil, err - } - if index >= int64(len(value.Content)) { - return nil, nil - } - value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind)) - return n.Visit(value.Content[index], tail, visitor) + return n.recurseArray(value, head, tail, visitor) default: return nil, nil } } +func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { + var newNode = yaml.Node{Kind: yaml.SequenceNode} + for index, content := range value.Content { + if index%2 == 0 { + continue + } + content = n.getOrReplace(content, n.guessKind(tail, content.Kind)) + var nestedValue, err = n.Visit(content, tail, visitor) + if err != nil { + return nil, err + } + newNode.Content = append(newNode.Content, nestedValue) + } + return &newNode, nil +} + +func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { + for index, content := range value.Content { + // value.Content is a concatenated array of key, value, + // so keys are in the even indexes, values in odd. + if index%2 == 1 || (content.Value != head) { + continue + } + value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind)) + return n.Visit(value.Content[index+1], tail, visitor) + } + value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) + mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)} + value.Content = append(value.Content, &mapEntryValue) + n.log.Debug("adding new node %v", value.Content) + return n.Visit(&mapEntryValue, tail, visitor) +} + +func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { + var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} + newNode.Content = make([]*yaml.Node, len(value.Content)) + + for index, childValue := range value.Content { + n.log.Debug("processing") + n.DebugNode(childValue) + childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind)) + var nestedValue, err = n.Visit(childValue, tail, visitor) + n.log.Debug("nestedValue") + n.DebugNode(nestedValue) + if err != nil { + return nil, err + } + newNode.Content[index] = nestedValue + } + return &newNode, nil +} + +func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { + var newNode = yaml.Node{Kind: n.guessKind(tail, 0)} + value.Content = append(value.Content, &newNode) + n.log.Debug("appending a new node, %v", value.Content) + return n.Visit(&newNode, tail, visitor) +} + +func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { + var index, err = strconv.ParseInt(head, 10, 64) // nolint + if err != nil { + return nil, err + } + if index >= int64(len(value.Content)) { + return nil, nil + } + value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind)) + return n.Visit(value.Content[index], tail, visitor) +} + // func matchesKey(key string, actual interface{}) bool { // var actualString = fmt.Sprintf("%v", actual) // var prefixMatch = strings.TrimSuffix(key, "*") From 8c0046a622a3536bc2cf409c921969dea357890b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 9 Dec 2019 13:57:38 +1100 Subject: [PATCH 09/68] Refactoring --- pkg/yqlib/data_navigator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 9486fb98..3a2d1845 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -80,7 +80,7 @@ func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } - if tail[0] == "*" && guess == yaml.SequenceNode || guess == yaml.MappingNode { + if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } return yaml.MappingNode From d061b2f9f9e88de58d5af7ac12eae0b51ad17127 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 12 Dec 2019 20:47:22 +1100 Subject: [PATCH 10/68] Can delete arrays --- examples/sample.yaml | 2 +- pkg/yqlib/data_navigator.go | 39 ++++++++++++++++-- pkg/yqlib/lib.go | 4 +- yq.go | 81 +++++++++++++++++-------------------- 4 files changed, 75 insertions(+), 51 deletions(-) diff --git a/examples/sample.yaml b/examples/sample.yaml index b26830e8..a08d1d1f 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,7 +1,7 @@ a: Easy! as one two three b: c: 2 - d: [3, 4] + d: [3, 4, 5] e: - name: fred value: 3 diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 3a2d1845..234405b3 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -10,8 +10,9 @@ import ( type DataNavigator interface { DebugNode(node *yaml.Node) - Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error) - Update(rootNode *yaml.Node, remainingPath []string, changesToApply yaml.Node) error + Get(rootNode *yaml.Node, path []string) (*yaml.Node, error) + Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error + Delete(rootNode *yaml.Node, path []string) error } type navigator struct { @@ -34,8 +35,8 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { return n.Visit(value, path, identityVisitor) } -func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.Node) error { - _, errorVisiting := n.Visit(value, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { +func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error { + _, errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { n.log.Debug("going to update") n.DebugNode(nodeToUpdate) n.log.Debug("with") @@ -53,6 +54,36 @@ func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml. return errorVisiting } +func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { + + lastBit, newTail := path[len(path)-1], path[:len(path)-1] + n.log.Debug("splitting path, %v", lastBit) + n.log.Debug("new tail, %v", newTail) + _, errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { + n.log.Debug("need to find %v in here", lastBit) + n.DebugNode(nodeToUpdate) + + if nodeToUpdate.Kind == yaml.SequenceNode { + var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint + if err != nil { + return nil, err + } + if index >= int64(len(nodeToUpdate.Content)) { + n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content)) + return nodeToUpdate, nil + } + original := nodeToUpdate.Content + nodeToUpdate.Content = append(original[:index], original[index+1:]...) + + } else if nodeToUpdate.Kind == yaml.MappingNode { + + } + + return nodeToUpdate, nil + }) + return errorVisiting +} + func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) { realValue := value if realValue.Kind == yaml.DocumentNode { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 10d30eec..344aa248 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -50,8 +50,8 @@ func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { var paths = l.parser.ParsePath(updateCommand.Path) return l.navigator.Update(rootNode, paths, updateCommand.Value) case "delete": - l.log.Debugf("need to implement delete") - return nil + var paths = l.parser.ParsePath(updateCommand.Path) + return l.navigator.Delete(rootNode, paths) default: return fmt.Errorf("Unknown command %v", updateCommand.Command) } diff --git a/yq.go b/yq.go index 7fec3c1c..6801d03f 100644 --- a/yq.go +++ b/yq.go @@ -77,7 +77,7 @@ func newCommandCLI() *cobra.Command { createReadCmd(), createWriteCmd(), // createPrefixCmd(), - // createDeleteCmd(), + createDeleteCmd(), // createNewCmd(), // createMergeCmd(), ) @@ -166,27 +166,27 @@ a.b.e: // return cmdWrite // } -// func createDeleteCmd() *cobra.Command { -// var cmdDelete = &cobra.Command{ -// Use: "delete [yaml_file] [path]", -// Aliases: []string{"d"}, -// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", -// Example: ` -// yq delete things.yaml a.b.c -// yq delete --inplace things.yaml a.b.c -// yq delete --inplace -- things.yaml --key-starting-with-dash -// yq d -i things.yaml a.b.c -// yq d things.yaml a.b.c -// `, -// Long: `Deletes the given path from the YAML file. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -// `, -// RunE: deleteProperty, -// } -// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// return cmdDelete -// } +func createDeleteCmd() *cobra.Command { + var cmdDelete = &cobra.Command{ + Use: "delete [yaml_file] [path]", + Aliases: []string{"d"}, + Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq delete things.yaml a.b.c +yq delete --inplace things.yaml a.b.c +yq delete --inplace -- things.yaml --key-starting-with-dash +yq d -i things.yaml a.b.c +yq d things.yaml a.b.c + `, + Long: `Deletes the given path from the YAML file. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: deleteProperty, + } + cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdDelete +} // func createNewCmd() *cobra.Command { // var cmdNew = &cobra.Command{ @@ -407,6 +407,20 @@ func writeProperty(cmd *cobra.Command, args []string) error { if updateCommandsError != nil { return updateCommandsError } + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} + +func deleteProperty(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("Must provide ") + } + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) + updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} + + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} + +func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() if errorParsingDocIndex != nil { return errorParsingDocIndex @@ -424,7 +438,7 @@ func writeProperty(cmd *cobra.Command, args []string) error { } return nil } - return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) + return readAndUpdate(writer, inputFile, updateData) } // func prefixProperty(cmd *cobra.Command, args []string) error { @@ -484,27 +498,6 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) return readStream(inputFile, mapYamlDecoder(updateData, encoder)) } -// func deleteProperty(cmd *cobra.Command, args []string) error { -// if len(args) < 2 { -// return errors.New("Must provide ") -// } -// var deletePath = args[1] -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { -// if updateAll || currentIndex == docIndexInt { -// log.Debugf("Deleting path in doc %v", currentIndex) -// return lib.DeletePath(dataBucket, deletePath) -// } -// return dataBucket, nil -// } - -// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -// } - // func mergeProperties(cmd *cobra.Command, args []string) error { // if len(args) < 2 { // return errors.New("Must provide at least 2 yaml files") From b7640946ac53cb217e6f3857fe6131da87c8f47f Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Dec 2019 17:31:26 +1100 Subject: [PATCH 11/68] Delete! --- pkg/yqlib/data_navigator.go | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 234405b3..73ae53b2 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -62,7 +62,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { _, errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { n.log.Debug("need to find %v in here", lastBit) n.DebugNode(nodeToUpdate) - + original := nodeToUpdate.Content if nodeToUpdate.Kind == yaml.SequenceNode { var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint if err != nil { @@ -72,11 +72,15 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content)) return nodeToUpdate, nil } - original := nodeToUpdate.Content nodeToUpdate.Content = append(original[:index], original[index+1:]...) } else if nodeToUpdate.Kind == yaml.MappingNode { - + //need to delete both the key and value children from Content + indexInMap := n.findIndexForKeyInMap(nodeToUpdate.Content, lastBit) + if indexInMap != -1 { + //skip two because its a key, value pair + nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...) + } } return nodeToUpdate, nil @@ -174,15 +178,14 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) } func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { - for index, content := range value.Content { - // value.Content is a concatenated array of key, value, - // so keys are in the even indexes, values in odd. - if index%2 == 1 || (content.Value != head) { - continue - } - value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind)) - return n.Visit(value.Content[index+1], tail, visitor) + indexInMap := n.findIndexForKeyInMap(value.Content, head) + + if indexInMap != -1 { + value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.guessKind(tail, value.Content[indexInMap+1].Kind)) + return n.Visit(value.Content[indexInMap+1], tail, visitor) } + + //didn't find it, lets add it. value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)} value.Content = append(value.Content, &mapEntryValue) @@ -190,6 +193,18 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis return n.Visit(&mapEntryValue, tail, visitor) } +func (n *navigator) findIndexForKeyInMap(contents []*yaml.Node, key string) int { + for index, content := range contents { + // value.Content is a concatenated array of key, value, + // so keys are in the even indexes, values in odd. + if index%2 == 1 || (content.Value != key) { + continue + } + return index + } + return -1 +} + func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} newNode.Content = make([]*yaml.Node, len(value.Content)) From 5988d0cffacc876b4a54ac482fba8a8e1293a8f5 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Dec 2019 18:24:23 +1100 Subject: [PATCH 12/68] Simplified --- commands_test.go | 49 ++++++++++--- examples/array.yaml | 2 + examples/data2.yaml | 1 + pkg/yqlib/data_navigator.go | 140 +++++++++++++++++++++--------------- yq.go | 9 ++- 5 files changed, 132 insertions(+), 69 deletions(-) diff --git a/commands_test.go b/commands_test.go index 49f12578..ed3fd562 100644 --- a/commands_test.go +++ b/commands_test.go @@ -172,6 +172,8 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { - lala - land serial: 1 +- become: false + gather_facts: true ` test.AssertResult(t, expectedOutput, result.Output) } @@ -194,9 +196,9 @@ serial: 1 test.AssertResult(t, expectedOutput, result.Output) } -func TestReadCmd_ArrayYaml_Splat(t *testing.T) { +func TestReadCmd_ArrayYaml_SplatA(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -v examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -208,6 +210,8 @@ func TestReadCmd_ArrayYaml_Splat(t *testing.T) { - lala - land serial: 1 +- become: false + gather_facts: true ` test.AssertResult(t, expectedOutput, result.Output) } @@ -218,18 +222,17 @@ func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := "- false\n" + expectedOutput := `- false +- true +` test.AssertResult(t, expectedOutput, result.Output) } func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, result.Error.Error()) + result := test.RunCmd(cmd, "read -v examples/array.yaml [x].gather_facts") + expectedOutput := `` + test.AssertResult(t, expectedOutput, result.Output) } // func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { @@ -326,6 +329,34 @@ func TestReadCmd_Verbose(t *testing.T) { // test.AssertResult(t, "2\n", result.Output) // } +func TestReadSplatPrefixYaml(t *testing.T) { + content := `a: 2 +b: + hi: + c: things + d: something else + there: + c: more things + d: more something else + there2: + c: more things also + d: more something else also +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("read -v %s b.there*.c", filename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `- more things +- more things also +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestPrefixCmd(t *testing.T) { content := `b: c: 3 diff --git a/examples/array.yaml b/examples/array.yaml index 67c34c04..47f04423 100644 --- a/examples/array.yaml +++ b/examples/array.yaml @@ -7,3 +7,5 @@ - lala - land serial: 1 +- become: false + gather_facts: true diff --git a/examples/data2.yaml b/examples/data2.yaml index 9c51b1f3..7969f1ec 100644 --- a/examples/data2.yaml +++ b/examples/data2.yaml @@ -2,3 +2,4 @@ a: other b: [3, 4] c: test: 1 + tell: 1 diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 73ae53b2..438dfd9d 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -3,6 +3,7 @@ package yqlib import ( "bytes" "strconv" + "strings" logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" @@ -19,11 +20,7 @@ type navigator struct { log *logging.Logger } -type VisitorFn func(*yaml.Node) (*yaml.Node, error) - -func identityVisitor(value *yaml.Node) (*yaml.Node, error) { - return value, nil -} +type VisitorFn func(*yaml.Node) error func NewDataNavigator(l *logging.Logger) DataNavigator { return &navigator{ @@ -32,11 +29,28 @@ func NewDataNavigator(l *logging.Logger) DataNavigator { } func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { - return n.Visit(value, path, identityVisitor) + matchingNodes := make([]*yaml.Node, 0) + + n.Visit(value, path, func(matchedNode *yaml.Node) error { + matchingNodes = append(matchingNodes, matchedNode) + n.log.Debug("Matched") + n.DebugNode(matchedNode) + return nil + }) + n.log.Debug("finished iterating, found %v matches", len(matchingNodes)) + if len(matchingNodes) == 0 { + return nil, nil + } else if len(matchingNodes) == 1 { + return matchingNodes[0], nil + } + // make a new node + var newNode = yaml.Node{Kind: yaml.SequenceNode} + newNode.Content = matchingNodes + return &newNode, nil } func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error { - _, errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { + errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error { n.log.Debug("going to update") n.DebugNode(nodeToUpdate) n.log.Debug("with") @@ -49,7 +63,7 @@ func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply ya nodeToUpdate.HeadComment = changesToApply.HeadComment nodeToUpdate.LineComment = changesToApply.LineComment nodeToUpdate.FootComment = changesToApply.FootComment - return nodeToUpdate, nil + return nil }) return errorVisiting } @@ -59,36 +73,39 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { lastBit, newTail := path[len(path)-1], path[:len(path)-1] n.log.Debug("splitting path, %v", lastBit) n.log.Debug("new tail, %v", newTail) - _, errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { + errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) error { n.log.Debug("need to find %v in here", lastBit) n.DebugNode(nodeToUpdate) original := nodeToUpdate.Content if nodeToUpdate.Kind == yaml.SequenceNode { var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint if err != nil { - return nil, err + return err } if index >= int64(len(nodeToUpdate.Content)) { - n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content)) - return nodeToUpdate, nil + n.log.Debug("index %v is greater than content length %v", index, len(nodeToUpdate.Content)) + return nil } nodeToUpdate.Content = append(original[:index], original[index+1:]...) } else if nodeToUpdate.Kind == yaml.MappingNode { - //need to delete both the key and value children from Content - indexInMap := n.findIndexForKeyInMap(nodeToUpdate.Content, lastBit) - if indexInMap != -1 { - //skip two because its a key, value pair + + _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error { nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...) + return nil + }) + if errorVisiting != nil { + return errorVisiting } + } - return nodeToUpdate, nil + return nil }) return errorVisiting } -func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) { +func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) error { realValue := value if realValue.Kind == yaml.DocumentNode { n.log.Debugf("its a document! returning the first child") @@ -130,7 +147,9 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y } func (n *navigator) DebugNode(value *yaml.Node) { - if n.log.IsEnabledFor(logging.DEBUG) { + if value == nil { + n.log.Debug("-- node is nil --") + } else if n.log.IsEnabledFor(logging.DEBUG) { buf := new(bytes.Buffer) encoder := yaml.NewEncoder(buf) encoder.Encode(value) @@ -140,7 +159,7 @@ func (n *navigator) DebugNode(value *yaml.Node) { } } -func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { +func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { switch value.Kind { case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) @@ -157,32 +176,36 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito } return n.recurseArray(value, head, tail, visitor) default: - return nil, nil + return nil } } -func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { - var newNode = yaml.Node{Kind: yaml.SequenceNode} +func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) error { for index, content := range value.Content { if index%2 == 0 { continue } content = n.getOrReplace(content, n.guessKind(tail, content.Kind)) - var nestedValue, err = n.Visit(content, tail, visitor) + var err = n.Visit(content, tail, visitor) if err != nil { - return nil, err + return err } - newNode.Content = append(newNode.Content, nestedValue) } - return &newNode, nil + return nil } -func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { - indexInMap := n.findIndexForKeyInMap(value.Content, head) - - if indexInMap != -1 { +func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { + visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error { value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.guessKind(tail, value.Content[indexInMap+1].Kind)) return n.Visit(value.Content[indexInMap+1], tail, visitor) + }) + + if errorVisiting != nil { + return errorVisiting + } + + if visited { + return nil } //didn't find it, lets add it. @@ -193,65 +216,64 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis return n.Visit(&mapEntryValue, tail, visitor) } -func (n *navigator) findIndexForKeyInMap(contents []*yaml.Node, key string) int { +type mapVisitorFn func(int) error + +func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { + visited := false for index, content := range contents { // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. - if index%2 == 1 || (content.Value != key) { - continue + if index%2 == 0 && (n.matchesKey(key, content.Value)) { + errorVisiting := visit(index) + if errorVisiting != nil { + return visited, errorVisiting + } + visited = true } - return index } - return -1 + return visited, nil } -func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { - var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} - newNode.Content = make([]*yaml.Node, len(value.Content)) +func (n *navigator) matchesKey(key string, actual string) bool { + var prefixMatch = strings.TrimSuffix(key, "*") + if prefixMatch != key { + return strings.HasPrefix(actual, prefixMatch) + } + return actual == key +} - for index, childValue := range value.Content { +func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) error { + for _, childValue := range value.Content { n.log.Debug("processing") n.DebugNode(childValue) childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind)) - var nestedValue, err = n.Visit(childValue, tail, visitor) - n.log.Debug("nestedValue") - n.DebugNode(nestedValue) + var err = n.Visit(childValue, tail, visitor) if err != nil { - return nil, err + return err } - newNode.Content[index] = nestedValue } - return &newNode, nil + return nil } -func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { +func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) error { var newNode = yaml.Node{Kind: n.guessKind(tail, 0)} value.Content = append(value.Content, &newNode) n.log.Debug("appending a new node, %v", value.Content) return n.Visit(&newNode, tail, visitor) } -func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { +func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { - return nil, err + return err } if index >= int64(len(value.Content)) { - return nil, nil + return nil } value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind)) return n.Visit(value.Content[index], tail, visitor) } -// func matchesKey(key string, actual interface{}) bool { -// var actualString = fmt.Sprintf("%v", actual) -// var prefixMatch = strings.TrimSuffix(key, "*") -// if prefixMatch != key { -// return strings.HasPrefix(actualString, prefixMatch) -// } -// return actualString == key -// } - // func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { // var matches = make([]*yaml.MapItem, 0) // for idx := range context { diff --git a/yq.go b/yq.go index 6801d03f..fc3cb256 100644 --- a/yq.go +++ b/yq.go @@ -277,10 +277,14 @@ func readProperty(cmd *cobra.Command, args []string) error { log.Debugf("reading %v in document %v", path, currentIndex) mappedDoc, errorParsing := lib.Get(&dataBucket, path) lib.DebugNode(mappedDoc) + log.Debugf("carry on") if errorParsing != nil { return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) + } else if mappedDoc != nil { + + mappedDocs = append(mappedDocs, mappedDoc) } - mappedDocs = append(mappedDocs, mappedDoc) + } currentIndex = currentIndex + 1 } @@ -288,6 +292,9 @@ func readProperty(cmd *cobra.Command, args []string) error { if errorReadingStream != nil { return errorReadingStream + } else if len(mappedDocs) == 0 { + log.Debug("no matching results, nothing to print") + return nil } var encoder = yaml.NewEncoder(cmd.OutOrStdout()) From 53a4a47ce306edead2dc9f059c488ec02f9d963d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Dec 2019 18:38:40 +1100 Subject: [PATCH 13/68] wip - prefix splat --- commands_test.go | 89 +++++++++++++++++-------------------- examples/data2.yaml | 2 + pkg/yqlib/data_navigator.go | 10 +++-- 3 files changed, 48 insertions(+), 53 deletions(-) diff --git a/commands_test.go b/commands_test.go index ed3fd562..8e09b81d 100644 --- a/commands_test.go +++ b/commands_test.go @@ -235,15 +235,14 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } -// func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } +func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") + expectedOutput := `- +- +` + test.AssertResult(t, expectedOutput, result.Output) +} func TestReadCmd_Error(t *testing.T) { cmd := getRootCommand() @@ -280,27 +279,26 @@ func TestReadCmd_ErrorUnreadableFile(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -// func TestReadCmd_ErrorBadPath(t *testing.T) { -// content := `b: -// d: -// e: -// - 3 -// - 4 -// f: -// - 1 -// - 2 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) +func TestReadCmd_ErrorBadPath(t *testing.T) { + content := `b: + d: + e: + - 3 + - 4 + f: + - 1 + - 2 +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) -// if result.Error == nil { -// t.Fatal("Expected command to fail due to invalid path") -// } -// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) + expectedOutput := `- +- +` + test.AssertResult(t, expectedOutput, result.Output) +} func TestReadCmd_Verbose(t *testing.T) { cmd := getRootCommand() @@ -878,35 +876,28 @@ b: } func TestDeleteSplatYaml(t *testing.T) { - content := `a: 2 -b: - hi: - c: things - d: something else - hello: - c: things2 - d: something else2 - there: - c: more things - d: more something else + content := `a: other +b: [3, 4] +c: + toast: leave + test: 1 + tell: 1 + taco: cool ` filename := test.WriteTempYamlFile(content) defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.*.c", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s c.te*", filename)) if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: 2 -b: - hi: - d: something else - hello: - d: something else2 - there: - d: more something else + expectedOutput := `a: other +b: [3, 4] +c: + toast: leave + taco: cool ` test.AssertResult(t, expectedOutput, result.Output) } diff --git a/examples/data2.yaml b/examples/data2.yaml index 7969f1ec..93764e94 100644 --- a/examples/data2.yaml +++ b/examples/data2.yaml @@ -1,5 +1,7 @@ a: other b: [3, 4] c: + toast: leave test: 1 tell: 1 + taco: cool diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 438dfd9d..66ee359b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -220,10 +220,12 @@ type mapVisitorFn func(int) error func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { visited := false - for index, content := range contents { - // value.Content is a concatenated array of key, value, - // so keys are in the even indexes, values in odd. - if index%2 == 0 && (n.matchesKey(key, content.Value)) { + + // value.Content is a concatenated array of key, value, + // so keys are in the even indexes, values in odd. + for index := 0; index < len(contents); index = index + 2 { + content := contents[index] + if n.matchesKey(key, content.Value) { errorVisiting := visit(index) if errorVisiting != nil { return visited, errorVisiting From 8be006fba442855eb3b4d241ce34d013de266565 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Dec 2019 18:52:37 +1100 Subject: [PATCH 14/68] Fixed delete splat --- pkg/yqlib/data_navigator.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 66ee359b..17b2542b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -89,14 +89,23 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { nodeToUpdate.Content = append(original[:index], original[index+1:]...) } else if nodeToUpdate.Kind == yaml.MappingNode { - + // need to delete in reverse... + matchingIndices := make([]int, 0) _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error { - nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...) + matchingIndices = append(matchingIndices, indexInMap) + n.log.Debug("matchingIndices %v", indexInMap) return nil }) + n.log.Debug("delete matching indices now") + n.log.Debug("%v", matchingIndices) if errorVisiting != nil { return errorVisiting } + for i := len(matchingIndices) - 1; i >= 0; i-- { + indexToDelete := matchingIndices[i] + n.log.Debug("deleting index %v, %v", indexToDelete, nodeToUpdate.Content[indexToDelete].Value) + nodeToUpdate.Content = append(nodeToUpdate.Content[:indexToDelete], nodeToUpdate.Content[indexToDelete+2:]...) + } } @@ -225,6 +234,7 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi // so keys are in the even indexes, values in odd. for index := 0; index < len(contents); index = index + 2 { content := contents[index] + n.log.Debug("index %v, checking %v", index, content.Value) if n.matchesKey(key, content.Value) { errorVisiting := visit(index) if errorVisiting != nil { From 2344638da45f2d16e271748b2657665d0bd51cde Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Dec 2019 18:53:49 +1100 Subject: [PATCH 15/68] Fixed delete splat --- pkg/yqlib/data_navigator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 17b2542b..6aa0ad0c 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -89,7 +89,8 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { nodeToUpdate.Content = append(original[:index], original[index+1:]...) } else if nodeToUpdate.Kind == yaml.MappingNode { - // need to delete in reverse... + // need to delete in reverse - otherwise the matching indexes + // become incorrect. matchingIndices := make([]int, 0) _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error { matchingIndices = append(matchingIndices, indexInMap) From b81fd638d76f1943ae382848fdb2a7773ed4df98 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Dec 2019 19:34:05 +1100 Subject: [PATCH 16/68] wip - new node --- pkg/yqlib/data_navigator.go | 16 ++++---- pkg/yqlib/lib.go | 11 ++++++ yq.go | 75 ++++++++++++++++++++++++++----------- 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 6aa0ad0c..66d4c710 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -14,6 +14,7 @@ type DataNavigator interface { Get(rootNode *yaml.Node, path []string) (*yaml.Node, error) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error Delete(rootNode *yaml.Node, path []string) error + GuessKind(tail []string, guess yaml.Kind) yaml.Kind } type navigator struct { @@ -68,6 +69,7 @@ func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply ya return errorVisiting } +// TODO: refactor delete.. func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { lastBit, newTail := path[len(path)-1], path[:len(path)-1] @@ -129,7 +131,7 @@ func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) er return visitor(realValue) } -func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind { +func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind { n.log.Debug("tail %v", tail) if len(tail) == 0 && guess == 0 { n.log.Debug("end of path, must be a scalar") @@ -195,7 +197,7 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) if index%2 == 0 { continue } - content = n.getOrReplace(content, n.guessKind(tail, content.Kind)) + content = n.getOrReplace(content, n.GuessKind(tail, content.Kind)) var err = n.Visit(content, tail, visitor) if err != nil { return err @@ -206,7 +208,7 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error { - value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.guessKind(tail, value.Content[indexInMap+1].Kind)) + value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.GuessKind(tail, value.Content[indexInMap+1].Kind)) return n.Visit(value.Content[indexInMap+1], tail, visitor) }) @@ -220,7 +222,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis //didn't find it, lets add it. value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) - mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)} + mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)} value.Content = append(value.Content, &mapEntryValue) n.log.Debug("adding new node %v", value.Content) return n.Visit(&mapEntryValue, tail, visitor) @@ -259,7 +261,7 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF for _, childValue := range value.Content { n.log.Debug("processing") n.DebugNode(childValue) - childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind)) + childValue = n.getOrReplace(childValue, n.GuessKind(tail, childValue.Kind)) var err = n.Visit(childValue, tail, visitor) if err != nil { return err @@ -269,7 +271,7 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF } func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) error { - var newNode = yaml.Node{Kind: n.guessKind(tail, 0)} + var newNode = yaml.Node{Kind: n.GuessKind(tail, 0)} value.Content = append(value.Content, &newNode) n.log.Debug("appending a new node, %v", value.Content) return n.Visit(&newNode, tail, visitor) @@ -283,7 +285,7 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v if index >= int64(len(value.Content)) { return nil } - value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind)) + value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind)) return n.Visit(value.Content[index], tail, visitor) } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 344aa248..9fb1591d 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,6 +17,7 @@ type YqLib interface { DebugNode(node *yaml.Node) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error + New(updateCommand UpdateCommand) (yaml.Node, error) } type lib struct { @@ -42,6 +43,16 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { return l.navigator.Get(rootNode, paths) } +func (l *lib) New(updateCommand UpdateCommand) (yaml.Node, error) { + var paths = l.parser.ParsePath(updateCommand.Path) + newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)} + errorUpdating := l.navigator.Update(&newNode, paths, updateCommand.Value) + if errorUpdating != nil { + return newNode, errorUpdating + } + return newNode, nil +} + func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { // later - support other command types l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) diff --git a/yq.go b/yq.go index fc3cb256..3e5cbe9f 100644 --- a/yq.go +++ b/yq.go @@ -78,7 +78,7 @@ func newCommandCLI() *cobra.Command { createWriteCmd(), // createPrefixCmd(), createDeleteCmd(), - // createNewCmd(), + createNewCmd(), // createMergeCmd(), ) rootCmd.SetOutput(os.Stdout) @@ -188,28 +188,28 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd return cmdDelete } -// func createNewCmd() *cobra.Command { -// var cmdNew = &cobra.Command{ -// Use: "new [path] [value]", -// Aliases: []string{"n"}, -// Short: "yq n [--script/-s script_file] a.b.c newValue", -// Example: ` -// yq new a.b.c cat -// yq n a.b.c cat -// yq n -- --key-starting-with-dash cat -// yq n --script create_script.yaml -// `, -// Long: `Creates a new yaml w.r.t the given path and value. -// Outputs to STDOUT +func createNewCmd() *cobra.Command { + var cmdNew = &cobra.Command{ + Use: "new [path] [value]", + Aliases: []string{"n"}, + Short: "yq n [--script/-s script_file] a.b.c newValue", + Example: ` +yq new a.b.c cat +yq n a.b.c cat +yq n -- --key-starting-with-dash cat +yq n --script create_script.yaml + `, + Long: `Creates a new yaml w.r.t the given path and value. +Outputs to STDOUT -// Create Scripts: -// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. -// `, -// RunE: newProperty, -// } -// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") -// return cmdNew -// } +Create Scripts: +Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. +`, + RunE: newProperty, + } + cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") + return cmdNew +} // func createMergeCmd() *cobra.Command { // var cmdMerge = &cobra.Command{ @@ -417,6 +417,34 @@ func writeProperty(cmd *cobra.Command, args []string) error { return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) } +func newProperty(cmd *cobra.Command, args []string) error { + var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") + if updateCommandsError != nil { + return updateCommandsError + } + firstCommand, restOfCommands := updateCommands[0], updateCommands[1:] + newNode, errorCreating := lib.New(firstCommand) + if errorCreating != nil { + return errorCreating + } + + for _, updateCommand := range restOfCommands { + + errorUpdating := lib.Update(&newNode, updateCommand) + + if errorUpdating != nil { + return errorUpdating + } + } + + var encoder = yaml.NewEncoder(cmd.OutOrStdout()) + encoder.SetIndent(2) + encoder.Encode(&newNode) + encoder.Close() + return nil + +} + func deleteProperty(cmd *cobra.Command, args []string) error { if len(args) < 2 { return errors.New("Must provide ") @@ -558,6 +586,9 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) return nil, errors.New(badArgsMessage) } else { updateCommands = make([]yqlib.UpdateCommand, 1) + log.Debug("args %v", args) + log.Debug("path %v", args[expectedArgs-2]) + log.Debug("Value %v", args[expectedArgs-1]) updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])} } return updateCommands, nil From a3cebec2fd68592e75b289b2b6ad460e6e0b2d1d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 16 Dec 2019 16:17:01 +1100 Subject: [PATCH 17/68] Added prefix command --- commands_test.go | 16 ++ examples/instruction_sample.yaml | 5 +- pkg/yqlib/data_navigator.go | 336 +------------------------------ pkg/yqlib/lib.go | 14 +- yq.go | 114 +++++++---- 5 files changed, 106 insertions(+), 379 deletions(-) diff --git a/commands_test.go b/commands_test.go index 8e09b81d..9002d816 100644 --- a/commands_test.go +++ b/commands_test.go @@ -605,6 +605,22 @@ func TestWriteCmd(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } +func TestWriteCmdScript(t *testing.T) { + content := `b: + c: 3 +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `IMPLEMENT ME` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestWriteMultiCmd(t *testing.T) { content := `b: c: 3 diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml index 9834c942..24445bca 100644 --- a/examples/instruction_sample.yaml +++ b/examples/instruction_sample.yaml @@ -6,5 +6,6 @@ - command: update path: b.e[+].name value: Mike Farah -- command: delete - path: a \ No newline at end of file +- command: update + path: d.a + value: Cow \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 66d4c710..1e2d03e0 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -12,7 +12,7 @@ import ( type DataNavigator interface { DebugNode(node *yaml.Node) Get(rootNode *yaml.Node, path []string) (*yaml.Node, error) - Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error + Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error Delete(rootNode *yaml.Node, path []string) error GuessKind(tail []string, guess yaml.Kind) yaml.Kind } @@ -50,12 +50,12 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { return &newNode, nil } -func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error { +func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error { errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error { n.log.Debug("going to update") n.DebugNode(nodeToUpdate) n.log.Debug("with") - n.DebugNode(&changesToApply) + n.DebugNode(changesToApply) nodeToUpdate.Value = changesToApply.Value nodeToUpdate.Tag = changesToApply.Tag nodeToUpdate.Kind = changesToApply.Kind @@ -288,333 +288,3 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind)) return n.Visit(value.Content[index], tail, visitor) } - -// func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { -// var matches = make([]*yaml.MapItem, 0) -// for idx := range context { -// var entry = &context[idx] -// if matchesKey(key, entry.Key) { -// matches = append(matches, entry) -// } -// } -// return matches -// } - -// func getMapSlice(context interface{}) yaml.MapSlice { -// var mapSlice yaml.MapSlice -// switch context := context.(type) { -// case yaml.MapSlice: -// mapSlice = context -// default: -// mapSlice = make(yaml.MapSlice, 0) -// } -// return mapSlice -// } - -// func getArray(context interface{}) (array []interface{}, ok bool) { -// switch context := context.(type) { -// case []interface{}: -// array = context -// ok = true -// default: -// array = make([]interface{}, 0) -// ok = false -// } -// return -// } - -// func writeMap(context interface{}, paths []string, value interface{}) interface{} { -// log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value) - -// mapSlice := getMapSlice(context) - -// if len(paths) == 0 { -// return context -// } - -// children := entriesInSlice(mapSlice, paths[0]) - -// if len(children) == 0 && paths[0] == "*" { -// log.Debugf("\tNo matches, return map as is") -// return context -// } - -// if len(children) == 0 { -// newChild := yaml.MapItem{Key: paths[0]} -// mapSlice = append(mapSlice, newChild) -// children = entriesInSlice(mapSlice, paths[0]) -// log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice) -// } - -// remainingPaths := paths[1:] -// for _, child := range children { -// child.Value = updatedChildValue(child.Value, remainingPaths, value) -// } -// log.Debugf("\tReturning mapSlice %v\n", mapSlice) -// return mapSlice -// } - -// func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { -// if len(remainingPaths) == 0 { -// return value -// } -// log.Debugf("updatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value) -// log.Debugf("type of child is %v", reflect.TypeOf(child)) - -// switch child := child.(type) { -// case nil: -// if remainingPaths[0] == "+" || remainingPaths[0] == "*" { -// return writeArray(child, remainingPaths, value) -// } -// case []interface{}: -// _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64) -// arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*" -// if arrayCommand { -// return writeArray(child, remainingPaths, value) -// } -// } -// return writeMap(child, remainingPaths, value) -// } - -// func writeArray(context interface{}, paths []string, value interface{}) []interface{} { -// log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value) -// array, _ := getArray(context) - -// if len(paths) == 0 { -// return array -// } - -// log.Debugf("\tarray %v\n", array) - -// rawIndex := paths[0] -// remainingPaths := paths[1:] -// var index int64 -// // the append array indicator -// if rawIndex == "+" { -// index = int64(len(array)) -// } else if rawIndex == "*" { -// for index, oldChild := range array { -// array[index] = updatedChildValue(oldChild, remainingPaths, value) -// } -// return array -// } else { -// index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint -// // writeArray is only called by updatedChildValue which handles parsing the -// // index, as such this renders this dead code. -// } - -// for index >= int64(len(array)) { -// array = append(array, nil) -// } -// currentChild := array[index] - -// log.Debugf("\tcurrentChild %v\n", currentChild) - -// array[index] = updatedChildValue(currentChild, remainingPaths, value) -// log.Debugf("\tReturning array %v\n", array) -// return array -// } - -// func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) { -// log.Debugf("readingMap %v with key %v\n", context, head) -// if head == "*" { -// return readMapSplat(context, tail) -// } - -// entries := entriesInSlice(context, head) -// if len(entries) == 1 { -// return calculateValue(entries[0].Value, tail) -// } else if len(entries) == 0 { -// return nil, nil -// } -// var errInIdx error -// values := make([]interface{}, len(entries)) -// for idx, entry := range entries { -// values[idx], errInIdx = calculateValue(entry.Value, tail) -// if errInIdx != nil { -// log.Errorf("Error updating index %v in %v", idx, context) -// return nil, errInIdx -// } - -// } -// return values, nil -// } - -// func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) { -// var newArray = make([]interface{}, len(context)) -// var i = 0 -// for _, entry := range context { -// if len(tail) > 0 { -// val, err := recurse(entry.Value, tail[0], tail[1:]) -// if err != nil { -// return nil, err -// } -// newArray[i] = val -// } else { -// newArray[i] = entry.Value -// } -// i++ -// } -// return newArray, nil -// } - -// func recurse(value interface{}, head string, tail []string) (interface{}, error) { -// switch value := value.(type) { -// case []interface{}: -// if head == "*" { -// return readArraySplat(value, tail) -// } -// index, err := strconv.ParseInt(head, 10, 64) -// if err != nil { -// return nil, fmt.Errorf("error accessing array: %v", err) -// } -// return readArray(value, index, tail) -// case yaml.MapSlice: -// return readMap(value, head, tail) -// default: -// return nil, nil -// } -// } - -// func readArray(array []interface{}, head int64, tail []string) (interface{}, error) { -// if head >= int64(len(array)) { -// return nil, nil -// } - -// value := array[head] -// return calculateValue(value, tail) -// } - -// func readArraySplat(array []interface{}, tail []string) (interface{}, error) { -// var newArray = make([]interface{}, len(array)) -// for index, value := range array { -// val, err := calculateValue(value, tail) -// if err != nil { -// return nil, err -// } -// newArray[index] = val -// } -// return newArray, nil -// } - -// func calculateValue(value interface{}, tail []string) (interface{}, error) { -// if len(tail) > 0 { -// return recurse(value, tail[0], tail[1:]) -// } -// return value, nil -// } - -// func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { -// log.Debugf("deleteMap for %v for %v\n", paths, context) - -// mapSlice := getMapSlice(context) - -// if len(paths) == 0 { -// return mapSlice, nil -// } - -// var index int -// var child yaml.MapItem -// for index, child = range mapSlice { -// if matchesKey(paths[0], child.Key) { -// log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) -// var badDelete error -// mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths) -// if badDelete != nil { -// return nil, badDelete -// } -// } -// } - -// return mapSlice, nil - -// } - -// func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) { -// remainingPaths := paths[1:] - -// var newSlice yaml.MapSlice -// if len(remainingPaths) > 0 { -// newChild := yaml.MapItem{Key: child.Key} -// var errorDeleting error -// newChild.Value, errorDeleting = deleteChildValue(child.Value, remainingPaths) -// if errorDeleting != nil { -// return nil, errorDeleting -// } - -// newSlice = make(yaml.MapSlice, len(original)) -// for i := range original { -// item := original[i] -// if i == index { -// item = newChild -// } -// newSlice[i] = item -// } -// } else { -// // Delete item from slice at index -// newSlice = append(original[:index], original[index+1:]...) -// log.Debugf("\tDeleted item index %d from original", index) -// } - -// log.Debugf("\tReturning original %v\n", original) -// return newSlice, nil -// } - -// func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { -// log.Debugf("deleteArraySplat for %v for %v\n", tail, array) -// var newArray = make([]interface{}, len(array)) -// for index, value := range array { -// val, err := deleteChildValue(value, tail) -// if err != nil { -// return nil, err -// } -// newArray[index] = val -// } -// return newArray, nil -// } - -// func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { -// log.Debugf("deleteArray for %v for %v\n", paths, array) - -// if index >= int64(len(array)) { -// return array, nil -// } - -// remainingPaths := paths[1:] -// if len(remainingPaths) > 0 { -// // Recurse into the array element at index -// var errorDeleting error -// array[index], errorDeleting = deleteMap(array[index], remainingPaths) -// if errorDeleting != nil { -// return nil, errorDeleting -// } - -// } else { -// // Delete the array element at index -// array = append(array[:index], array[index+1:]...) -// log.Debugf("\tDeleted item index %d from array, leaving %v", index, array) -// } - -// log.Debugf("\tReturning array: %v\n", array) -// return array, nil -// } - -// func deleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) { -// log.Debugf("deleteChildValue for %v for %v\n", remainingPaths, child) -// var head = remainingPaths[0] -// var tail = remainingPaths[1:] -// switch child := child.(type) { -// case yaml.MapSlice: -// return deleteMap(child, remainingPaths) -// case []interface{}: -// if head == "*" { -// return deleteArraySplat(child, tail) -// } -// index, err := strconv.ParseInt(head, 10, 64) -// if err != nil { -// return nil, fmt.Errorf("error accessing array: %v", err) -// } -// return deleteArray(child, remainingPaths, index) -// } -// return child, nil -// } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 9fb1591d..2415daea 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -10,14 +10,14 @@ import ( type UpdateCommand struct { Command string Path string - Value yaml.Node + Value *yaml.Node } type YqLib interface { DebugNode(node *yaml.Node) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error - New(updateCommand UpdateCommand) (yaml.Node, error) + New(path string) yaml.Node } type lib struct { @@ -43,14 +43,10 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { return l.navigator.Get(rootNode, paths) } -func (l *lib) New(updateCommand UpdateCommand) (yaml.Node, error) { - var paths = l.parser.ParsePath(updateCommand.Path) +func (l *lib) New(path string) yaml.Node { + var paths = l.parser.ParsePath(path) newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)} - errorUpdating := l.navigator.Update(&newNode, paths, updateCommand.Value) - if errorUpdating != nil { - return newNode, errorUpdating - } - return newNode, nil + return newNode } func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { diff --git a/yq.go b/yq.go index 3e5cbe9f..a9f14528 100644 --- a/yq.go +++ b/yq.go @@ -76,7 +76,7 @@ func newCommandCLI() *cobra.Command { rootCmd.AddCommand( createReadCmd(), createWriteCmd(), - // createPrefixCmd(), + createPrefixCmd(), createDeleteCmd(), createNewCmd(), // createMergeCmd(), @@ -143,28 +143,28 @@ a.b.e: return cmdWrite } -// func createPrefixCmd() *cobra.Command { -// var cmdWrite = &cobra.Command{ -// Use: "prefix [yaml_file] [path]", -// Aliases: []string{"p"}, -// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", -// Example: ` -// yq prefix things.yaml a.b.c -// yq prefix --inplace things.yaml a.b.c -// yq prefix --inplace -- things.yaml --key-starting-with-dash -// yq p -i things.yaml a.b.c -// yq p --doc 2 things.yaml a.b.d -// yq p -d2 things.yaml a.b.d -// `, -// Long: `Prefixes w.r.t to the yaml file at the given path. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -// `, -// RunE: prefixProperty, -// } -// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// return cmdWrite -// } +func createPrefixCmd() *cobra.Command { + var cmdPrefix = &cobra.Command{ + Use: "prefix [yaml_file] [path]", + Aliases: []string{"p"}, + Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq prefix things.yaml a.b.c +yq prefix --inplace things.yaml a.b.c +yq prefix --inplace -- things.yaml --key-starting-with-dash +yq p -i things.yaml a.b.c +yq p --doc 2 things.yaml a.b.d +yq p -d2 things.yaml a.b.d + `, + Long: `Prefixes w.r.t to the yaml file at the given path. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: prefixProperty, + } + cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdPrefix +} func createDeleteCmd() *cobra.Command { var cmdDelete = &cobra.Command{ @@ -418,17 +418,13 @@ func writeProperty(cmd *cobra.Command, args []string) error { } func newProperty(cmd *cobra.Command, args []string) error { - var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") + var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") if updateCommandsError != nil { return updateCommandsError } - firstCommand, restOfCommands := updateCommands[0], updateCommands[1:] - newNode, errorCreating := lib.New(firstCommand) - if errorCreating != nil { - return errorCreating - } + newNode := lib.New(updateCommands[0].Path) - for _, updateCommand := range restOfCommands { + for _, updateCommand := range updateCommands { errorUpdating := lib.Update(&newNode, updateCommand) @@ -442,6 +438,40 @@ func newProperty(cmd *cobra.Command, args []string) error { encoder.Encode(&newNode) encoder.Close() return nil +} + +func prefixProperty(cmd *cobra.Command, args []string) error { + + if len(args) < 2 { + return errors.New("Must provide ") + } + updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} + log.Debugf("args %v", args) + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + var updateData = func(dataBucket *yaml.Node, currentIndex int) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Prefixing document %v", currentIndex) + lib.DebugNode(dataBucket) + updateCommand.Value = dataBucket.Content[0] + dataBucket.Content = make([]*yaml.Node, 1) + + newNode := lib.New(updateCommand.Path) + dataBucket.Content[0] = &newNode + + errorUpdating := lib.Update(dataBucket, updateCommand) + if errorUpdating != nil { + return errorUpdating + } + + } + return nil + } + return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) } @@ -575,12 +605,26 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) // return readAndUpdate(cmd.OutOrStdout(), input, updateData) // } +type updateCommandParsed struct { + Command string + Path string + Value yaml.Node +} + func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) if writeScript != "" { - if err := readData(writeScript, 0, &updateCommands); err != nil { + var parsedCommands = make([]updateCommandParsed, 0) + if err := readData(writeScript, 0, &parsedCommands); err != nil { return nil, err } + log.Debugf("Read write commands file '%v'", parsedCommands) + for index := range parsedCommands { + parsedCommand := parsedCommands[index] + updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value} + updateCommands = append(updateCommands, updateCommand) + } + log.Debugf("Read write commands file '%v'", updateCommands) } else if len(args) < expectedArgs { return nil, errors.New(badArgsMessage) @@ -594,7 +638,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) return updateCommands, nil } -func parseValue(argument string) yaml.Node { +func parseValue(argument string) *yaml.Node { var err interface{} var tag = customTag @@ -618,11 +662,11 @@ func parseValue(argument string) yaml.Node { tag = "!!null" } if argument == "[]" { - return yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} + return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} } } log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag) - return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} + return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} } func safelyRenameFile(from string, to string) { From d7392f7b58e62b23677fc67b7d987e1a0d04fde6 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 16 Dec 2019 16:46:20 +1100 Subject: [PATCH 18/68] Refactoring --- commands_test.go | 18 ++++ go.mod | 6 +- go.sum | 7 ++ pkg/yqlib/value_parser.go | 46 ++++++--- yq.go | 190 ++++++++++++-------------------------- 5 files changed, 118 insertions(+), 149 deletions(-) diff --git a/commands_test.go b/commands_test.go index 9002d816..58d58e14 100644 --- a/commands_test.go +++ b/commands_test.go @@ -94,6 +94,24 @@ func TestReadCmd(t *testing.T) { test.AssertResult(t, "2\n", result.Output) } +// func TestReadRawCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read examples/sample.yaml b.c") +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, "21\n", result.Output) +// } + +// func TestReadRawMultiCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read examples/sample.yaml b.c") +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, "21\n", result.Output) +// } + func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") diff --git a/go.mod b/go.mod index efc8ffd2..61719058 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/mikefarah/yq/v3 require ( - github.com/mikefarah/yaml/v2 v2.4.0 + github.com/mikefarah/yaml/v2 v2.4.0 // indirect github.com/pkg/errors v0.8.1 github.com/spf13/cobra v0.0.5 - golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 // indirect - gopkg.in/imdario/mergo.v0 v0.3.7 + golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 // indirect + gopkg.in/imdario/mergo.v0 v0.3.7 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 ) diff --git a/go.sum b/go.sum index 307ebb49..2f045e24 100644 --- a/go.sum +++ b/go.sum @@ -35,14 +35,21 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 h1:s5lp4ug7qHzUccgyFdjsX7OZDzHXRaePrF3B3vmUiuM= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 h1:kJQZhwFzSwJS2BxboKjdZzWczQOZx8VuH7Y8hhuGUtM= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8= diff --git a/pkg/yqlib/value_parser.go b/pkg/yqlib/value_parser.go index d08da020..7a51d7c9 100644 --- a/pkg/yqlib/value_parser.go +++ b/pkg/yqlib/value_parser.go @@ -2,34 +2,50 @@ package yqlib import ( "strconv" + + logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) type ValueParser interface { - ParseValue(argument string) interface{} + Parse(argument string, customTag string) *yaml.Node } -type valueParser struct{} - -func NewValueParser() ValueParser { - return &valueParser{} +type valueParser struct { + log *logging.Logger } -func (v *valueParser) ParseValue(argument string) interface{} { - var value, err interface{} +func NewValueParser(l *logging.Logger) ValueParser { + return &valueParser{log: l} +} + +func (v *valueParser) Parse(argument string, customTag string) *yaml.Node { + var err interface{} + var tag = customTag + var inQuotes = len(argument) > 0 && argument[0] == '"' - if !inQuotes { - value, err = strconv.ParseFloat(argument, 64) + if tag == "" && !inQuotes { + + _, err = strconv.ParseBool(argument) if err == nil { - return value + tag = "!!bool" } - value, err = strconv.ParseBool(argument) + _, err = strconv.ParseFloat(argument, 64) if err == nil { - return value + tag = "!!float" + } + _, err = strconv.ParseInt(argument, 10, 64) + if err == nil { + tag = "!!int" + } + + if argument == "null" { + tag = "!!null" } if argument == "[]" { - return make([]interface{}, 0) + return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} } - return argument } - return argument[1 : len(argument)-1] + v.log.Debugf("parsed value '%v', tag: '%v'", argument, tag) + return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} } diff --git a/yq.go b/yq.go index a9f14528..d4998e69 100644 --- a/yq.go +++ b/yq.go @@ -29,7 +29,7 @@ var version = false var docIndex = "0" var log = logging.MustGetLogger("yq") var lib = yqlib.NewYqLib(log) -var valueParser = yqlib.NewValueParser() +var valueParser = yqlib.NewValueParser(log) func main() { cmd := newCommandCLI() @@ -263,28 +263,14 @@ func readProperty(cmd *cobra.Command, args []string) error { for { var dataBucket yaml.Node errorReading := decoder.Decode(&dataBucket) - log.Debugf("decoded node for doc %v", currentIndex) - lib.DebugNode(&dataBucket) + if errorReading == io.EOF { - log.Debugf("done %v / %v", currentIndex, docIndexInt) - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) - } - return nil + return handleEOF(updateAll, docIndexInt, currentIndex) } - log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - if updateAll || currentIndex == docIndexInt { - log.Debugf("reading %v in document %v", path, currentIndex) - mappedDoc, errorParsing := lib.Get(&dataBucket, path) - lib.DebugNode(mappedDoc) - log.Debugf("carry on") - if errorParsing != nil { - return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } else if mappedDoc != nil { - - mappedDocs = append(mappedDocs, mappedDoc) - } - + var errorParsing error + mappedDocs, errorParsing = appendDocument(mappedDocs, dataBucket, path, updateAll, docIndexInt, currentIndex) + if errorParsing != nil { + return errorParsing } currentIndex = currentIndex + 1 } @@ -292,7 +278,38 @@ func readProperty(cmd *cobra.Command, args []string) error { if errorReadingStream != nil { return errorReadingStream - } else if len(mappedDocs) == 0 { + } + + return printResults(mappedDocs, cmd) +} + +func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { + log.Debugf("done %v / %v", currentIndex, docIndexInt) + if !updateAll && currentIndex <= docIndexInt { + return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) + } + return nil +} + +func appendDocument(mappedDocs []*yaml.Node, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yaml.Node, error) { + log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) + lib.DebugNode(&dataBucket) + if !updateAll && currentIndex != docIndexInt { + return mappedDocs, nil + } + log.Debugf("reading %v in document %v", path, currentIndex) + mappedDoc, errorParsing := lib.Get(&dataBucket, path) + lib.DebugNode(mappedDoc) + if errorParsing != nil { + return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) + } else if mappedDoc != nil { + return append(mappedDocs, mappedDoc), nil + } + return mappedDocs, nil +} + +func printResults(mappedDocs []*yaml.Node, cmd *cobra.Command) error { + if len(mappedDocs) == 0 { log.Debug("no matching results, nothing to print") return nil } @@ -307,7 +324,7 @@ func readProperty(cmd *cobra.Command, args []string) error { cmd.Println(mappedDoc.Value) } } - } else if !updateAll { + } else if len(mappedDocs) == 1 { err = encoder.Encode(mappedDocs[0]) } else { err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs}) @@ -319,43 +336,6 @@ func readProperty(cmd *cobra.Command, args []string) error { return nil } -// func newProperty(cmd *cobra.Command, args []string) error { -// updatedData, err := newYaml(args) -// if err != nil { -// return err -// } -// dataStr, err := toString(updatedData) -// if err != nil { -// return err -// } -// cmd.Println(dataStr) -// return nil -// } - -// func newYaml(args []string) (interface{}, error) { -// var writeCommands, writeCommandsError = readUpdateCommands(args, 2, "Must provide ") -// if writeCommandsError != nil { -// return nil, writeCommandsError -// } - -// var dataBucket interface{} -// var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[") -// if isArray { -// dataBucket = make([]interface{}, 0) -// } else { -// dataBucket = make(yaml.MapSlice, 0) -// } - -// for _, entry := range writeCommands { -// path := entry.Key.(string) -// value := entry.Value -// log.Debugf("setting %v to %v", path, value) -// dataBucket = lib.WritePath(dataBucket, path, value) -// } - -// return dataBucket, nil -// } - func parseDocumentIndex() (bool, int, error) { if docIndex == "*" { return true, -1, nil @@ -454,25 +434,27 @@ func prefixProperty(cmd *cobra.Command, args []string) error { } var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Prefixing document %v", currentIndex) - lib.DebugNode(dataBucket) - updateCommand.Value = dataBucket.Content[0] - dataBucket.Content = make([]*yaml.Node, 1) - - newNode := lib.New(updateCommand.Path) - dataBucket.Content[0] = &newNode - - errorUpdating := lib.Update(dataBucket, updateCommand) - if errorUpdating != nil { - return errorUpdating - } - - } - return nil + return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) } return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +} +func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Prefixing document %v", currentIndex) + lib.DebugNode(dataBucket) + updateCommand.Value = dataBucket.Content[0] + dataBucket.Content = make([]*yaml.Node, 1) + + newNode := lib.New(updateCommand.Path) + dataBucket.Content[0] = &newNode + + errorUpdating := lib.Update(dataBucket, updateCommand) + if errorUpdating != nil { + return errorUpdating + } + } + return nil } func deleteProperty(cmd *cobra.Command, args []string) error { @@ -506,29 +488,6 @@ func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io return readAndUpdate(writer, inputFile, updateData) } -// func prefixProperty(cmd *cobra.Command, args []string) error { -// if len(args) != 2 { -// return errors.New("Must provide ") -// } -// prefixPath := args[1] - -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { - -// if updateAll || currentIndex == docIndexInt { -// log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) -// var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath) -// return mapDataBucket, nil -// } -// return dataBucket, nil -// } -// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -// } - func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { var destination io.Writer var destinationName string @@ -633,42 +592,11 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) log.Debug("args %v", args) log.Debug("path %v", args[expectedArgs-2]) log.Debug("Value %v", args[expectedArgs-1]) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])} + updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag)} } return updateCommands, nil } -func parseValue(argument string) *yaml.Node { - var err interface{} - var tag = customTag - - var inQuotes = len(argument) > 0 && argument[0] == '"' - if tag == "" && !inQuotes { - - _, err = strconv.ParseBool(argument) - if err == nil { - tag = "!!bool" - } - _, err = strconv.ParseFloat(argument, 64) - if err == nil { - tag = "!!float" - } - _, err = strconv.ParseInt(argument, 10, 64) - if err == nil { - tag = "!!int" - } - - if argument == "null" { - tag = "!!null" - } - if argument == "[]" { - return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} - } - } - log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag) - return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} -} - func safelyRenameFile(from string, to string) { if renameError := os.Rename(from, to); renameError != nil { log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) From 290579ac7fc21b9770dd35aa4634b23aa69aa183 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 16 Dec 2019 20:38:55 +1100 Subject: [PATCH 19/68] Handle simple aliases --- commands_test.go | 9 +++++++++ examples/simple-anchor.yaml | 4 ++++ pkg/yqlib/data_navigator.go | 22 ++++++++++++++++++---- pkg/yqlib/lib.go | 20 +++++++++++--------- 4 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 examples/simple-anchor.yaml diff --git a/commands_test.go b/commands_test.go index 58d58e14..fa497bfd 100644 --- a/commands_test.go +++ b/commands_test.go @@ -112,6 +112,15 @@ func TestReadCmd(t *testing.T) { // test.AssertResult(t, "21\n", result.Output) // } +func TestReadAnchorsCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "1\n", result.Output) +} + func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") diff --git a/examples/simple-anchor.yaml b/examples/simple-anchor.yaml new file mode 100644 index 00000000..c83b2dcf --- /dev/null +++ b/examples/simple-anchor.yaml @@ -0,0 +1,4 @@ +foo: &foo + a: 1 + +foobar: *foo \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 1e2d03e0..e1493eb3 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -18,14 +18,16 @@ type DataNavigator interface { } type navigator struct { - log *logging.Logger + log *logging.Logger + followAliases bool } type VisitorFn func(*yaml.Node) error -func NewDataNavigator(l *logging.Logger) DataNavigator { +func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator { return &navigator{ - log: l, + log: l, + followAliases: followAliases, } } @@ -147,6 +149,10 @@ func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } + if guess == yaml.AliasNode { + n.log.Debug("guess was an alias, okey doke.") + return guess + } return yaml.MappingNode } @@ -172,6 +178,7 @@ func (n *navigator) DebugNode(value *yaml.Node) { } func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { + n.log.Debug("recursing, processing %v", head) switch value.Kind { case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) @@ -187,6 +194,13 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito return n.appendArray(value, tail, visitor) } return n.recurseArray(value, head, tail, visitor) + case yaml.AliasNode: + n.log.Debug("its an alias, followAliases: %v", n.followAliases) + n.DebugNode(value.Alias) + if n.followAliases == true { + return n.recurse(value.Alias, head, tail, visitor) + } + return nil default: return nil } @@ -237,7 +251,7 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi // so keys are in the even indexes, values in odd. for index := 0; index < len(contents); index = index + 2 { content := contents[index] - n.log.Debug("index %v, checking %v", index, content.Value) + n.log.Debug("index %v, checking %v", index, content.Value)) if n.matchesKey(key, content.Value) { errorVisiting := visit(index) if errorVisiting != nil { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 2415daea..eb3dc6db 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -28,37 +28,39 @@ type lib struct { func NewYqLib(l *logging.Logger) YqLib { return &lib{ - navigator: NewDataNavigator(l), - parser: NewPathParser(), - log: l, + parser: NewPathParser(), + log: l, } } func (l *lib) DebugNode(node *yaml.Node) { - l.navigator.DebugNode(node) + navigator := NewDataNavigator(l.log, false) + navigator.DebugNode(node) } func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { var paths = l.parser.ParsePath(path) - return l.navigator.Get(rootNode, paths) + navigator := NewDataNavigator(l.log, true) + return navigator.Get(rootNode, paths) } func (l *lib) New(path string) yaml.Node { var paths = l.parser.ParsePath(path) - newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)} + navigator := NewDataNavigator(l.log, false) + newNode := yaml.Node{Kind: navigator.GuessKind(paths, 0)} return newNode } func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { - // later - support other command types + navigator := NewDataNavigator(l.log, false) l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) switch updateCommand.Command { case "update": var paths = l.parser.ParsePath(updateCommand.Path) - return l.navigator.Update(rootNode, paths, updateCommand.Value) + return navigator.Update(rootNode, paths, updateCommand.Value) case "delete": var paths = l.parser.ParsePath(updateCommand.Path) - return l.navigator.Delete(rootNode, paths) + return navigator.Delete(rootNode, paths) default: return fmt.Errorf("Unknown command %v", updateCommand.Command) } From 19fe718cfb25256091c9c7fbf4e6627459a7a742 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 16 Dec 2019 21:09:23 +1100 Subject: [PATCH 20/68] Aliases! --- examples/merge-anchor.yml | 17 ++++++++++ pkg/yqlib/data_navigator.go | 39 +++++++++++++++++------ test.yml | 6 ++-- yq.go | 63 ++++++++++++++++++++----------------- 4 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 examples/merge-anchor.yml diff --git a/examples/merge-anchor.yml b/examples/merge-anchor.yml new file mode 100644 index 00000000..cbfcc0e6 --- /dev/null +++ b/examples/merge-anchor.yml @@ -0,0 +1,17 @@ +foo: &foo + a: original + thing: coolasdf + +bar: &bar + b: 2 + + +overrideA: + <<: [*foo,*bar] + a: vanilla + c: 3 + +foobar: + <<: *foo + thing: ice + c: 3 diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index e1493eb3..d8524749 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -96,7 +96,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { // need to delete in reverse - otherwise the matching indexes // become incorrect. matchingIndices := make([]int, 0) - _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error { + _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(matchingNode []*yaml.Node, indexInMap int) error { matchingIndices = append(matchingIndices, indexInMap) n.log.Debug("matchingIndices %v", indexInMap) return nil @@ -153,6 +153,9 @@ func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind { n.log.Debug("guess was an alias, okey doke.") return guess } + n.log.Debug("forcing a mapping node") + n.log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode) + n.log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode) return yaml.MappingNode } @@ -221,9 +224,9 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) } func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { - visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error { - value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.GuessKind(tail, value.Content[indexInMap+1].Kind)) - return n.Visit(value.Content[indexInMap+1], tail, visitor) + visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error { + contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind)) + return n.Visit(contents[indexInMap+1], tail, visitor) }) if errorVisiting != nil { @@ -242,18 +245,36 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis return n.Visit(&mapEntryValue, tail, visitor) } -type mapVisitorFn func(int) error +// need to pass the node in, as it may be aliased +type mapVisitorFn func([]*yaml.Node, int) error func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { visited := false - + n.log.Debug("visitMatchingEntries %v in %v", key, contents) // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. - for index := 0; index < len(contents); index = index + 2 { + // merge aliases are defined first, but we only want to traverse them + // if we dont find a match on this node first. + for index := len(contents) - 2; index >= 0; index = index - 2 { content := contents[index] - n.log.Debug("index %v, checking %v", index, content.Value)) + n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) + + // only visit aliases if we didn't find a match in this object. + if n.followAliases && !visited && contents[index+1].Kind == yaml.AliasNode { + valueNode := contents[index+1] + + n.log.Debug("need to visit the alias too") + n.DebugNode(valueNode) + visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) + if errorInAlias != nil { + return false, errorInAlias + } + visited = visited || visitedAlias + } + if n.matchesKey(key, content.Value) { - errorVisiting := visit(index) + n.log.Debug("found a match! %v", content.Value) + errorVisiting := visit(contents, index) if errorVisiting != nil { return visited, errorVisiting } diff --git a/test.yml b/test.yml index 71f1098c..c83b2dcf 100644 --- a/test.yml +++ b/test.yml @@ -1,2 +1,4 @@ -a: apple -c: cat +foo: &foo + a: 1 + +foobar: *foo \ No newline at end of file diff --git a/yq.go b/yq.go index d4998e69..14a862bc 100644 --- a/yq.go +++ b/yq.go @@ -79,7 +79,7 @@ func newCommandCLI() *cobra.Command { createPrefixCmd(), createDeleteCmd(), createNewCmd(), - // createMergeCmd(), + createMergeCmd(), ) rootCmd.SetOutput(os.Stdout) @@ -211,36 +211,36 @@ Note that you can give a create script to perform more sophisticated yaml. This return cmdNew } -// func createMergeCmd() *cobra.Command { -// var cmdMerge = &cobra.Command{ -// Use: "merge [initial_yaml_file] [additional_yaml_file]...", -// Aliases: []string{"m"}, -// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", -// Example: ` -// yq merge things.yaml other.yaml -// yq merge --inplace things.yaml other.yaml -// yq m -i things.yaml other.yaml -// yq m --overwrite things.yaml other.yaml -// yq m -i -x things.yaml other.yaml -// yq m -i -a things.yaml other.yaml -// `, -// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +func createMergeCmd() *cobra.Command { + var cmdMerge = &cobra.Command{ + Use: "merge [initial_yaml_file] [additional_yaml_file]...", + Aliases: []string{"m"}, + Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", + Example: ` +yq merge things.yaml other.yaml +yq merge --inplace things.yaml other.yaml +yq m -i things.yaml other.yaml +yq m --overwrite things.yaml other.yaml +yq m -i -x things.yaml other.yaml +yq m -i -a things.yaml other.yaml + `, + Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. -// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. +If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. +If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. -// Note that if you set both flags only overwrite will take effect. -// `, -// RunE: mergeProperties, -// } -// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") -// cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") -// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") -// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// return cmdMerge -// } +Note that if you set both flags only overwrite will take effect. +`, + RunE: mergeProperties, + } + cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + // cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") + // cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") + // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") + cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdMerge +} func readProperty(cmd *cobra.Command, args []string) error { var path = "" @@ -397,6 +397,11 @@ func writeProperty(cmd *cobra.Command, args []string) error { return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) } +func mergeProperties(cmd *cobra.Command, args []string) error { + // first generate update commands from the file + return nil +} + func newProperty(cmd *cobra.Command, args []string) error { var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") if updateCommandsError != nil { From 949bf1c1d7afb5ddc4d211ef726cd42bc180cfe9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Dec 2019 15:15:15 +1100 Subject: [PATCH 21/68] Merge anchors - wip --- commands_test.go | 45 +++++++++++ .../{merge-anchor.yml => merge-anchor.yaml} | 8 +- pkg/yqlib/data_navigator.go | 77 +++++++++++++++---- 3 files changed, 110 insertions(+), 20 deletions(-) rename examples/{merge-anchor.yml => merge-anchor.yaml} (69%) diff --git a/commands_test.go b/commands_test.go index fa497bfd..9f1349eb 100644 --- a/commands_test.go +++ b/commands_test.go @@ -121,6 +121,51 @@ func TestReadAnchorsCmd(t *testing.T) { test.AssertResult(t, "1\n", result.Output) } +func TestReadMergeAnchorsOriginalCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "original\n", result.Output) +} + +func TestReadMergeAnchorsOverrideCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.thing") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "ice\n", result.Output) +} + +func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "original\n", result.Output) +} + +func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.thing") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "coconut\n", result.Output) +} + +func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.c") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "newbar\n", result.Output) +} + func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") diff --git a/examples/merge-anchor.yml b/examples/merge-anchor.yaml similarity index 69% rename from examples/merge-anchor.yml rename to examples/merge-anchor.yaml index cbfcc0e6..2459c9a9 100644 --- a/examples/merge-anchor.yml +++ b/examples/merge-anchor.yaml @@ -4,12 +4,12 @@ foo: &foo bar: &bar b: 2 + thing: coconut + c: oldbar - -overrideA: +foobarList: <<: [*foo,*bar] - a: vanilla - c: 3 + c: newbar foobar: <<: *foo diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index d8524749..abbf0524 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -254,24 +254,11 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them - // if we dont find a match on this node first. - for index := len(contents) - 2; index >= 0; index = index - 2 { + // if we don't find a match on this node first. + for index := 0; index < len(contents); index = index + 2 { content := contents[index] n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) - // only visit aliases if we didn't find a match in this object. - if n.followAliases && !visited && contents[index+1].Kind == yaml.AliasNode { - valueNode := contents[index+1] - - n.log.Debug("need to visit the alias too") - n.DebugNode(valueNode) - visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) - if errorInAlias != nil { - return false, errorInAlias - } - visited = visited || visitedAlias - } - if n.matchesKey(key, content.Value) { n.log.Debug("found a match! %v", content.Value) errorVisiting := visit(contents, index) @@ -281,7 +268,65 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi visited = true } } - return visited, nil + + if visited == true || n.followAliases == false { + return visited, nil + } + + // didnt find a match, lets check the aliases. + // merge aliases are defined first, but we only want to traverse them + // if we don't find a match on this node first. + // traverse them backwards so that the last alias overrides the preceding. + + n.log.Debug("no entry in the map, checking for aliases") + + for index := len(contents) - 2; index >= 0; index = index - 2 { + // content := contents[index] + n.log.Debug("looking for %v", yaml.AliasNode) + + n.log.Debug("searching for aliases key %v kind %v", contents[index].Value, contents[index].Kind) + n.log.Debug("searching for aliases value %v kind %v", contents[index+1].Value, contents[index+1].Kind) + + // only visit aliases if we didn't find a match in this object. + + // NEED TO HANDLE A SEQUENCE OF ALIASES, and search each one. + // probably stop searching after we find a match, because overrides. + + if contents[index+1].Kind == yaml.AliasNode { + valueNode := contents[index+1] + + n.log.Debug("found an alias") + n.DebugNode(contents[index]) + n.DebugNode(valueNode) + visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) + if errorInAlias != nil { + return false, errorInAlias + } + if visitedAlias == true { + return true, nil + } + } else if contents[index+1].Kind == yaml.SequenceNode { + // could be an array of aliases...need to search this backwards too! + possibleAliasArray := contents[index+1].Content + for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { + child := possibleAliasArray[aliasIndex] + if child.Kind == yaml.AliasNode { + n.log.Debug("found an alias") + n.DebugNode(child) + visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit) + if errorInAlias != nil { + return false, errorInAlias + } + if visitedAlias == true { + return true, nil + } + } + } + } + + } + n.log.Debug("no aliases") + return false, nil } func (n *navigator) matchesKey(key string, actual string) bool { From 865a55645c6749b5ca4626b4673db82cd5648d7c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Dec 2019 15:33:54 +1100 Subject: [PATCH 22/68] Merge anchors - refactored --- pkg/yqlib/data_navigator.go | 88 ++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index abbf0524..edf341f4 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -248,13 +248,8 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis // need to pass the node in, as it may be aliased type mapVisitorFn func([]*yaml.Node, int) error -func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { visited := false - n.log.Debug("visitMatchingEntries %v in %v", key, contents) - // value.Content is a concatenated array of key, value, - // so keys are in the even indexes, values in odd. - // merge aliases are defined first, but we only want to traverse them - // if we don't find a match on this node first. for index := 0; index < len(contents); index = index + 2 { content := contents[index] n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) @@ -268,64 +263,69 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi visited = true } } + return visited, nil +} - if visited == true || n.followAliases == false { - return visited, nil +func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { + + n.log.Debug("visitMatchingEntries %v in %v", key, contents) + // value.Content is a concatenated array of key, value, + // so keys are in the even indexes, values in odd. + // merge aliases are defined first, but we only want to traverse them + // if we don't find a match directly on this node first. + visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(contents, key, visit) + if errorVisitedDirectEntries != nil || visited == true || n.followAliases == false { + return visited, errorVisitedDirectEntries } - // didnt find a match, lets check the aliases. + + return n.visitAliases(contents, key, visit) +} + +func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { // merge aliases are defined first, but we only want to traverse them // if we don't find a match on this node first. // traverse them backwards so that the last alias overrides the preceding. - - n.log.Debug("no entry in the map, checking for aliases") - + // a node can either be + // an alias to one other node (e.g. <<: *blah) + // or a sequence of aliases (e.g. <<: [*blah, *foo]) + n.log.Debug("checking for aliases") for index := len(contents) - 2; index >= 0; index = index - 2 { - // content := contents[index] - n.log.Debug("looking for %v", yaml.AliasNode) - - n.log.Debug("searching for aliases key %v kind %v", contents[index].Value, contents[index].Kind) - n.log.Debug("searching for aliases value %v kind %v", contents[index+1].Value, contents[index+1].Kind) - - // only visit aliases if we didn't find a match in this object. - - // NEED TO HANDLE A SEQUENCE OF ALIASES, and search each one. - // probably stop searching after we find a match, because overrides. if contents[index+1].Kind == yaml.AliasNode { valueNode := contents[index+1] - n.log.Debug("found an alias") n.DebugNode(contents[index]) n.DebugNode(valueNode) + visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) - if errorInAlias != nil { - return false, errorInAlias - } - if visitedAlias == true { - return true, nil + if visitedAlias == true || errorInAlias != nil { + return visitedAlias, errorInAlias } } else if contents[index+1].Kind == yaml.SequenceNode { // could be an array of aliases...need to search this backwards too! - possibleAliasArray := contents[index+1].Content - for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { - child := possibleAliasArray[aliasIndex] - if child.Kind == yaml.AliasNode { - n.log.Debug("found an alias") - n.DebugNode(child) - visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit) - if errorInAlias != nil { - return false, errorInAlias - } - if visitedAlias == true { - return true, nil - } - } + visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, key, visit) + if visitedAliasSeq == true || errorVisitingAliasSeq != nil { + return visitedAliasSeq, errorVisitingAliasSeq + } + } + } + n.log.Debug("nope no matching aliases found") + return false, nil +} + +func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { + for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { + child := possibleAliasArray[aliasIndex] + if child.Kind == yaml.AliasNode { + n.log.Debug("found an alias") + n.DebugNode(child) + visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit) + if visitedAlias == true || errorInAlias != nil { + return visitedAlias, errorInAlias } } - } - n.log.Debug("no aliases") return false, nil } From 784513dd1853e18880defc78e631ff507dde04da Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Dec 2019 15:35:16 +1100 Subject: [PATCH 23/68] Merge anchors - refactored --- pkg/yqlib/data_navigator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index edf341f4..b0bd6d0c 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -303,7 +303,7 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis return visitedAlias, errorInAlias } } else if contents[index+1].Kind == yaml.SequenceNode { - // could be an array of aliases...need to search this backwards too! + // could be an array of aliases... visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, key, visit) if visitedAliasSeq == true || errorVisitingAliasSeq != nil { return visitedAliasSeq, errorVisitingAliasSeq @@ -315,6 +315,7 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis } func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { + // need to search this backwards too, so that aliases defined last override the preceding. for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { child := possibleAliasArray[aliasIndex] if child.Kind == yaml.AliasNode { From 4fb44dbc47035ec0964c8259fc9643a03ecf17fb Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Dec 2019 17:13:11 +1100 Subject: [PATCH 24/68] Return path, smart print --- pkg/yqlib/data_navigator.go | 89 ++++++++++++++++++++----------------- pkg/yqlib/lib.go | 4 +- yq.go | 47 ++++++++------------ 3 files changed, 68 insertions(+), 72 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index b0bd6d0c..e3181e3b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -11,7 +11,7 @@ import ( type DataNavigator interface { DebugNode(node *yaml.Node) - Get(rootNode *yaml.Node, path []string) (*yaml.Node, error) + Get(rootNode *yaml.Node, path []string) ([]MatchingNode, error) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error Delete(rootNode *yaml.Node, path []string) error GuessKind(tail []string, guess yaml.Kind) yaml.Kind @@ -22,7 +22,7 @@ type navigator struct { followAliases bool } -type VisitorFn func(*yaml.Node) error +type VisitorFn func(matchingNode *yaml.Node, pathStack []interface{}) error func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator { return &navigator{ @@ -31,29 +31,28 @@ func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator { } } -func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { - matchingNodes := make([]*yaml.Node, 0) +type MatchingNode struct { + Node *yaml.Node + PathStack []interface{} +} - n.Visit(value, path, func(matchedNode *yaml.Node) error { - matchingNodes = append(matchingNodes, matchedNode) +func (n *navigator) Get(value *yaml.Node, path []string) ([]MatchingNode, error) { + matchingNodes := make([]MatchingNode, 0) + + n.Visit(value, path, func(matchedNode *yaml.Node, pathStack []interface{}) error { + matchingNodes = append(matchingNodes, MatchingNode{matchedNode, pathStack}) n.log.Debug("Matched") + for _, pathElement := range pathStack { + n.log.Debug("%v", pathElement) + } n.DebugNode(matchedNode) return nil }) - n.log.Debug("finished iterating, found %v matches", len(matchingNodes)) - if len(matchingNodes) == 0 { - return nil, nil - } else if len(matchingNodes) == 1 { - return matchingNodes[0], nil - } - // make a new node - var newNode = yaml.Node{Kind: yaml.SequenceNode} - newNode.Content = matchingNodes - return &newNode, nil + return matchingNodes, nil } func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error { - errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error { + errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error { n.log.Debug("going to update") n.DebugNode(nodeToUpdate) n.log.Debug("with") @@ -77,7 +76,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { lastBit, newTail := path[len(path)-1], path[:len(path)-1] n.log.Debug("splitting path, %v", lastBit) n.log.Debug("new tail, %v", newTail) - errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) error { + errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error { n.log.Debug("need to find %v in here", lastBit) n.DebugNode(nodeToUpdate) original := nodeToUpdate.Content @@ -121,16 +120,21 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) error { realValue := value + emptyArray := make([]interface{}, 0) if realValue.Kind == yaml.DocumentNode { n.log.Debugf("its a document! returning the first child") - realValue = value.Content[0] + return n.doVisit(value.Content[0], path, visitor, emptyArray) } + return n.doVisit(value, path, visitor, emptyArray) +} + +func (n *navigator) doVisit(value *yaml.Node, path []string, visitor VisitorFn, pathStack []interface{}) error { if len(path) > 0 { n.log.Debugf("diving into %v", path[0]) n.DebugNode(value) - return n.recurse(realValue, path[0], path[1:], visitor) + return n.recurse(value, path[0], path[1:], visitor, pathStack) } - return visitor(realValue) + return visitor(value, pathStack) } func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind { @@ -180,28 +184,28 @@ func (n *navigator) DebugNode(value *yaml.Node) { } } -func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { +func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { n.log.Debug("recursing, processing %v", head) switch value.Kind { case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) if head == "*" { - return n.splatMap(value, tail, visitor) + return n.splatMap(value, tail, visitor, pathStack) } - return n.recurseMap(value, head, tail, visitor) + return n.recurseMap(value, head, tail, visitor, pathStack) case yaml.SequenceNode: n.log.Debug("its a sequence of %v things!, %v", len(value.Content)) if head == "*" { - return n.splatArray(value, tail, visitor) + return n.splatArray(value, tail, visitor, pathStack) } else if head == "+" { - return n.appendArray(value, tail, visitor) + return n.appendArray(value, tail, visitor, pathStack) } - return n.recurseArray(value, head, tail, visitor) + return n.recurseArray(value, head, tail, visitor, pathStack) case yaml.AliasNode: n.log.Debug("its an alias, followAliases: %v", n.followAliases) n.DebugNode(value.Alias) if n.followAliases == true { - return n.recurse(value.Alias, head, tail, visitor) + return n.recurse(value.Alias, head, tail, visitor, pathStack) } return nil default: @@ -209,13 +213,13 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito } } -func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) error { +func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { for index, content := range value.Content { if index%2 == 0 { continue } content = n.getOrReplace(content, n.GuessKind(tail, content.Kind)) - var err = n.Visit(content, tail, visitor) + var err = n.doVisit(content, tail, visitor, append(pathStack, value.Content[index-1].Value)) if err != nil { return err } @@ -223,10 +227,10 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) return nil } -func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { +func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error { contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind)) - return n.Visit(contents[indexInMap+1], tail, visitor) + return n.doVisit(contents[indexInMap+1], tail, visitor, append(pathStack, contents[indexInMap].Value)) }) if errorVisiting != nil { @@ -238,15 +242,16 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis } //didn't find it, lets add it. - value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode}) + mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} + value.Content = append(value.Content, &mapEntryKey) mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)} value.Content = append(value.Content, &mapEntryValue) n.log.Debug("adding new node %v", value.Content) - return n.Visit(&mapEntryValue, tail, visitor) + return n.doVisit(&mapEntryValue, tail, visitor, append(pathStack, head)) } // need to pass the node in, as it may be aliased -type mapVisitorFn func([]*yaml.Node, int) error +type mapVisitorFn func(contents []*yaml.Node, index int) error func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { visited := false @@ -338,12 +343,12 @@ func (n *navigator) matchesKey(key string, actual string) bool { return actual == key } -func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) error { - for _, childValue := range value.Content { +func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { + for index, childValue := range value.Content { n.log.Debug("processing") n.DebugNode(childValue) childValue = n.getOrReplace(childValue, n.GuessKind(tail, childValue.Kind)) - var err = n.Visit(childValue, tail, visitor) + var err = n.doVisit(childValue, tail, visitor, append(pathStack, index)) if err != nil { return err } @@ -351,14 +356,14 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF return nil } -func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) error { +func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { var newNode = yaml.Node{Kind: n.GuessKind(tail, 0)} value.Content = append(value.Content, &newNode) n.log.Debug("appending a new node, %v", value.Content) - return n.Visit(&newNode, tail, visitor) + return n.doVisit(&newNode, tail, visitor, append(pathStack, len(value.Content)-1)) } -func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { +func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { return err @@ -367,5 +372,5 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v return nil } value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind)) - return n.Visit(value.Content[index], tail, visitor) + return n.doVisit(value.Content[index], tail, visitor, append(pathStack, index)) } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index eb3dc6db..637d8e1c 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -15,7 +15,7 @@ type UpdateCommand struct { type YqLib interface { DebugNode(node *yaml.Node) - Get(rootNode *yaml.Node, path string) (*yaml.Node, error) + Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error New(path string) yaml.Node } @@ -38,7 +38,7 @@ func (l *lib) DebugNode(node *yaml.Node) { navigator.DebugNode(node) } -func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) { +func (l *lib) Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) { var paths = l.parser.ParsePath(path) navigator := NewDataNavigator(l.log, true) return navigator.Get(rootNode, paths) diff --git a/yq.go b/yq.go index 14a862bc..becedccd 100644 --- a/yq.go +++ b/yq.go @@ -17,7 +17,6 @@ import ( yaml "gopkg.in/yaml.v3" ) -var rawOutput = false var customTag = "" var writeInplace = false var writeScript = "" @@ -103,7 +102,6 @@ yq r -- things.yaml --key-starting-with-dashes RunE: readProperty, } cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdRead.PersistentFlags().BoolVarP(&rawOutput, "raw", "r", false, "raw yaml output - prints out values instead of yaml") return cmdRead } @@ -256,7 +254,7 @@ func readProperty(cmd *cobra.Command, args []string) error { return errorParsingDocIndex } - var mappedDocs []*yaml.Node + var matchingNodes []yqlib.MatchingNode var currentIndex = 0 var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error { @@ -268,7 +266,7 @@ func readProperty(cmd *cobra.Command, args []string) error { return handleEOF(updateAll, docIndexInt, currentIndex) } var errorParsing error - mappedDocs, errorParsing = appendDocument(mappedDocs, dataBucket, path, updateAll, docIndexInt, currentIndex) + matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex) if errorParsing != nil { return errorParsing } @@ -280,7 +278,7 @@ func readProperty(cmd *cobra.Command, args []string) error { return errorReadingStream } - return printResults(mappedDocs, cmd) + return printResults(matchingNodes, cmd) } func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { @@ -291,48 +289,41 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { return nil } -func appendDocument(mappedDocs []*yaml.Node, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yaml.Node, error) { +func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]yqlib.MatchingNode, error) { log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) lib.DebugNode(&dataBucket) if !updateAll && currentIndex != docIndexInt { - return mappedDocs, nil + return originalMatchingNodes, nil } log.Debugf("reading %v in document %v", path, currentIndex) - mappedDoc, errorParsing := lib.Get(&dataBucket, path) - lib.DebugNode(mappedDoc) + matchingNodes, errorParsing := lib.Get(&dataBucket, path) if errorParsing != nil { return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } else if mappedDoc != nil { - return append(mappedDocs, mappedDoc), nil } - return mappedDocs, nil + return append(originalMatchingNodes, matchingNodes...), nil } -func printResults(mappedDocs []*yaml.Node, cmd *cobra.Command) error { - if len(mappedDocs) == 0 { +func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error { + if len(matchingNodes) == 0 { log.Debug("no matching results, nothing to print") return nil } - var encoder = yaml.NewEncoder(cmd.OutOrStdout()) - encoder.SetIndent(2) var err error - if rawOutput { - for _, mappedDoc := range mappedDocs { - if mappedDoc != nil { - cmd.Println(mappedDoc.Value) + for _, mappedDoc := range matchingNodes { + if mappedDoc.Node.Kind == yaml.ScalarNode { + cmd.Println(mappedDoc.Node.Value) + } else { + var encoder = yaml.NewEncoder(cmd.OutOrStdout()) + encoder.SetIndent(2) + if err = encoder.Encode(mappedDoc.Node); err != nil { + return err } + encoder.Close() } - } else if len(mappedDocs) == 1 { - err = encoder.Encode(mappedDocs[0]) - } else { - err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs}) } - if err != nil { - return err - } - encoder.Close() + return nil } From 3d3eaf3034259f4ec3019c7b39719ab2872118a1 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Dec 2019 17:16:03 +1100 Subject: [PATCH 25/68] Return path, smart print --- yq.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yq.go b/yq.go index becedccd..ed6a1e62 100644 --- a/yq.go +++ b/yq.go @@ -313,7 +313,7 @@ func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error for _, mappedDoc := range matchingNodes { if mappedDoc.Node.Kind == yaml.ScalarNode { - cmd.Println(mappedDoc.Node.Value) + cmd.Print(mappedDoc.Node.Value) } else { var encoder = yaml.NewEncoder(cmd.OutOrStdout()) encoder.SetIndent(2) From 5204a13685cebc9c28929cfdcc5437ec674eba55 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 23 Dec 2019 09:08:00 +1100 Subject: [PATCH 26/68] Show paths --- pkg/yqlib/data_navigator.go | 2 +- yq.go | 61 +++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index e3181e3b..9dfe75c8 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -241,7 +241,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis return nil } - //didn't find it, lets add it. + //TODO: have option to NOT do this... didn't find it, lets add it. mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} value.Content = append(value.Content, &mapEntryKey) mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)} diff --git a/yq.go b/yq.go index ed6a1e62..42a9987a 100644 --- a/yq.go +++ b/yq.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "strconv" + "strings" "github.com/mikefarah/yq/v3/pkg/yqlib" @@ -18,6 +19,7 @@ import ( ) var customTag = "" +var printMode = "v" var writeInplace = false var writeScript = "" var overwriteFlag = false @@ -102,6 +104,7 @@ yq r -- things.yaml --key-starting-with-dashes RunE: readProperty, } cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), k (keys), kv (key and value pairs)") return cmdRead } @@ -303,24 +306,62 @@ func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml. return append(originalMatchingNodes, matchingNodes...), nil } +func pathToString(pathStack []interface{}) string { + var sb strings.Builder + for index, path := range pathStack { + sb.WriteString(fmt.Sprintf("%v", path)) + if index < len(pathStack)-1 { + sb.WriteString(".") + } + } + return sb.String() +} + +func printValue(node *yaml.Node, cmd *cobra.Command) error { + if node.Kind == yaml.ScalarNode { + cmd.Print(node.Value) + return nil + } + var encoder = yaml.NewEncoder(cmd.OutOrStdout()) + encoder.SetIndent(2) + if err := encoder.Encode(node); err != nil { + return err + } + encoder.Close() + return nil +} + func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error { if len(matchingNodes) == 0 { log.Debug("no matching results, nothing to print") return nil } - var err error - - for _, mappedDoc := range matchingNodes { - if mappedDoc.Node.Kind == yaml.ScalarNode { - cmd.Print(mappedDoc.Node.Value) - } else { - var encoder = yaml.NewEncoder(cmd.OutOrStdout()) - encoder.SetIndent(2) - if err = encoder.Encode(mappedDoc.Node); err != nil { + for index, mappedDoc := range matchingNodes { + switch printMode { + case "k": + cmd.Print(pathToString(mappedDoc.PathStack)) + if index < len(matchingNodes)-1 { + cmd.Print("\n") + } + case "kv", "vk": + // put it into a node and print that. + var parentNode = yaml.Node{Kind: yaml.MappingNode} + parentNode.Content = make([]*yaml.Node, 2) + parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: pathToString(mappedDoc.PathStack)} + parentNode.Content[1] = mappedDoc.Node + if err := printValue(&parentNode, cmd); err != nil { return err } - encoder.Close() + default: + if err := printValue(mappedDoc.Node, cmd); err != nil { + return err + } + // Printing our Scalars does not print a new line at the end + // we only want to do that if there are more values (so users can easily script extraction of values in the yaml) + if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode { + cmd.Print("\n") + } } } From 1e541cd65f03f46a95e63ddf50470bb9f9b02937 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 23 Dec 2019 09:25:44 +1100 Subject: [PATCH 27/68] wip handle aliases when printing keys --- pkg/yqlib/data_navigator.go | 28 ++++++++++------------------ yq.go | 8 +++++++- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 9dfe75c8..740fbc0b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -189,9 +189,6 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito switch value.Kind { case yaml.MappingNode: n.log.Debug("its a map with %v entries", len(value.Content)/2) - if head == "*" { - return n.splatMap(value, tail, visitor, pathStack) - } return n.recurseMap(value, head, tail, visitor, pathStack) case yaml.SequenceNode: n.log.Debug("its a sequence of %v things!, %v", len(value.Content)) @@ -213,20 +210,6 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito } } -func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { - for index, content := range value.Content { - if index%2 == 0 { - continue - } - content = n.getOrReplace(content, n.GuessKind(tail, content.Kind)) - var err = n.doVisit(content, tail, visitor, append(pathStack, value.Content[index-1].Value)) - if err != nil { - return err - } - } - return nil -} - func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error { contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind)) @@ -279,7 +262,11 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi // merge aliases are defined first, but we only want to traverse them // if we don't find a match directly on this node first. visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(contents, key, visit) - if errorVisitedDirectEntries != nil || visited == true || n.followAliases == false { + + //TODO: crap we have to remember what we visited so we dont print the same key in the alias + // eff + + if errorVisitedDirectEntries != nil || n.followAliases == false { return visited, errorVisitedDirectEntries } // didnt find a match, lets check the aliases. @@ -336,6 +323,11 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key stri } func (n *navigator) matchesKey(key string, actual string) bool { + n.log.Debug("key: (%v), actual: (%v)", key, actual) + if n.followAliases == true && actual == "<<" { + // dont match alias keys, as we'll follow them instead + return false + } var prefixMatch = strings.TrimSuffix(key, "*") if prefixMatch != key { return strings.HasPrefix(actual, prefixMatch) diff --git a/yq.go b/yq.go index 42a9987a..99c96fed 100644 --- a/yq.go +++ b/yq.go @@ -309,7 +309,13 @@ func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml. func pathToString(pathStack []interface{}) string { var sb strings.Builder for index, path := range pathStack { - sb.WriteString(fmt.Sprintf("%v", path)) + switch path.(type) { + case int: + sb.WriteString(fmt.Sprintf("[%v]", path)) + default: + sb.WriteString(fmt.Sprintf("%v", path)) + } + if index < len(pathStack)-1 { sb.WriteString(".") } From 93dbe80a777def79715ea294411a402dc32306b5 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 24 Dec 2019 10:35:57 +1100 Subject: [PATCH 28/68] wip --- commands_test.go | 139 ++++++++++++++----------------- pkg/yqlib/data_navigator.go | 64 ++++++-------- pkg/yqlib/lib.go | 8 +- pkg/yqlib/navigation_settings.go | 78 +++++++++++++++++ test.yml | 15 +++- 5 files changed, 180 insertions(+), 124 deletions(-) create mode 100644 pkg/yqlib/navigation_settings.go diff --git a/commands_test.go b/commands_test.go index 9f1349eb..6421817c 100644 --- a/commands_test.go +++ b/commands_test.go @@ -91,34 +91,16 @@ func TestReadCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "2\n", result.Output) + test.AssertResult(t, "2", result.Output) } -// func TestReadRawCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "read examples/sample.yaml b.c") -// if result.Error != nil { -// t.Error(result.Error) -// } -// test.AssertResult(t, "21\n", result.Output) -// } - -// func TestReadRawMultiCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "read examples/sample.yaml b.c") -// if result.Error != nil { -// t.Error(result.Error) -// } -// test.AssertResult(t, "21\n", result.Output) -// } - func TestReadAnchorsCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "1\n", result.Output) + test.AssertResult(t, "1", result.Output) } func TestReadMergeAnchorsOriginalCmd(t *testing.T) { @@ -127,7 +109,7 @@ func TestReadMergeAnchorsOriginalCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "original\n", result.Output) + test.AssertResult(t, "original", result.Output) } func TestReadMergeAnchorsOverrideCmd(t *testing.T) { @@ -136,7 +118,7 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "ice\n", result.Output) + test.AssertResult(t, "ice", result.Output) } func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { @@ -145,7 +127,7 @@ func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "original\n", result.Output) + test.AssertResult(t, "original", result.Output) } func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { @@ -154,7 +136,7 @@ func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "coconut\n", result.Output) + test.AssertResult(t, "coconut", result.Output) } func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { @@ -163,7 +145,7 @@ func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "newbar\n", result.Output) + test.AssertResult(t, "newbar", result.Output) } func TestReadInvalidDocumentIndexCmd(t *testing.T) { @@ -205,7 +187,7 @@ func TestReadMultiCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "here\n", result.Output) + test.AssertResult(t, "here", result.Output) } func TestReadMultiAllCmd(t *testing.T) { @@ -215,10 +197,9 @@ func TestReadMultiAllCmd(t *testing.T) { t.Error(result.Error) } test.AssertResult(t, - `- first document -- second document -- third document -`, result.Output) + `first document +second document +third document`, result.Output) } func TestReadCmd_ArrayYaml(t *testing.T) { @@ -227,7 +208,7 @@ func TestReadCmd_ArrayYaml(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "false\n", result.Output) + test.AssertResult(t, "false", result.Output) } func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { @@ -270,20 +251,20 @@ serial: 1 func TestReadCmd_ArrayYaml_SplatA(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/array.yaml [*]") + result := test.RunCmd(cmd, "read examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } - expectedOutput := `- become: true - gather_facts: false - hosts: lalaland - name: "Apply smth" - roles: - - lala - - land - serial: 1 -- become: false - gather_facts: true + expectedOutput := `become: true +gather_facts: false +hosts: lalaland +name: "Apply smth" +roles: +- lala +- land +serial: 1 +become: false +gather_facts: true ` test.AssertResult(t, expectedOutput, result.Output) } @@ -294,15 +275,14 @@ func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `- false -- true -` + expectedOutput := `false +true` test.AssertResult(t, expectedOutput, result.Output) } func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/array.yaml [x].gather_facts") + result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") expectedOutput := `` test.AssertResult(t, expectedOutput, result.Output) } @@ -310,9 +290,7 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") - expectedOutput := `- -- -` + expectedOutput := `` test.AssertResult(t, expectedOutput, result.Output) } @@ -366,19 +344,17 @@ func TestReadCmd_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) - expectedOutput := `- -- -` + expectedOutput := `` test.AssertResult(t, expectedOutput, result.Output) } func TestReadCmd_Verbose(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "-v read examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "2\n", result.Output) + test.AssertResult(t, "2", result.Output) } // func TestReadCmd_ToJson(t *testing.T) { @@ -416,14 +392,13 @@ b: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -v %s b.there*.c", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read %s b.there*.c", filename)) if result.Error != nil { t.Error(result.Error) } - expectedOutput := `- more things -- more things also -` + expectedOutput := `more things +more things also` test.AssertResult(t, expectedOutput, result.Output) } @@ -595,7 +570,7 @@ func TestPrefixCmd_Verbose(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("-v prefix %s x", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("prefix %s x", filename)) if result.Error != nil { t.Error(result.Error) } @@ -684,12 +659,20 @@ func TestWriteCmdScript(t *testing.T) { filename := test.WriteTempYamlFile(content) defer test.RemoveTempYamlFile(filename) + updateScript := `- command: update + path: b.c + value: 7` + scriptFilename := test.WriteTempYamlFile(updateScript) + defer test.RemoveTempYamlFile(scriptFilename) + cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) if result.Error != nil { t.Error(result.Error) } - expectedOutput := `IMPLEMENT ME` + expectedOutput := `b: + c: 7 +` test.AssertResult(t, expectedOutput, result.Output) } @@ -870,7 +853,7 @@ func TestWriteCmd_AppendEmptyArray(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[+] v", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename)) if result.Error != nil { t.Error(result.Error) } @@ -890,7 +873,7 @@ func TestWriteCmd_SplatArray(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[*].c new", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename)) if result.Error != nil { t.Error(result.Error) } @@ -910,7 +893,7 @@ func TestWriteCmd_SplatMap(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.* new", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename)) if result.Error != nil { t.Error(result.Error) } @@ -930,7 +913,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.c.* new", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename)) if result.Error != nil { t.Error(result.Error) } @@ -976,7 +959,7 @@ c: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s c.te*", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename)) if result.Error != nil { t.Error(result.Error) } @@ -1119,7 +1102,7 @@ something: else` test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) } -func TestMergeCmd(t *testing.T) { +func xTestMergeCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge examples/data1.yaml examples/data2.yaml") if result.Error != nil { @@ -1135,7 +1118,7 @@ c: test.AssertResult(t, expectedOutput, result.Output) } -func TestMergeOverwriteCmd(t *testing.T) { +func xTestMergeOverwriteCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml") if result.Error != nil { @@ -1151,7 +1134,7 @@ c: test.AssertResult(t, expectedOutput, result.Output) } -func TestMergeAppendCmd(t *testing.T) { +func xTestMergeAppendCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml") if result.Error != nil { @@ -1168,7 +1151,7 @@ c: ` test.AssertResult(t, expectedOutput, result.Output) } -func TestMergeArraysCmd(t *testing.T) { +func xTestMergeArraysCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml") if result.Error != nil { @@ -1183,7 +1166,7 @@ func TestMergeArraysCmd(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } -func TestMergeCmd_Multi(t *testing.T) { +func xTestMergeCmd_Multi(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml") if result.Error != nil { @@ -1205,7 +1188,7 @@ c: test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) } -func TestMergeYamlMultiAllCmd(t *testing.T) { +func xTestMergeYamlMultiAllCmd(t *testing.T) { content := `b: c: 3 apples: green @@ -1234,7 +1217,7 @@ something: else` test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) } -func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) { +func xTestMergeYamlMultiAllOverwriteCmd(t *testing.T) { content := `b: c: 3 apples: green @@ -1263,7 +1246,7 @@ something: good` test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) } -func TestMergeCmd_Error(t *testing.T) { +func xTestMergeCmd_Error(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge examples/data1.yaml") if result.Error == nil { @@ -1273,7 +1256,7 @@ func TestMergeCmd_Error(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { +func xTestMergeCmd_ErrorUnreadableFile(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge examples/data1.yaml fake-unknown") if result.Error == nil { @@ -1288,7 +1271,7 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -func TestMergeCmd_Verbose(t *testing.T) { +func xTestMergeCmd_Verbose(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "-v merge examples/data1.yaml examples/data2.yaml") if result.Error != nil { @@ -1304,7 +1287,7 @@ c: test.AssertResult(t, expectedOutput, result.Output) } -func TestMergeCmd_Inplace(t *testing.T) { +func xTestMergeCmd_Inplace(t *testing.T) { filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml")) err := os.Chmod(filename, os.FileMode(int(0666))) if err != nil { @@ -1329,7 +1312,7 @@ c: test.AssertResult(t, os.FileMode(int(0666)), info.Mode()) } -func TestMergeAllowEmptyCmd(t *testing.T) { +func xTestMergeAllowEmptyCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --allow-empty examples/data1.yaml examples/empty.yaml") if result.Error != nil { diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 740fbc0b..ac21dbb7 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -3,7 +3,6 @@ package yqlib import ( "bytes" "strconv" - "strings" logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" @@ -18,16 +17,16 @@ type DataNavigator interface { } type navigator struct { - log *logging.Logger - followAliases bool + log *logging.Logger + navigationSettings NavigationSettings } type VisitorFn func(matchingNode *yaml.Node, pathStack []interface{}) error -func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator { +func NewDataNavigator(l *logging.Logger, navigationSettings NavigationSettings) DataNavigator { return &navigator{ - log: l, - followAliases: followAliases, + log: l, + navigationSettings: navigationSettings, } } @@ -95,7 +94,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { // need to delete in reverse - otherwise the matching indexes // become incorrect. matchingIndices := make([]int, 0) - _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(matchingNode []*yaml.Node, indexInMap int) error { + _, errorVisiting := n.visitMatchingEntries(nodeToUpdate, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error { matchingIndices = append(matchingIndices, indexInMap) n.log.Debug("matchingIndices %v", indexInMap) return nil @@ -199,9 +198,10 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito } return n.recurseArray(value, head, tail, visitor, pathStack) case yaml.AliasNode: - n.log.Debug("its an alias, followAliases: %v", n.followAliases) + n.log.Debug("its an alias!") n.DebugNode(value.Alias) - if n.followAliases == true { + if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true { + n.log.Debug("following the alias") return n.recurse(value.Alias, head, tail, visitor, pathStack) } return nil @@ -211,7 +211,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito } func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { - visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error { + visited, errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error { contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind)) return n.doVisit(contents[indexInMap+1], tail, visitor, append(pathStack, contents[indexInMap].Value)) }) @@ -220,11 +220,10 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis return errorVisiting } - if visited { + if visited || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false { return nil } - //TODO: have option to NOT do this... didn't find it, lets add it. mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} value.Content = append(value.Content, &mapEntryKey) mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)} @@ -236,13 +235,14 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis // need to pass the node in, as it may be aliased type mapVisitorFn func(contents []*yaml.Node, index int) error -func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { + var contents = node.Content visited := false for index := 0; index < len(contents); index = index + 2 { content := contents[index] n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) - if n.matchesKey(key, content.Value) { + if n.navigationSettings.ShouldVisit(content, head, tail, pathStack) == true { n.log.Debug("found a match! %v", content.Value) errorVisiting := visit(contents, index) if errorVisiting != nil { @@ -254,27 +254,28 @@ func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string return visited, nil } -func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { - - n.log.Debug("visitMatchingEntries %v in %v", key, contents) +func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { + var contents = node.Content + n.log.Debug("visitMatchingEntries %v", head) + n.DebugNode(node) // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them // if we don't find a match directly on this node first. - visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(contents, key, visit) + visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) //TODO: crap we have to remember what we visited so we dont print the same key in the alias // eff - if errorVisitedDirectEntries != nil || n.followAliases == false { + if errorVisitedDirectEntries != nil || visited == true || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false { return visited, errorVisitedDirectEntries } // didnt find a match, lets check the aliases. - return n.visitAliases(contents, key, visit) + return n.visitAliases(contents, head, tail, pathStack, visit) } -func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { // merge aliases are defined first, but we only want to traverse them // if we don't find a match on this node first. // traverse them backwards so that the last alias overrides the preceding. @@ -290,13 +291,13 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis n.DebugNode(contents[index]) n.DebugNode(valueNode) - visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) + visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit) if visitedAlias == true || errorInAlias != nil { return visitedAlias, errorInAlias } } else if contents[index+1].Kind == yaml.SequenceNode { // could be an array of aliases... - visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, key, visit) + visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit) if visitedAliasSeq == true || errorVisitingAliasSeq != nil { return visitedAliasSeq, errorVisitingAliasSeq } @@ -306,14 +307,14 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis return false, nil } -func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { // need to search this backwards too, so that aliases defined last override the preceding. for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { child := possibleAliasArray[aliasIndex] if child.Kind == yaml.AliasNode { n.log.Debug("found an alias") n.DebugNode(child) - visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit) + visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit) if visitedAlias == true || errorInAlias != nil { return visitedAlias, errorInAlias } @@ -322,19 +323,6 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key stri return false, nil } -func (n *navigator) matchesKey(key string, actual string) bool { - n.log.Debug("key: (%v), actual: (%v)", key, actual) - if n.followAliases == true && actual == "<<" { - // dont match alias keys, as we'll follow them instead - return false - } - var prefixMatch = strings.TrimSuffix(key, "*") - if prefixMatch != key { - return strings.HasPrefix(actual, prefixMatch) - } - return actual == key -} - func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { for index, childValue := range value.Content { n.log.Debug("processing") diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 637d8e1c..b4d4dc9e 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -34,25 +34,25 @@ func NewYqLib(l *logging.Logger) YqLib { } func (l *lib) DebugNode(node *yaml.Node) { - navigator := NewDataNavigator(l.log, false) + navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log)) navigator.DebugNode(node) } func (l *lib) Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) { var paths = l.parser.ParsePath(path) - navigator := NewDataNavigator(l.log, true) + navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log)) return navigator.Get(rootNode, paths) } func (l *lib) New(path string) yaml.Node { var paths = l.parser.ParsePath(path) - navigator := NewDataNavigator(l.log, false) + navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log)) newNode := yaml.Node{Kind: navigator.GuessKind(paths, 0)} return newNode } func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { - navigator := NewDataNavigator(l.log, false) + navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log)) l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) switch updateCommand.Command { case "update": diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go new file mode 100644 index 00000000..e48c88c8 --- /dev/null +++ b/pkg/yqlib/navigation_settings.go @@ -0,0 +1,78 @@ +package yqlib + +import ( + "strings" + + logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" +) + +type NavigationSettings interface { + FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool +} + +type NavigationSettingsImpl struct { + followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + shouldVisit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool +} + +func (ns NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return ns.followAlias(node, head, tail, pathStack) +} + +func (ns NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return ns.autoCreateMap(node, head, tail, pathStack) +} + +func (ns NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return ns.shouldVisit(node, head, tail, pathStack) +} + +func UpdateNavigationSettings(l *logging.Logger) NavigationSettings { + return NavigationSettingsImpl{ + followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return false + }, + autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return true + }, + shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) + return strings.HasPrefix(node.Value, prefixMatch) + } + l.Debug("equals match, %v", node.Value == head) + return node.Value == head + }, + } +} + +func ReadNavigationSettings(l *logging.Logger) NavigationSettings { + return NavigationSettingsImpl{ + followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return true + }, + autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return false + }, + shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + l.Debug("shouldVisit h: %v, actual: %v", head, node.Value) + if node.Value == "<<" { + l.Debug("its an alias, skip it") + // dont match alias keys, as we'll follow them instead + return false + } + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) + return strings.HasPrefix(node.Value, prefixMatch) + } + l.Debug("equals match, %v", node.Value == head) + return node.Value == head + }, + } +} diff --git a/test.yml b/test.yml index c83b2dcf..804fbe36 100644 --- a/test.yml +++ b/test.yml @@ -1,4 +1,11 @@ -foo: &foo - a: 1 - -foobar: *foo \ No newline at end of file +a: 2 +b: + hi: + c: things + d: something else + there: + c: more things + d: more something else + there2: + c: more things also + d: more something else also \ No newline at end of file From 9925b26b9dc2624f057793034b37a2e283b989fe Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 24 Dec 2019 10:46:21 +1100 Subject: [PATCH 29/68] Added Key and Value printing tests --- commands_test.go | 143 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/commands_test.go b/commands_test.go index 6421817c..62bb6a04 100644 --- a/commands_test.go +++ b/commands_test.go @@ -94,6 +94,24 @@ func TestReadCmd(t *testing.T) { test.AssertResult(t, "2", result.Output) } +func TestReadWithKeyAndValueCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.c") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "b.c: 2\n", result.Output) +} + +func TestReadWithKeyCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "b.c", result.Output) +} + func TestReadAnchorsCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a") @@ -103,6 +121,15 @@ func TestReadAnchorsCmd(t *testing.T) { test.AssertResult(t, "1", result.Output) } +func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/simple-anchor.yaml foobar.a") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "foobar.a: 1\n", result.Output) +} + func TestReadMergeAnchorsOriginalCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a") @@ -190,6 +217,15 @@ func TestReadMultiCmd(t *testing.T) { test.AssertResult(t, "here", result.Output) } +func TestReadMultiWithKeyAndValueCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p vk -d 1 examples/multiple_docs.yaml another.document") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "another.document: here\n", result.Output) +} + func TestReadMultiAllCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -d* examples/multiple_docs.yaml commonKey") @@ -202,6 +238,19 @@ second document third document`, result.Output) } +func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv -d* examples/multiple_docs.yaml commonKey") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, + `commonKey: first document +commonKey: second document +commonKey: third document +`, result.Output) +} + func TestReadCmd_ArrayYaml(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [0].gather_facts") @@ -249,7 +298,7 @@ serial: 1 test.AssertResult(t, expectedOutput, result.Output) } -func TestReadCmd_ArrayYaml_SplatA(t *testing.T) { +func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [*]") if result.Error != nil { @@ -269,6 +318,39 @@ gather_facts: true test.AssertResult(t, expectedOutput, result.Output) } +func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/array.yaml [*]") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `'[0]': + become: true + gather_facts: false + hosts: lalaland + name: "Apply smth" + roles: + - lala + - land + serial: 1 +'[1]': + become: false + gather_facts: true +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p k examples/array.yaml [*]") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `[0] +[1]` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts") @@ -350,7 +432,7 @@ func TestReadCmd_ErrorBadPath(t *testing.T) { func TestReadCmd_Verbose(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -v examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -375,7 +457,7 @@ func TestReadCmd_Verbose(t *testing.T) { // test.AssertResult(t, "2\n", result.Output) // } -func TestReadSplatPrefixYaml(t *testing.T) { +func TestReadSplatPrefixCmd(t *testing.T) { content := `a: 2 b: hi: @@ -402,6 +484,61 @@ more things also` test.AssertResult(t, expectedOutput, result.Output) } +func TestReadSplatPrefixWithKeyAndValueCmd(t *testing.T) { + content := `a: 2 +b: + hi: + c: things + d: something else + there: + c: more things + d: more something else + there2: + c: more things also + d: more something else also +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("read -p kv %s b.there*.c", filename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `b.there.c: more things +b.there2.c: more things also +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestReadSplatPrefixWithKeyCmd(t *testing.T) { + content := `a: 2 +b: + hi: + c: things + d: something else + there: + c: more things + d: more something else + there2: + c: more things also + d: more something else also +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("read -p k %s b.there*.c", filename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `b.there.c +b.there2.c` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestPrefixCmd(t *testing.T) { content := `b: c: 3 From ff5b23251b71d45ecaa00b364b25d9127d2ee147 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 25 Dec 2019 12:11:04 +1100 Subject: [PATCH 30/68] Refactor wip --- commands_test.go | 19 +- examples/merge-anchor.yaml | 2 + pkg/yqlib/data_navigator.go | 245 ++++++------------------ pkg/yqlib/delete_navigation_settings.go | 72 +++++++ pkg/yqlib/lib.go | 75 ++++++-- pkg/yqlib/navigation_settings.go | 86 ++++----- pkg/yqlib/read_navigation_strategy.go | 37 ++++ pkg/yqlib/update_navigation_strategy.go | 43 +++++ yq.go | 10 +- 9 files changed, 331 insertions(+), 258 deletions(-) create mode 100644 pkg/yqlib/delete_navigation_settings.go create mode 100644 pkg/yqlib/read_navigation_strategy.go create mode 100644 pkg/yqlib/update_navigation_strategy.go diff --git a/commands_test.go b/commands_test.go index 62bb6a04..6c08c86e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -148,6 +148,18 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) { test.AssertResult(t, "ice", result.Output) } +func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "r -p kv examples/merge-anchor.yaml foobar.th*") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `foobar.thing: ice +foobar.thirty: well beyond +foobar.thirsty: yep` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a") @@ -365,8 +377,11 @@ true` func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) + if result.Error == nil { + t.Error("Expected command to fail due to missing arg") + } + expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { diff --git a/examples/merge-anchor.yaml b/examples/merge-anchor.yaml index 2459c9a9..048b02e2 100644 --- a/examples/merge-anchor.yaml +++ b/examples/merge-anchor.yaml @@ -1,6 +1,7 @@ foo: &foo a: original thing: coolasdf + thirsty: yep bar: &bar b: 2 @@ -14,4 +15,5 @@ foobarList: foobar: <<: *foo thing: ice + thirty: well beyond c: 3 diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index ac21dbb7..0bace7c4 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -1,208 +1,73 @@ package yqlib import ( - "bytes" + "fmt" "strconv" - logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" ) type DataNavigator interface { - DebugNode(node *yaml.Node) - Get(rootNode *yaml.Node, path []string) ([]MatchingNode, error) - Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error - Delete(rootNode *yaml.Node, path []string) error - GuessKind(tail []string, guess yaml.Kind) yaml.Kind + Traverse(value *yaml.Node, path []string) error } type navigator struct { - log *logging.Logger navigationSettings NavigationSettings } -type VisitorFn func(matchingNode *yaml.Node, pathStack []interface{}) error - -func NewDataNavigator(l *logging.Logger, navigationSettings NavigationSettings) DataNavigator { +func NewDataNavigator(navigationSettings NavigationSettings) DataNavigator { return &navigator{ - log: l, navigationSettings: navigationSettings, } } -type MatchingNode struct { - Node *yaml.Node - PathStack []interface{} -} - -func (n *navigator) Get(value *yaml.Node, path []string) ([]MatchingNode, error) { - matchingNodes := make([]MatchingNode, 0) - - n.Visit(value, path, func(matchedNode *yaml.Node, pathStack []interface{}) error { - matchingNodes = append(matchingNodes, MatchingNode{matchedNode, pathStack}) - n.log.Debug("Matched") - for _, pathElement := range pathStack { - n.log.Debug("%v", pathElement) - } - n.DebugNode(matchedNode) - return nil - }) - return matchingNodes, nil -} - -func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error { - errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error { - n.log.Debug("going to update") - n.DebugNode(nodeToUpdate) - n.log.Debug("with") - n.DebugNode(changesToApply) - nodeToUpdate.Value = changesToApply.Value - nodeToUpdate.Tag = changesToApply.Tag - nodeToUpdate.Kind = changesToApply.Kind - nodeToUpdate.Style = changesToApply.Style - nodeToUpdate.Content = changesToApply.Content - nodeToUpdate.HeadComment = changesToApply.HeadComment - nodeToUpdate.LineComment = changesToApply.LineComment - nodeToUpdate.FootComment = changesToApply.FootComment - return nil - }) - return errorVisiting -} - -// TODO: refactor delete.. -func (n *navigator) Delete(rootNode *yaml.Node, path []string) error { - - lastBit, newTail := path[len(path)-1], path[:len(path)-1] - n.log.Debug("splitting path, %v", lastBit) - n.log.Debug("new tail, %v", newTail) - errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error { - n.log.Debug("need to find %v in here", lastBit) - n.DebugNode(nodeToUpdate) - original := nodeToUpdate.Content - if nodeToUpdate.Kind == yaml.SequenceNode { - var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint - if err != nil { - return err - } - if index >= int64(len(nodeToUpdate.Content)) { - n.log.Debug("index %v is greater than content length %v", index, len(nodeToUpdate.Content)) - return nil - } - nodeToUpdate.Content = append(original[:index], original[index+1:]...) - - } else if nodeToUpdate.Kind == yaml.MappingNode { - // need to delete in reverse - otherwise the matching indexes - // become incorrect. - matchingIndices := make([]int, 0) - _, errorVisiting := n.visitMatchingEntries(nodeToUpdate, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error { - matchingIndices = append(matchingIndices, indexInMap) - n.log.Debug("matchingIndices %v", indexInMap) - return nil - }) - n.log.Debug("delete matching indices now") - n.log.Debug("%v", matchingIndices) - if errorVisiting != nil { - return errorVisiting - } - for i := len(matchingIndices) - 1; i >= 0; i-- { - indexToDelete := matchingIndices[i] - n.log.Debug("deleting index %v, %v", indexToDelete, nodeToUpdate.Content[indexToDelete].Value) - nodeToUpdate.Content = append(nodeToUpdate.Content[:indexToDelete], nodeToUpdate.Content[indexToDelete+2:]...) - } - - } - - return nil - }) - return errorVisiting -} - -func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) error { +func (n *navigator) Traverse(value *yaml.Node, path []string) error { realValue := value emptyArray := make([]interface{}, 0) if realValue.Kind == yaml.DocumentNode { - n.log.Debugf("its a document! returning the first child") - return n.doVisit(value.Content[0], path, visitor, emptyArray) + log.Debugf("its a document! returning the first child") + return n.doTraverse(value.Content[0], "", path, emptyArray) } - return n.doVisit(value, path, visitor, emptyArray) + return n.doTraverse(value, "", path, emptyArray) } -func (n *navigator) doVisit(value *yaml.Node, path []string, visitor VisitorFn, pathStack []interface{}) error { +func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pathStack []interface{}) error { if len(path) > 0 { - n.log.Debugf("diving into %v", path[0]) - n.DebugNode(value) - return n.recurse(value, path[0], path[1:], visitor, pathStack) + log.Debugf("diving into %v", path[0]) + DebugNode(value) + return n.recurse(value, path[0], path[1:], pathStack) } - return visitor(value, pathStack) -} - -func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind { - n.log.Debug("tail %v", tail) - if len(tail) == 0 && guess == 0 { - n.log.Debug("end of path, must be a scalar") - return yaml.ScalarNode - } else if len(tail) == 0 { - return guess - } - - var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64) - if tail[0] == "+" || errorParsingInt == nil { - return yaml.SequenceNode - } - if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { - return guess - } - if guess == yaml.AliasNode { - n.log.Debug("guess was an alias, okey doke.") - return guess - } - n.log.Debug("forcing a mapping node") - n.log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode) - n.log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode) - return yaml.MappingNode + return n.navigationSettings.Visit(value, head, path, pathStack) } func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { if original.Kind != expectedKind { - n.log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind) + log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind) return &yaml.Node{Kind: expectedKind} } return original } -func (n *navigator) DebugNode(value *yaml.Node) { - if value == nil { - n.log.Debug("-- node is nil --") - } else if n.log.IsEnabledFor(logging.DEBUG) { - buf := new(bytes.Buffer) - encoder := yaml.NewEncoder(buf) - encoder.Encode(value) - encoder.Close() - n.log.Debug("Tag: %v", value.Tag) - n.log.Debug("%v", buf.String()) - } -} - -func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { - n.log.Debug("recursing, processing %v", head) +func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { + log.Debug("recursing, processing %v", head) switch value.Kind { case yaml.MappingNode: - n.log.Debug("its a map with %v entries", len(value.Content)/2) - return n.recurseMap(value, head, tail, visitor, pathStack) + log.Debug("its a map with %v entries", len(value.Content)/2) + return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: - n.log.Debug("its a sequence of %v things!, %v", len(value.Content)) + log.Debug("its a sequence of %v things!, %v", len(value.Content)) if head == "*" { - return n.splatArray(value, tail, visitor, pathStack) + return n.splatArray(value, tail, pathStack) } else if head == "+" { - return n.appendArray(value, tail, visitor, pathStack) + return n.appendArray(value, tail, pathStack) } - return n.recurseArray(value, head, tail, visitor, pathStack) + return n.recurseArray(value, head, tail, pathStack) case yaml.AliasNode: - n.log.Debug("its an alias!") - n.DebugNode(value.Alias) + log.Debug("its an alias!") + DebugNode(value.Alias) if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true { - n.log.Debug("following the alias") - return n.recurse(value.Alias, head, tail, visitor, pathStack) + log.Debug("following the alias") + return n.recurse(value.Alias, head, tail, pathStack) } return nil default: @@ -210,10 +75,10 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito } } -func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { +func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { visited, errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error { - contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind)) - return n.doVisit(contents[indexInMap+1], tail, visitor, append(pathStack, contents[indexInMap].Value)) + contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) + return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) }) if errorVisiting != nil { @@ -226,10 +91,10 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} value.Content = append(value.Content, &mapEntryKey) - mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)} + mapEntryValue := yaml.Node{Kind: guessKind(tail, 0)} value.Content = append(value.Content, &mapEntryValue) - n.log.Debug("adding new node %v", value.Content) - return n.doVisit(&mapEntryValue, tail, visitor, append(pathStack, head)) + log.Debug("adding new node %v", value.Content) + return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head)) } // need to pass the node in, as it may be aliased @@ -240,10 +105,10 @@ func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tai visited := false for index := 0; index < len(contents); index = index + 2 { content := contents[index] - n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) + log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) if n.navigationSettings.ShouldVisit(content, head, tail, pathStack) == true { - n.log.Debug("found a match! %v", content.Value) + log.Debug("found a match! %v", content.Value) errorVisiting := visit(contents, index) if errorVisiting != nil { return visited, errorVisiting @@ -256,8 +121,8 @@ func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tai func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { var contents = node.Content - n.log.Debug("visitMatchingEntries %v", head) - n.DebugNode(node) + log.Debug("visitMatchingEntries %v", head) + DebugNode(node) // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them @@ -282,14 +147,14 @@ func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []stri // a node can either be // an alias to one other node (e.g. <<: *blah) // or a sequence of aliases (e.g. <<: [*blah, *foo]) - n.log.Debug("checking for aliases") + log.Debug("checking for aliases") for index := len(contents) - 2; index >= 0; index = index - 2 { if contents[index+1].Kind == yaml.AliasNode { valueNode := contents[index+1] - n.log.Debug("found an alias") - n.DebugNode(contents[index]) - n.DebugNode(valueNode) + log.Debug("found an alias") + DebugNode(contents[index]) + DebugNode(valueNode) visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit) if visitedAlias == true || errorInAlias != nil { @@ -303,7 +168,7 @@ func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []stri } } } - n.log.Debug("nope no matching aliases found") + log.Debug("nope no matching aliases found") return false, nil } @@ -312,8 +177,8 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head str for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { child := possibleAliasArray[aliasIndex] if child.Kind == yaml.AliasNode { - n.log.Debug("found an alias") - n.DebugNode(child) + log.Debug("found an alias") + DebugNode(child) visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit) if visitedAlias == true || errorInAlias != nil { return visitedAlias, errorInAlias @@ -323,12 +188,13 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head str return false, nil } -func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { +func (n *navigator) splatArray(value *yaml.Node, tail []string, pathStack []interface{}) error { for index, childValue := range value.Content { - n.log.Debug("processing") - n.DebugNode(childValue) - childValue = n.getOrReplace(childValue, n.GuessKind(tail, childValue.Kind)) - var err = n.doVisit(childValue, tail, visitor, append(pathStack, index)) + log.Debug("processing") + DebugNode(childValue) + head := fmt.Sprintf("%v", index) + childValue = n.getOrReplace(childValue, guessKind(tail, childValue.Kind)) + var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) if err != nil { return err } @@ -336,14 +202,15 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF return nil } -func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error { - var newNode = yaml.Node{Kind: n.GuessKind(tail, 0)} +func (n *navigator) appendArray(value *yaml.Node, tail []string, pathStack []interface{}) error { + var newNode = yaml.Node{Kind: guessKind(tail, 0)} value.Content = append(value.Content, &newNode) - n.log.Debug("appending a new node, %v", value.Content) - return n.doVisit(&newNode, tail, visitor, append(pathStack, len(value.Content)-1)) + log.Debug("appending a new node, %v", value.Content) + head := fmt.Sprintf("%v", len(value.Content)-1) + return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1)) } -func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error { +func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { return err @@ -351,6 +218,6 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v if index >= int64(len(value.Content)) { return nil } - value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind)) - return n.doVisit(value.Content[index], tail, visitor, append(pathStack, index)) + value.Content[index] = n.getOrReplace(value.Content[index], guessKind(tail, value.Content[index].Kind)) + return n.doTraverse(value.Content[index], head, tail, append(pathStack, index)) } diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go new file mode 100644 index 00000000..ad1c8ff9 --- /dev/null +++ b/pkg/yqlib/delete_navigation_settings.go @@ -0,0 +1,72 @@ +package yqlib + +import ( + "strconv" + "strings" + + yaml "gopkg.in/yaml.v3" +) + +func DeleteNavigationSettings(lastBit string) NavigationSettings { + return &NavigationSettingsImpl{ + visitedNodes: []*VisitedNode{}, + followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return false + }, + autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return true + }, + shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) + return strings.HasPrefix(node.Value, prefixMatch) + } + log.Debug("equals match, %v", node.Value == head) + return node.Value == head + }, + visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + log.Debug("need to find %v in here", lastBit) + DebugNode(node) + if node.Kind == yaml.SequenceNode { + newContent, errorDeleting := deleteFromArray(node.Content, lastBit) + if errorDeleting != nil { + return errorDeleting + } + node.Content = newContent + } else if node.Kind == yaml.MappingNode { + // need to delete in reverse - otherwise the matching indexes + // become incorrect. + // matchingIndices := make([]int, 0) + // _, errorVisiting := n.visitMatchingEntries(node, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error { + // matchingIndices = append(matchingIndices, indexInMap) + // log.Debug("matchingIndices %v", indexInMap) + // return nil + // }) + // log.Debug("delete matching indices now") + // log.Debug("%v", matchingIndices) + // if errorVisiting != nil { + // return errorVisiting + // } + // for i := len(matchingIndices) - 1; i >= 0; i-- { + // indexToDelete := matchingIndices[i] + // log.Debug("deleting index %v, %v", indexToDelete, node.Content[indexToDelete].Value) + // node.Content = append(node.Content[:indexToDelete], node.Content[indexToDelete+2:]...) + // } + } + return nil + }, + } +} + +func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) { + var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint + if err != nil { + return content, err + } + if index >= int64(len(content)) { + log.Debug("index %v is greater than content length %v", index, len(content)) + return content, nil + } + return append(content[:index], content[index+1:]...), nil +} diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index b4d4dc9e..c11773d1 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -1,21 +1,63 @@ package yqlib import ( + "bytes" "fmt" + "strconv" logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" ) +var log = logging.MustGetLogger("yq") + type UpdateCommand struct { Command string Path string Value *yaml.Node } +func DebugNode(value *yaml.Node) { + if value == nil { + log.Debug("-- node is nil --") + } else if log.IsEnabledFor(logging.DEBUG) { + buf := new(bytes.Buffer) + encoder := yaml.NewEncoder(buf) + encoder.Encode(value) + encoder.Close() + log.Debug("Tag: %v", value.Tag) + log.Debug("%v", buf.String()) + } +} + +func guessKind(tail []string, guess yaml.Kind) yaml.Kind { + log.Debug("tail %v", tail) + if len(tail) == 0 && guess == 0 { + log.Debug("end of path, must be a scalar") + return yaml.ScalarNode + } else if len(tail) == 0 { + return guess + } + + var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64) + if tail[0] == "+" || errorParsingInt == nil { + return yaml.SequenceNode + } + if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { + return guess + } + if guess == yaml.AliasNode { + log.Debug("guess was an alias, okey doke.") + return guess + } + log.Debug("forcing a mapping node") + log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode) + log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode) + return yaml.MappingNode +} + type YqLib interface { - DebugNode(node *yaml.Node) - Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) + Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error New(path string) yaml.Node } @@ -23,44 +65,41 @@ type YqLib interface { type lib struct { navigator DataNavigator parser PathParser - log *logging.Logger } func NewYqLib(l *logging.Logger) YqLib { return &lib{ parser: NewPathParser(), - log: l, } } -func (l *lib) DebugNode(node *yaml.Node) { - navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log)) - navigator.DebugNode(node) -} - -func (l *lib) Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) { +func (l *lib) Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) { var paths = l.parser.ParsePath(path) - navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log)) - return navigator.Get(rootNode, paths) + navigationSettings := ReadNavigationSettings() + navigator := NewDataNavigator(navigationSettings) + error := navigator.Traverse(rootNode, paths) + return navigationSettings.GetVisitedNodes(), error + } func (l *lib) New(path string) yaml.Node { var paths = l.parser.ParsePath(path) - navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log)) - newNode := yaml.Node{Kind: navigator.GuessKind(paths, 0)} + newNode := yaml.Node{Kind: guessKind(paths, 0)} return newNode } func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { - navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log)) - l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) + log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) switch updateCommand.Command { case "update": var paths = l.parser.ParsePath(updateCommand.Path) - return navigator.Update(rootNode, paths, updateCommand.Value) + navigator := NewDataNavigator(UpdateNavigationSettings(updateCommand.Value)) + return navigator.Traverse(rootNode, paths) case "delete": var paths = l.parser.ParsePath(updateCommand.Path) - return navigator.Delete(rootNode, paths) + lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1] + navigator := NewDataNavigator(DeleteNavigationSettings(lastBit)) + return navigator.Traverse(rootNode, newTail) default: return fmt.Errorf("Unknown command %v", updateCommand.Command) } diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go index e48c88c8..5ee1d6dc 100644 --- a/pkg/yqlib/navigation_settings.go +++ b/pkg/yqlib/navigation_settings.go @@ -3,76 +3,74 @@ package yqlib import ( "strings" - logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" ) +type VisitedNode struct { + Node *yaml.Node + Head string + Tail []string + PathStack []interface{} +} + type NavigationSettings interface { FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error + GetVisitedNodes() []*VisitedNode } type NavigationSettingsImpl struct { followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool shouldVisit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error + visitedNodes []*VisitedNode } -func (ns NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { +func matches(node *yaml.Node, head string) bool { + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) + return strings.HasPrefix(node.Value, prefixMatch) + } + log.Debug("equals match, %v", node.Value == head) + return node.Value == head +} + +func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode { + return ns.visitedNodes +} + +func (ns *NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { return ns.followAlias(node, head, tail, pathStack) } -func (ns NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { +func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { return ns.autoCreateMap(node, head, tail, pathStack) } -func (ns NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - return ns.shouldVisit(node, head, tail, pathStack) +func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack}) + log.Debug("adding to visited nodes") + return ns.visit(node, head, tail, pathStack) } -func UpdateNavigationSettings(l *logging.Logger) NavigationSettings { - return NavigationSettingsImpl{ - followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - return false - }, - autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - return true - }, - shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) - return strings.HasPrefix(node.Value, prefixMatch) - } - l.Debug("equals match, %v", node.Value == head) - return node.Value == head - }, +func (ns *NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + if !ns.alreadyVisited(node) { + return ns.shouldVisit(node, head, tail, pathStack) + } else { + log.Debug("Skipping over %v as we have seen it already", node.Value) } + return false } -func ReadNavigationSettings(l *logging.Logger) NavigationSettings { - return NavigationSettingsImpl{ - followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { +func (ns *NavigationSettingsImpl) alreadyVisited(node *yaml.Node) bool { + for _, candidate := range ns.visitedNodes { + if candidate.Node.Value == node.Value { return true - }, - autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - return false - }, - shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - l.Debug("shouldVisit h: %v, actual: %v", head, node.Value) - if node.Value == "<<" { - l.Debug("its an alias, skip it") - // dont match alias keys, as we'll follow them instead - return false - } - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) - return strings.HasPrefix(node.Value, prefixMatch) - } - l.Debug("equals match, %v", node.Value == head) - return node.Value == head - }, + } } + return false } diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go new file mode 100644 index 00000000..08d12929 --- /dev/null +++ b/pkg/yqlib/read_navigation_strategy.go @@ -0,0 +1,37 @@ +package yqlib + +import ( + "strings" + + yaml "gopkg.in/yaml.v3" +) + +func ReadNavigationSettings() NavigationSettings { + return &NavigationSettingsImpl{ + visitedNodes: []*VisitedNode{}, + followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return true + }, + autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return false + }, + shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + log.Debug("shouldVisit h: %v, actual: %v", head, node.Value) + if node.Value == "<<" { + log.Debug("its an alias, skip it") + // dont match alias keys, as we'll follow them instead + return false + } + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) + return strings.HasPrefix(node.Value, prefixMatch) + } + log.Debug("equals match, %v", node.Value == head) + return node.Value == head + }, + visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + return nil + }, + } +} diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go new file mode 100644 index 00000000..021b78ce --- /dev/null +++ b/pkg/yqlib/update_navigation_strategy.go @@ -0,0 +1,43 @@ +package yqlib + +import ( + "strings" + + yaml "gopkg.in/yaml.v3" +) + +func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings { + return &NavigationSettingsImpl{ + visitedNodes: []*VisitedNode{}, + followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return false + }, + autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return true + }, + shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) + return strings.HasPrefix(node.Value, prefixMatch) + } + log.Debug("equals match, %v", node.Value == head) + return node.Value == head + }, + visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + log.Debug("going to update") + DebugNode(node) + log.Debug("with") + DebugNode(changesToApply) + node.Value = changesToApply.Value + node.Tag = changesToApply.Tag + node.Kind = changesToApply.Kind + node.Style = changesToApply.Style + node.Content = changesToApply.Content + node.HeadComment = changesToApply.HeadComment + node.LineComment = changesToApply.LineComment + node.FootComment = changesToApply.FootComment + return nil + }, + } +} diff --git a/yq.go b/yq.go index 99c96fed..64614d09 100644 --- a/yq.go +++ b/yq.go @@ -257,7 +257,7 @@ func readProperty(cmd *cobra.Command, args []string) error { return errorParsingDocIndex } - var matchingNodes []yqlib.MatchingNode + var matchingNodes []*yqlib.VisitedNode var currentIndex = 0 var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error { @@ -292,9 +292,9 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { return nil } -func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]yqlib.MatchingNode, error) { +func appendDocument(originalMatchingNodes []*yqlib.VisitedNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.VisitedNode, error) { log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - lib.DebugNode(&dataBucket) + yqlib.DebugNode(&dataBucket) if !updateAll && currentIndex != docIndexInt { return originalMatchingNodes, nil } @@ -337,7 +337,7 @@ func printValue(node *yaml.Node, cmd *cobra.Command) error { return nil } -func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error { +func printResults(matchingNodes []*yqlib.VisitedNode, cmd *cobra.Command) error { if len(matchingNodes) == 0 { log.Debug("no matching results, nothing to print") return nil @@ -485,7 +485,7 @@ func prefixProperty(cmd *cobra.Command, args []string) error { func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { if updateAll || currentIndex == docIndexInt { log.Debugf("Prefixing document %v", currentIndex) - lib.DebugNode(dataBucket) + yqlib.DebugNode(dataBucket) updateCommand.Value = dataBucket.Content[0] dataBucket.Content = make([]*yaml.Node, 1) From cf389bed4a59f800205be63d32355017f360c677 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 27 Dec 2019 19:06:08 +1100 Subject: [PATCH 31/68] Refactor wip --- commands_test.go | 7 +- examples/merge-anchor.yaml | 2 +- examples/sample2.yaml | 1 + pkg/yqlib/data_navigator.go | 78 ++++++++++---------- pkg/yqlib/delete_navigation_settings.go | 10 --- pkg/yqlib/navigation_settings.go | 94 ++++++++++++++++++++----- pkg/yqlib/read_navigation_strategy.go | 17 ----- pkg/yqlib/update_navigation_strategy.go | 11 --- test.yml | 12 +--- 9 files changed, 125 insertions(+), 107 deletions(-) diff --git a/commands_test.go b/commands_test.go index 6c08c86e..33aeb4a8 100644 --- a/commands_test.go +++ b/commands_test.go @@ -154,9 +154,10 @@ func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `foobar.thing: ice -foobar.thirty: well beyond -foobar.thirsty: yep` + expectedOutput := `foobar.thirty: well beyond +foobar.thing: ice +foobar.thirsty: yep +` test.AssertResult(t, expectedOutput, result.Output) } diff --git a/examples/merge-anchor.yaml b/examples/merge-anchor.yaml index 048b02e2..6d3f426d 100644 --- a/examples/merge-anchor.yaml +++ b/examples/merge-anchor.yaml @@ -14,6 +14,6 @@ foobarList: foobar: <<: *foo - thing: ice thirty: well beyond + thing: ice c: 3 diff --git a/examples/sample2.yaml b/examples/sample2.yaml index fea95f2e..e16e0b69 100644 --- a/examples/sample2.yaml +++ b/examples/sample2.yaml @@ -3,6 +3,7 @@ b: c: things d: whatever things: + borg: snorg thing1: cat: 'fred' thing2: diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 0bace7c4..23734111 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -37,6 +37,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat DebugNode(value) return n.recurse(value, path[0], path[1:], pathStack) } + log.Debug("should I visit?") return n.navigationSettings.Visit(value, head, path, pathStack) } @@ -76,16 +77,27 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt } func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { - visited, errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error { - contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) - return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) + traversedEntry := false + errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error { + + log.Debug("should I traverse? %v", head) + DebugNode(value) + if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) == true { + log.Debug("yep!") + traversedEntry = true + contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) + return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) + } else { + log.Debug("nope not traversing") + } + return nil }) if errorVisiting != nil { return errorVisiting } - if visited || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false { + if traversedEntry == true || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false { return nil } @@ -93,33 +105,27 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat value.Content = append(value.Content, &mapEntryKey) mapEntryValue := yaml.Node{Kind: guessKind(tail, 0)} value.Content = append(value.Content, &mapEntryValue) - log.Debug("adding new node %v", value.Content) + log.Debug("adding new node %v", head) return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head)) } // need to pass the node in, as it may be aliased type mapVisitorFn func(contents []*yaml.Node, index int) error -func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error { var contents = node.Content - visited := false for index := 0; index < len(contents); index = index + 2 { content := contents[index] log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) - - if n.navigationSettings.ShouldVisit(content, head, tail, pathStack) == true { - log.Debug("found a match! %v", content.Value) - errorVisiting := visit(contents, index) - if errorVisiting != nil { - return visited, errorVisiting - } - visited = true + errorVisiting := visit(contents, index) + if errorVisiting != nil { + return errorVisiting } } - return visited, nil + return nil } -func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error { var contents = node.Content log.Debug("visitMatchingEntries %v", head) DebugNode(node) @@ -127,20 +133,15 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them // if we don't find a match directly on this node first. - visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) + errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) - //TODO: crap we have to remember what we visited so we dont print the same key in the alias - // eff - - if errorVisitedDirectEntries != nil || visited == true || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false { - return visited, errorVisitedDirectEntries + if errorVisitedDirectEntries != nil || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false { + return errorVisitedDirectEntries } - // didnt find a match, lets check the aliases. - return n.visitAliases(contents, head, tail, pathStack, visit) } -func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error { // merge aliases are defined first, but we only want to traverse them // if we don't find a match on this node first. // traverse them backwards so that the last alias overrides the preceding. @@ -156,36 +157,35 @@ func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []stri DebugNode(contents[index]) DebugNode(valueNode) - visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit) - if visitedAlias == true || errorInAlias != nil { - return visitedAlias, errorInAlias + errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit) + if errorInAlias != nil { + return errorInAlias } } else if contents[index+1].Kind == yaml.SequenceNode { // could be an array of aliases... - visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit) - if visitedAliasSeq == true || errorVisitingAliasSeq != nil { - return visitedAliasSeq, errorVisitingAliasSeq + errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit) + if errorVisitingAliasSeq != nil { + return errorVisitingAliasSeq } } } - log.Debug("nope no matching aliases found") - return false, nil + return nil } -func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) { +func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error { // need to search this backwards too, so that aliases defined last override the preceding. for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { child := possibleAliasArray[aliasIndex] if child.Kind == yaml.AliasNode { log.Debug("found an alias") DebugNode(child) - visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit) - if visitedAlias == true || errorInAlias != nil { - return visitedAlias, errorInAlias + errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit) + if errorInAlias != nil { + return errorInAlias } } } - return false, nil + return nil } func (n *navigator) splatArray(value *yaml.Node, tail []string, pathStack []interface{}) error { diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go index ad1c8ff9..dd2aabf8 100644 --- a/pkg/yqlib/delete_navigation_settings.go +++ b/pkg/yqlib/delete_navigation_settings.go @@ -2,7 +2,6 @@ package yqlib import ( "strconv" - "strings" yaml "gopkg.in/yaml.v3" ) @@ -16,15 +15,6 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings { autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { return true }, - shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) - return strings.HasPrefix(node.Value, prefixMatch) - } - log.Debug("equals match, %v", node.Value == head) - return node.Value == head - }, visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { log.Debug("need to find %v in here", lastBit) DebugNode(node) diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go index 5ee1d6dc..72d1ae3d 100644 --- a/pkg/yqlib/navigation_settings.go +++ b/pkg/yqlib/navigation_settings.go @@ -1,6 +1,7 @@ package yqlib import ( + "fmt" "strings" yaml "gopkg.in/yaml.v3" @@ -16,15 +17,14 @@ type VisitedNode struct { type NavigationSettings interface { FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool - ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error + ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool GetVisitedNodes() []*VisitedNode } type NavigationSettingsImpl struct { followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool - shouldVisit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error visitedNodes []*VisitedNode } @@ -51,26 +51,88 @@ func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, ta return ns.autoCreateMap(node, head, tail, pathStack) } -func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { - ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack}) - log.Debug("adding to visited nodes") - return ns.visit(node, head, tail, pathStack) -} - -func (ns *NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - if !ns.alreadyVisited(node) { - return ns.shouldVisit(node, head, tail, pathStack) - } else { - log.Debug("Skipping over %v as we have seen it already", node.Value) +func (ns *NavigationSettingsImpl) matchesNextPath(path string, candidate string) bool { + var prefixMatch = strings.TrimSuffix(path, "*") + if prefixMatch != path { + log.Debug("prefix match, %v", strings.HasPrefix(candidate, prefixMatch)) + return strings.HasPrefix(candidate, prefixMatch) } - return false + return candidate == path } -func (ns *NavigationSettingsImpl) alreadyVisited(node *yaml.Node) bool { +func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + // we should traverse aliases (if enabled), but not visit them :/ + if len(pathStack) == 0 { + return true + } + + if ns.alreadyVisited(pathStack) { + return false + } + + lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + + return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && ns.matchesNextPath(head, lastBit)) +} + +func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + // we should traverse aliases (if enabled), but not visit them :/ + if len(pathStack) == 0 { + return true + } + + if ns.alreadyVisited(pathStack) { + return false + } + + lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + // only visit aliases if its an exact match + return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && ns.matchesNextPath(head, lastBit)) + +} + +func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + if ns.shouldVisit(node, head, tail, pathStack) { + ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack}) + log.Debug("adding to visited nodes, %v", head) + return ns.visit(node, head, tail, pathStack) + } + return nil +} + +func (ns *NavigationSettingsImpl) alreadyVisited(pathStack []interface{}) bool { + log.Debug("looking for pathStack") + for _, val := range pathStack { + log.Debug("\t %v", val) + } for _, candidate := range ns.visitedNodes { - if candidate.Node.Value == node.Value { + candidatePathStack := candidate.PathStack + if patchStacksMatch(candidatePathStack, pathStack) { + log.Debug("paths match, already seen it") return true } + } + log.Debug("never seen it before!") return false } + +func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool { + log.Debug("checking against path") + for _, val := range path1 { + log.Debug("\t %v", val) + } + + if len(path1) != len(path2) { + return false + } + for index, p1Value := range path1 { + + p2Value := path2[index] + if p1Value != p2Value { + return false + } + } + return true + +} diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go index 08d12929..7439495f 100644 --- a/pkg/yqlib/read_navigation_strategy.go +++ b/pkg/yqlib/read_navigation_strategy.go @@ -1,8 +1,6 @@ package yqlib import ( - "strings" - yaml "gopkg.in/yaml.v3" ) @@ -15,21 +13,6 @@ func ReadNavigationSettings() NavigationSettings { autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { return false }, - shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - log.Debug("shouldVisit h: %v, actual: %v", head, node.Value) - if node.Value == "<<" { - log.Debug("its an alias, skip it") - // dont match alias keys, as we'll follow them instead - return false - } - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) - return strings.HasPrefix(node.Value, prefixMatch) - } - log.Debug("equals match, %v", node.Value == head) - return node.Value == head - }, visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { return nil }, diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go index 021b78ce..2fb122d7 100644 --- a/pkg/yqlib/update_navigation_strategy.go +++ b/pkg/yqlib/update_navigation_strategy.go @@ -1,8 +1,6 @@ package yqlib import ( - "strings" - yaml "gopkg.in/yaml.v3" ) @@ -15,15 +13,6 @@ func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings { autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { return true }, - shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) - return strings.HasPrefix(node.Value, prefixMatch) - } - log.Debug("equals match, %v", node.Value == head) - return node.Value == head - }, visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { log.Debug("going to update") DebugNode(node) diff --git a/test.yml b/test.yml index 804fbe36..6a6683f1 100644 --- a/test.yml +++ b/test.yml @@ -1,11 +1,3 @@ -a: 2 b: - hi: - c: things - d: something else - there: - c: more things - d: more something else - there2: - c: more things also - d: more something else also \ No newline at end of file + c: thing + d: another thing \ No newline at end of file From 707ad09ba51e8f6b590fb4122d53851b1089f6c0 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 27 Dec 2019 19:06:58 +1100 Subject: [PATCH 32/68] Refactor wip --- pkg/yqlib/data_navigator.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 23734111..84bac231 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -82,11 +82,13 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat log.Debug("should I traverse? %v", head) DebugNode(value) - if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) == true { + newPath := append(pathStack, contents[indexInMap].Value) + + if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPath) == true { log.Debug("yep!") traversedEntry = true contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) - return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) + return n.doTraverse(contents[indexInMap+1], head, tail, newPath) } else { log.Debug("nope not traversing") } From df52383ffb8848a787ad40fa1dc98a66f6714952 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 28 Dec 2019 10:51:54 +1300 Subject: [PATCH 33/68] Delete works! needs refactor --- commands_test.go | 2 +- pkg/yqlib/data_navigator.go | 7 ++--- pkg/yqlib/delete_navigation_settings.go | 41 +++++++++++++------------ pkg/yqlib/navigation_settings.go | 34 ++++++-------------- pkg/yqlib/path_parser.go | 23 ++++++++++++++ 5 files changed, 58 insertions(+), 49 deletions(-) diff --git a/commands_test.go b/commands_test.go index 33aeb4a8..9c07897e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1077,7 +1077,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } -func TestDeleteYaml(t *testing.T) { +func TestDeleteYamlCmd(t *testing.T) { content := `a: 2 b: c: things diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 84bac231..364985ed 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -82,13 +82,12 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat log.Debug("should I traverse? %v", head) DebugNode(value) - newPath := append(pathStack, contents[indexInMap].Value) - - if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPath) == true { + newPathStack := append(pathStack, contents[indexInMap].Value) + if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPathStack, contents[indexInMap].Value) == true { log.Debug("yep!") traversedEntry = true contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) - return n.doTraverse(contents[indexInMap+1], head, tail, newPath) + return n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) } else { log.Debug("nope not traversing") } diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go index dd2aabf8..b4392b4f 100644 --- a/pkg/yqlib/delete_navigation_settings.go +++ b/pkg/yqlib/delete_navigation_settings.go @@ -7,6 +7,7 @@ import ( ) func DeleteNavigationSettings(lastBit string) NavigationSettings { + parser := NewPathParser() return &NavigationSettingsImpl{ visitedNodes: []*VisitedNode{}, followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { @@ -16,7 +17,7 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings { return true }, visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { - log.Debug("need to find %v in here", lastBit) + log.Debug("need to find and delete %v in here", lastBit) DebugNode(node) if node.Kind == yaml.SequenceNode { newContent, errorDeleting := deleteFromArray(node.Content, lastBit) @@ -25,31 +26,33 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings { } node.Content = newContent } else if node.Kind == yaml.MappingNode { - // need to delete in reverse - otherwise the matching indexes - // become incorrect. - // matchingIndices := make([]int, 0) - // _, errorVisiting := n.visitMatchingEntries(node, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error { - // matchingIndices = append(matchingIndices, indexInMap) - // log.Debug("matchingIndices %v", indexInMap) - // return nil - // }) - // log.Debug("delete matching indices now") - // log.Debug("%v", matchingIndices) - // if errorVisiting != nil { - // return errorVisiting - // } - // for i := len(matchingIndices) - 1; i >= 0; i-- { - // indexToDelete := matchingIndices[i] - // log.Debug("deleting index %v, %v", indexToDelete, node.Content[indexToDelete].Value) - // node.Content = append(node.Content[:indexToDelete], node.Content[indexToDelete+2:]...) - // } + node.Content = deleteFromMap(parser, node.Content, pathStack, lastBit) } return nil }, } } +func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, lastBit string) []*yaml.Node { + newContents := make([]*yaml.Node, 0) + for index := 0; index < len(contents); index = index + 2 { + keyNode := contents[index] + valueNode := contents[index+1] + if pathParser.MatchesNextPathElement(keyNode, lastBit, []string{}, pathStack, keyNode.Value) == false { + log.Debug("adding node %v", keyNode.Value) + newContents = append(newContents, keyNode, valueNode) + } else { + log.Debug("skipping node %v", keyNode.Value) + } + } + return newContents +} func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) { + + if lastBit == "*" { + return make([]*yaml.Node, 0), nil + } + var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint if err != nil { return content, err diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go index 72d1ae3d..f2da767f 100644 --- a/pkg/yqlib/navigation_settings.go +++ b/pkg/yqlib/navigation_settings.go @@ -2,7 +2,6 @@ package yqlib import ( "fmt" - "strings" yaml "gopkg.in/yaml.v3" ) @@ -18,7 +17,7 @@ type NavigationSettings interface { FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error - ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool + ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool GetVisitedNodes() []*VisitedNode } @@ -29,16 +28,6 @@ type NavigationSettingsImpl struct { visitedNodes []*VisitedNode } -func matches(node *yaml.Node, head string) bool { - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch)) - return strings.HasPrefix(node.Value, prefixMatch) - } - log.Debug("equals match, %v", node.Value == head) - return node.Value == head -} - func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode { return ns.visitedNodes } @@ -51,16 +40,7 @@ func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, ta return ns.autoCreateMap(node, head, tail, pathStack) } -func (ns *NavigationSettingsImpl) matchesNextPath(path string, candidate string) bool { - var prefixMatch = strings.TrimSuffix(path, "*") - if prefixMatch != path { - log.Debug("prefix match, %v", strings.HasPrefix(candidate, prefixMatch)) - return strings.HasPrefix(candidate, prefixMatch) - } - return candidate == path -} - -func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { +func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool { // we should traverse aliases (if enabled), but not visit them :/ if len(pathStack) == 0 { return true @@ -70,9 +50,10 @@ func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, t return false } - lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + parser := NewPathParser() - return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && ns.matchesNextPath(head, lastBit)) + return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && + parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit)) } func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { @@ -86,8 +67,11 @@ func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail } lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + parser := NewPathParser() + // only visit aliases if its an exact match - return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && ns.matchesNextPath(head, lastBit)) + return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && + parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit)) } diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 83157054..8ae73cc0 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -1,7 +1,14 @@ package yqlib +import ( + "strings" + + yaml "gopkg.in/yaml.v3" +) + type PathParser interface { ParsePath(path string) []string + MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool } type pathParser struct{} @@ -10,6 +17,22 @@ func NewPathParser() PathParser { return &pathParser{} } +/** + * node: node that we may traverse/visit + * head: path element expression to match against + * tail: remaining path element expressions + * pathStack: stack of actual paths we've matched to get to node + * lastBit: actual value of this nodes 'key' or index. + */ +func (p *pathParser) MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool { + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + log.Debug("prefix match, %v", strings.HasPrefix(lastBit, prefixMatch)) + return strings.HasPrefix(lastBit, prefixMatch) + } + return lastBit == head +} + func (p *pathParser) ParsePath(path string) []string { if path == "" { return []string{} From 0652f67a91b5c9aa3759cf4b71c1e183fb495b09 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 28 Dec 2019 20:19:37 +1300 Subject: [PATCH 34/68] Refactored! --- pkg/yqlib/data_navigator.go | 16 +-- ...tings.go => delete_navigation_strategy.go} | 29 ++-- pkg/yqlib/lib.go | 14 +- pkg/yqlib/navigation_settings.go | 122 ----------------- pkg/yqlib/navigation_strategy.go | 124 ++++++++++++++++++ pkg/yqlib/path_parser.go | 15 +-- pkg/yqlib/read_navigation_strategy.go | 16 +-- pkg/yqlib/update_navigation_strategy.go | 13 +- yq.go | 6 +- 9 files changed, 177 insertions(+), 178 deletions(-) rename pkg/yqlib/{delete_navigation_settings.go => delete_navigation_strategy.go} (51%) delete mode 100644 pkg/yqlib/navigation_settings.go create mode 100644 pkg/yqlib/navigation_strategy.go diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 364985ed..d47e8011 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -12,12 +12,12 @@ type DataNavigator interface { } type navigator struct { - navigationSettings NavigationSettings + navigationStrategy NavigationStrategy } -func NewDataNavigator(navigationSettings NavigationSettings) DataNavigator { +func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator { return &navigator{ - navigationSettings: navigationSettings, + navigationStrategy: NavigationStrategy, } } @@ -38,7 +38,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat return n.recurse(value, path[0], path[1:], pathStack) } log.Debug("should I visit?") - return n.navigationSettings.Visit(value, head, path, pathStack) + return n.navigationStrategy.Visit(NodeContext{value, head, path, pathStack}) } func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { @@ -66,7 +66,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt case yaml.AliasNode: log.Debug("its an alias!") DebugNode(value.Alias) - if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true { + if n.navigationStrategy.FollowAlias(NodeContext{value, head, tail, pathStack}) == true { log.Debug("following the alias") return n.recurse(value.Alias, head, tail, pathStack) } @@ -83,7 +83,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat log.Debug("should I traverse? %v", head) DebugNode(value) newPathStack := append(pathStack, contents[indexInMap].Value) - if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPathStack, contents[indexInMap].Value) == true { + if n.navigationStrategy.ShouldTraverse(NodeContext{contents[indexInMap+1], head, tail, newPathStack}, contents[indexInMap].Value) == true { log.Debug("yep!") traversedEntry = true contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) @@ -98,7 +98,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat return errorVisiting } - if traversedEntry == true || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false { + if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NodeContext{value, head, tail, pathStack}) == false { return nil } @@ -136,7 +136,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st // if we don't find a match directly on this node first. errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) - if errorVisitedDirectEntries != nil || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false { + if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NodeContext{node, head, tail, pathStack}) == false { return errorVisitedDirectEntries } return n.visitAliases(contents, head, tail, pathStack, visit) diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_strategy.go similarity index 51% rename from pkg/yqlib/delete_navigation_settings.go rename to pkg/yqlib/delete_navigation_strategy.go index b4392b4f..8d221c4a 100644 --- a/pkg/yqlib/delete_navigation_settings.go +++ b/pkg/yqlib/delete_navigation_strategy.go @@ -6,38 +6,39 @@ import ( yaml "gopkg.in/yaml.v3" ) -func DeleteNavigationSettings(lastBit string) NavigationSettings { +func DeleteNavigationStrategy(pathElementToDelete string) NavigationStrategy { parser := NewPathParser() - return &NavigationSettingsImpl{ - visitedNodes: []*VisitedNode{}, - followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + return &NavigationStrategyImpl{ + visitedNodes: []*NodeContext{}, + followAlias: func(nodeContext NodeContext) bool { return false }, - autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + autoCreateMap: func(nodeContext NodeContext) bool { return true }, - visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { - log.Debug("need to find and delete %v in here", lastBit) + visit: func(nodeContext NodeContext) error { + node := nodeContext.Node + log.Debug("need to find and delete %v in here", pathElementToDelete) DebugNode(node) if node.Kind == yaml.SequenceNode { - newContent, errorDeleting := deleteFromArray(node.Content, lastBit) + newContent, errorDeleting := deleteFromArray(node.Content, pathElementToDelete) if errorDeleting != nil { return errorDeleting } node.Content = newContent } else if node.Kind == yaml.MappingNode { - node.Content = deleteFromMap(parser, node.Content, pathStack, lastBit) + node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete) } return nil }, } } -func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, lastBit string) []*yaml.Node { +func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete string) []*yaml.Node { newContents := make([]*yaml.Node, 0) for index := 0; index < len(contents); index = index + 2 { keyNode := contents[index] valueNode := contents[index+1] - if pathParser.MatchesNextPathElement(keyNode, lastBit, []string{}, pathStack, keyNode.Value) == false { + if pathParser.MatchesNextPathElement(NodeContext{keyNode, pathElementToDelete, []string{}, pathStack}, keyNode.Value) == false { log.Debug("adding node %v", keyNode.Value) newContents = append(newContents, keyNode, valueNode) } else { @@ -47,13 +48,13 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int return newContents } -func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) { +func deleteFromArray(content []*yaml.Node, pathElementToDelete string) ([]*yaml.Node, error) { - if lastBit == "*" { + if pathElementToDelete == "*" { return make([]*yaml.Node, 0), nil } - var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint + var index, err = strconv.ParseInt(pathElementToDelete, 10, 64) // nolint if err != nil { return content, err } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index c11773d1..74ed5517 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -57,7 +57,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { } type YqLib interface { - Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) + Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error New(path string) yaml.Node } @@ -73,12 +73,12 @@ func NewYqLib(l *logging.Logger) YqLib { } } -func (l *lib) Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) { +func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { var paths = l.parser.ParsePath(path) - navigationSettings := ReadNavigationSettings() - navigator := NewDataNavigator(navigationSettings) + NavigationStrategy := ReadNavigationStrategy() + navigator := NewDataNavigator(NavigationStrategy) error := navigator.Traverse(rootNode, paths) - return navigationSettings.GetVisitedNodes(), error + return NavigationStrategy.GetVisitedNodes(), error } @@ -93,12 +93,12 @@ func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { switch updateCommand.Command { case "update": var paths = l.parser.ParsePath(updateCommand.Path) - navigator := NewDataNavigator(UpdateNavigationSettings(updateCommand.Value)) + navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand.Value)) return navigator.Traverse(rootNode, paths) case "delete": var paths = l.parser.ParsePath(updateCommand.Path) lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1] - navigator := NewDataNavigator(DeleteNavigationSettings(lastBit)) + navigator := NewDataNavigator(DeleteNavigationStrategy(lastBit)) return navigator.Traverse(rootNode, newTail) default: return fmt.Errorf("Unknown command %v", updateCommand.Command) diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go deleted file mode 100644 index f2da767f..00000000 --- a/pkg/yqlib/navigation_settings.go +++ /dev/null @@ -1,122 +0,0 @@ -package yqlib - -import ( - "fmt" - - yaml "gopkg.in/yaml.v3" -) - -type VisitedNode struct { - Node *yaml.Node - Head string - Tail []string - PathStack []interface{} -} - -type NavigationSettings interface { - FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool - AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool - Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error - ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool - GetVisitedNodes() []*VisitedNode -} - -type NavigationSettingsImpl struct { - followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool - autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool - visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error - visitedNodes []*VisitedNode -} - -func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode { - return ns.visitedNodes -} - -func (ns *NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - return ns.followAlias(node, head, tail, pathStack) -} - -func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - return ns.autoCreateMap(node, head, tail, pathStack) -} - -func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool { - // we should traverse aliases (if enabled), but not visit them :/ - if len(pathStack) == 0 { - return true - } - - if ns.alreadyVisited(pathStack) { - return false - } - - parser := NewPathParser() - - return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && - parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit)) -} - -func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { - // we should traverse aliases (if enabled), but not visit them :/ - if len(pathStack) == 0 { - return true - } - - if ns.alreadyVisited(pathStack) { - return false - } - - lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) - parser := NewPathParser() - - // only visit aliases if its an exact match - return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && - parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit)) - -} - -func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { - if ns.shouldVisit(node, head, tail, pathStack) { - ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack}) - log.Debug("adding to visited nodes, %v", head) - return ns.visit(node, head, tail, pathStack) - } - return nil -} - -func (ns *NavigationSettingsImpl) alreadyVisited(pathStack []interface{}) bool { - log.Debug("looking for pathStack") - for _, val := range pathStack { - log.Debug("\t %v", val) - } - for _, candidate := range ns.visitedNodes { - candidatePathStack := candidate.PathStack - if patchStacksMatch(candidatePathStack, pathStack) { - log.Debug("paths match, already seen it") - return true - } - - } - log.Debug("never seen it before!") - return false -} - -func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool { - log.Debug("checking against path") - for _, val := range path1 { - log.Debug("\t %v", val) - } - - if len(path1) != len(path2) { - return false - } - for index, p1Value := range path1 { - - p2Value := path2[index] - if p1Value != p2Value { - return false - } - } - return true - -} diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go new file mode 100644 index 00000000..55f4acb7 --- /dev/null +++ b/pkg/yqlib/navigation_strategy.go @@ -0,0 +1,124 @@ +package yqlib + +import ( + "fmt" + + yaml "gopkg.in/yaml.v3" +) + +type NodeContext struct { + Node *yaml.Node + Head string + Tail []string + PathStack []interface{} +} + +type NavigationStrategy interface { + FollowAlias(nodeContext NodeContext) bool + AutoCreateMap(nodeContext NodeContext) bool + Visit(nodeContext NodeContext) error + // node key is the string value of the last element in the path stack + // we use it to match against the pathExpression in head. + ShouldTraverse(nodeContext NodeContext, nodeKey string) bool + GetVisitedNodes() []*NodeContext +} + +type NavigationStrategyImpl struct { + followAlias func(nodeContext NodeContext) bool + autoCreateMap func(nodeContext NodeContext) bool + visit func(nodeContext NodeContext) error + visitedNodes []*NodeContext +} + +func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext { + return ns.visitedNodes +} + +func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool { + return ns.followAlias(nodeContext) +} + +func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool { + return ns.autoCreateMap(nodeContext) +} + +func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool { + // we should traverse aliases (if enabled), but not visit them :/ + if len(nodeContext.PathStack) == 0 { + return true + } + + if ns.alreadyVisited(nodeContext.PathStack) { + return false + } + + parser := NewPathParser() + + return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" && + parser.MatchesNextPathElement(nodeContext, nodeKey)) +} + +func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { + // we should traverse aliases (if enabled), but not visit them :/ + pathStack := nodeContext.PathStack + if len(pathStack) == 0 { + return true + } + + if ns.alreadyVisited(pathStack) { + return false + } + + nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + parser := NewPathParser() + + // only visit aliases if its an exact match + return (nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" && + parser.MatchesNextPathElement(nodeContext, nodeKey)) +} + +func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { + if ns.shouldVisit(nodeContext) { + ns.visitedNodes = append(ns.visitedNodes, &nodeContext) + log.Debug("adding to visited nodes, %v", nodeContext.Head) + return ns.visit(nodeContext) + } + return nil +} + +func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { + log.Debug("looking for pathStack") + for _, val := range pathStack { + log.Debug("\t %v", val) + } + for _, candidate := range ns.visitedNodes { + candidatePathStack := candidate.PathStack + if patchStacksMatch(candidatePathStack, pathStack) { + log.Debug("paths match, already seen it") + return true + } + + } + log.Debug("never seen it before!") + return false +} + +func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool { + log.Debug("checking against path") + for _, val := range path1 { + log.Debug("\t %v", val) + } + + if len(path1) != len(path2) { + return false + } + for index, p1Value := range path1 { + + p2Value := path2[index] + if p1Value != p2Value { + return false + } + } + return true + +} diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 8ae73cc0..e4665ddb 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -2,13 +2,11 @@ package yqlib import ( "strings" - - yaml "gopkg.in/yaml.v3" ) type PathParser interface { ParsePath(path string) []string - MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool + MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool } type pathParser struct{} @@ -22,15 +20,16 @@ func NewPathParser() PathParser { * head: path element expression to match against * tail: remaining path element expressions * pathStack: stack of actual paths we've matched to get to node - * lastBit: actual value of this nodes 'key' or index. + * nodeKey: actual value of this nodes 'key' or index. */ -func (p *pathParser) MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool { +func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool { + head := nodeContext.Head var prefixMatch = strings.TrimSuffix(head, "*") if prefixMatch != head { - log.Debug("prefix match, %v", strings.HasPrefix(lastBit, prefixMatch)) - return strings.HasPrefix(lastBit, prefixMatch) + log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch)) + return strings.HasPrefix(nodeKey, prefixMatch) } - return lastBit == head + return nodeKey == head } func (p *pathParser) ParsePath(path string) []string { diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go index 7439495f..70ecfb7d 100644 --- a/pkg/yqlib/read_navigation_strategy.go +++ b/pkg/yqlib/read_navigation_strategy.go @@ -1,19 +1,15 @@ package yqlib -import ( - yaml "gopkg.in/yaml.v3" -) - -func ReadNavigationSettings() NavigationSettings { - return &NavigationSettingsImpl{ - visitedNodes: []*VisitedNode{}, - followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { +func ReadNavigationStrategy() NavigationStrategy { + return &NavigationStrategyImpl{ + visitedNodes: []*NodeContext{}, + followAlias: func(nodeContext NodeContext) bool { return true }, - autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + autoCreateMap: func(nodeContext NodeContext) bool { return false }, - visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + visit: func(nodeContext NodeContext) error { return nil }, } diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go index 2fb122d7..281a4033 100644 --- a/pkg/yqlib/update_navigation_strategy.go +++ b/pkg/yqlib/update_navigation_strategy.go @@ -4,16 +4,17 @@ import ( yaml "gopkg.in/yaml.v3" ) -func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings { - return &NavigationSettingsImpl{ - visitedNodes: []*VisitedNode{}, - followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { +func UpdateNavigationStrategy(changesToApply *yaml.Node) NavigationStrategy { + return &NavigationStrategyImpl{ + visitedNodes: []*NodeContext{}, + followAlias: func(nodeContext NodeContext) bool { return false }, - autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { + autoCreateMap: func(nodeContext NodeContext) bool { return true }, - visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { + visit: func(nodeContext NodeContext) error { + node := nodeContext.Node log.Debug("going to update") DebugNode(node) log.Debug("with") diff --git a/yq.go b/yq.go index 64614d09..7d1c72b9 100644 --- a/yq.go +++ b/yq.go @@ -257,7 +257,7 @@ func readProperty(cmd *cobra.Command, args []string) error { return errorParsingDocIndex } - var matchingNodes []*yqlib.VisitedNode + var matchingNodes []*yqlib.NodeContext var currentIndex = 0 var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error { @@ -292,7 +292,7 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { return nil } -func appendDocument(originalMatchingNodes []*yqlib.VisitedNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.VisitedNode, error) { +func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) { log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) yqlib.DebugNode(&dataBucket) if !updateAll && currentIndex != docIndexInt { @@ -337,7 +337,7 @@ func printValue(node *yaml.Node, cmd *cobra.Command) error { return nil } -func printResults(matchingNodes []*yqlib.VisitedNode, cmd *cobra.Command) error { +func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error { if len(matchingNodes) == 0 { log.Debug("no matching results, nothing to print") return nil From 8a6af1720d5e189e7853fd63d7173fbd9e689f30 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 30 Dec 2019 11:21:21 +1300 Subject: [PATCH 35/68] Fixed modify array issue! --- commands_test.go | 9 ++++++ pkg/yqlib/data_navigator.go | 32 +++++++++++++------- pkg/yqlib/delete_navigation_strategy.go | 2 +- pkg/yqlib/lib.go | 18 +++++++++++ pkg/yqlib/navigation_strategy.go | 40 +++++++++++++++++++------ yq.go | 22 ++------------ 6 files changed, 82 insertions(+), 41 deletions(-) diff --git a/commands_test.go b/commands_test.go index 9c07897e..a7c9f84f 100644 --- a/commands_test.go +++ b/commands_test.go @@ -103,6 +103,15 @@ func TestReadWithKeyAndValueCmd(t *testing.T) { test.AssertResult(t, "b.c: 2\n", result.Output) } +func TestReadArrayCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.e.1.name") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "b.e.1.name: sam\n", result.Output) +} + func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index d47e8011..e92c4c2a 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -37,8 +37,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat DebugNode(value) return n.recurse(value, path[0], path[1:], pathStack) } - log.Debug("should I visit?") - return n.navigationStrategy.Visit(NodeContext{value, head, path, pathStack}) + return n.navigationStrategy.Visit(NewNodeContext(value, head, path, pathStack)) } func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { @@ -66,7 +65,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt case yaml.AliasNode: log.Debug("its an alias!") DebugNode(value.Alias) - if n.navigationStrategy.FollowAlias(NodeContext{value, head, tail, pathStack}) == true { + if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) == true { log.Debug("following the alias") return n.recurse(value.Alias, head, tail, pathStack) } @@ -79,15 +78,21 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { traversedEntry := false errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error { - - log.Debug("should I traverse? %v", head) - DebugNode(value) + log.Debug("recurseMap: visitMatchingEntries") + n.navigationStrategy.DebugVisitedNodes() newPathStack := append(pathStack, contents[indexInMap].Value) - if n.navigationStrategy.ShouldTraverse(NodeContext{contents[indexInMap+1], head, tail, newPathStack}, contents[indexInMap].Value) == true { - log.Debug("yep!") + log.Debug("appended %v", contents[indexInMap].Value) + n.navigationStrategy.DebugVisitedNodes() + log.Debug("should I traverse? %v, %v", head, PathStackToString(newPathStack)) + DebugNode(value) + if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true { + log.Debug("recurseMap: Going to traverse") traversedEntry = true contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) - return n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) + errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) + log.Debug("recurseMap: Finished traversing") + n.navigationStrategy.DebugVisitedNodes() + return errorTraversing } else { log.Debug("nope not traversing") } @@ -98,7 +103,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat return errorVisiting } - if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NodeContext{value, head, tail, pathStack}) == false { + if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false { return nil } @@ -117,7 +122,9 @@ func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tai var contents = node.Content for index := 0; index < len(contents); index = index + 2 { content := contents[index] + log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) + n.navigationStrategy.DebugVisitedNodes() errorVisiting := visit(contents, index) if errorVisiting != nil { return errorVisiting @@ -136,7 +143,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st // if we don't find a match directly on this node first. errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) - if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NodeContext{node, head, tail, pathStack}) == false { + if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) == false { return errorVisitedDirectEntries } return n.visitAliases(contents, head, tail, pathStack, visit) @@ -220,5 +227,8 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, p return nil } value.Content[index] = n.getOrReplace(value.Content[index], guessKind(tail, value.Content[index].Kind)) + + // THERES SOMETHING WRONG HERE, ./yq read -p kv examples/sample.yaml b.e.1.* + // THERES SOMETHING WRONG HERE, ./yq read -p kv examples/sample.yaml b.e.1.name return n.doTraverse(value.Content[index], head, tail, append(pathStack, index)) } diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go index 8d221c4a..b4f95c3a 100644 --- a/pkg/yqlib/delete_navigation_strategy.go +++ b/pkg/yqlib/delete_navigation_strategy.go @@ -38,7 +38,7 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int for index := 0; index < len(contents); index = index + 2 { keyNode := contents[index] valueNode := contents[index+1] - if pathParser.MatchesNextPathElement(NodeContext{keyNode, pathElementToDelete, []string{}, pathStack}, keyNode.Value) == false { + if pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) == false { log.Debug("adding node %v", keyNode.Value) newContents = append(newContents, keyNode, valueNode) } else { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 74ed5517..2217a1b5 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "strconv" + "strings" logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" @@ -30,6 +31,23 @@ func DebugNode(value *yaml.Node) { } } +func PathStackToString(pathStack []interface{}) string { + var sb strings.Builder + for index, path := range pathStack { + switch path.(type) { + case int: + sb.WriteString(fmt.Sprintf("[%v]", path)) + default: + sb.WriteString(fmt.Sprintf("%v", path)) + } + + if index < len(pathStack)-1 { + sb.WriteString(".") + } + } + return sb.String() +} + func guessKind(tail []string, guess yaml.Kind) yaml.Kind { log.Debug("tail %v", tail) if len(tail) == 0 && guess == 0 { diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 55f4acb7..85dd5b21 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -13,6 +13,20 @@ type NodeContext struct { PathStack []interface{} } +func NewNodeContext(node *yaml.Node, head string, tail []string, pathStack []interface{}) NodeContext { + newTail := make([]string, len(tail)) + copy(newTail, tail) + + newPathStack := make([]interface{}, len(pathStack)) + copy(newPathStack, pathStack) + return NodeContext{ + Node: node, + Head: head, + Tail: newTail, + PathStack: newPathStack, + } +} + type NavigationStrategy interface { FollowAlias(nodeContext NodeContext) bool AutoCreateMap(nodeContext NodeContext) bool @@ -21,6 +35,7 @@ type NavigationStrategy interface { // we use it to match against the pathExpression in head. ShouldTraverse(nodeContext NodeContext, nodeKey string) bool GetVisitedNodes() []*NodeContext + DebugVisitedNodes() } type NavigationStrategyImpl struct { @@ -78,19 +93,29 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { } func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { + log.Debug("Visit?, %v, %v", nodeContext.Head, PathStackToString(nodeContext.PathStack)) + DebugNode(nodeContext.Node) if ns.shouldVisit(nodeContext) { + log.Debug("yep, visiting") + // pathStack array must be + // copied, as append() may sometimes reuse and modify the array ns.visitedNodes = append(ns.visitedNodes, &nodeContext) - log.Debug("adding to visited nodes, %v", nodeContext.Head) + ns.DebugVisitedNodes() return ns.visit(nodeContext) } + log.Debug("nope, skip it") return nil } -func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { - log.Debug("looking for pathStack") - for _, val := range pathStack { - log.Debug("\t %v", val) +func (ns *NavigationStrategyImpl) DebugVisitedNodes() { + log.Debug("%v", ns.visitedNodes) + for _, candidate := range ns.visitedNodes { + log.Debug(" - %v", PathStackToString(candidate.PathStack)) } +} + +func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { + log.Debug("checking already visited pathStack: %v", PathStackToString(pathStack)) for _, candidate := range ns.visitedNodes { candidatePathStack := candidate.PathStack if patchStacksMatch(candidatePathStack, pathStack) { @@ -104,10 +129,7 @@ func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { } func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool { - log.Debug("checking against path") - for _, val := range path1 { - log.Debug("\t %v", val) - } + log.Debug("checking against path: %v", PathStackToString(path1)) if len(path1) != len(path2) { return false diff --git a/yq.go b/yq.go index 7d1c72b9..59bb74a2 100644 --- a/yq.go +++ b/yq.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "os" "strconv" - "strings" "github.com/mikefarah/yq/v3/pkg/yqlib" @@ -306,23 +305,6 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml. return append(originalMatchingNodes, matchingNodes...), nil } -func pathToString(pathStack []interface{}) string { - var sb strings.Builder - for index, path := range pathStack { - switch path.(type) { - case int: - sb.WriteString(fmt.Sprintf("[%v]", path)) - default: - sb.WriteString(fmt.Sprintf("%v", path)) - } - - if index < len(pathStack)-1 { - sb.WriteString(".") - } - } - return sb.String() -} - func printValue(node *yaml.Node, cmd *cobra.Command) error { if node.Kind == yaml.ScalarNode { cmd.Print(node.Value) @@ -346,7 +328,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error for index, mappedDoc := range matchingNodes { switch printMode { case "k": - cmd.Print(pathToString(mappedDoc.PathStack)) + cmd.Print(yqlib.PathStackToString(mappedDoc.PathStack)) if index < len(matchingNodes)-1 { cmd.Print("\n") } @@ -354,7 +336,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error // put it into a node and print that. var parentNode = yaml.Node{Kind: yaml.MappingNode} parentNode.Content = make([]*yaml.Node, 2) - parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: pathToString(mappedDoc.PathStack)} + parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: yqlib.PathStackToString(mappedDoc.PathStack)} parentNode.Content[1] = mappedDoc.Node if err := printValue(&parentNode, cmd); err != nil { return err From 4dbdd4a805e6bdca53aeafc4b04fa92eebc39643 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 30 Dec 2019 16:51:07 +1300 Subject: [PATCH 36/68] Deep splat! --- commands_test.go | 18 ++++++++++++++++++ pkg/yqlib/data_navigator.go | 26 ++++++++++++++++---------- pkg/yqlib/lib.go | 2 +- pkg/yqlib/navigation_strategy.go | 5 ++--- pkg/yqlib/path_parser.go | 3 +++ 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/commands_test.go b/commands_test.go index a7c9f84f..5d0925da 100644 --- a/commands_test.go +++ b/commands_test.go @@ -112,6 +112,24 @@ func TestReadArrayCmd(t *testing.T) { test.AssertResult(t, "b.e.1.name: sam\n", result.Output) } +func TestReadDeepSplatCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b.c: 2 +b.d.[0]: 3 +b.d.[1]: 4 +b.d.[2]: 5 +b.e.[0].name: fred +b.e.[0].value: 3 +b.e.[1].name: sam +b.e.[1].value: 4 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index e92c4c2a..c17e7e5b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -31,13 +31,19 @@ func (n *navigator) Traverse(value *yaml.Node, path []string) error { return n.doTraverse(value, "", path, emptyArray) } -func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pathStack []interface{}) error { - if len(path) > 0 { - log.Debugf("diving into %v", path[0]) - DebugNode(value) - return n.recurse(value, path[0], path[1:], pathStack) +func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { + log.Debug("head %v", head) + DebugNode(value) + if head == "**" && value.Kind != yaml.ScalarNode { + return n.recurse(value, head, tail, pathStack) } - return n.navigationStrategy.Visit(NewNodeContext(value, head, path, pathStack)) + + if len(tail) > 0 { + log.Debugf("diving into %v", tail[0]) + DebugNode(value) + return n.recurse(value, tail[0], tail[1:], pathStack) + } + return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack)) } func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { @@ -56,8 +62,8 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: log.Debug("its a sequence of %v things!, %v", len(value.Content)) - if head == "*" { - return n.splatArray(value, tail, pathStack) + if head == "*" || head == "**" { + return n.splatArray(value, head, tail, pathStack) } else if head == "+" { return n.appendArray(value, tail, pathStack) } @@ -196,11 +202,11 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head str return nil } -func (n *navigator) splatArray(value *yaml.Node, tail []string, pathStack []interface{}) error { +func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { for index, childValue := range value.Content { log.Debug("processing") DebugNode(childValue) - head := fmt.Sprintf("%v", index) + // head = fmt.Sprintf("%v", index) childValue = n.getOrReplace(childValue, guessKind(tail, childValue.Kind)) var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) if err != nil { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 2217a1b5..6d851b91 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -61,7 +61,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } - if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { + if (tail[0] == "*" || tail[0] == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } if guess == yaml.AliasNode { diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 85dd5b21..91f942d1 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -74,13 +74,12 @@ func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKe } func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { - // we should traverse aliases (if enabled), but not visit them :/ pathStack := nodeContext.PathStack if len(pathStack) == 0 { return true } - if ns.alreadyVisited(pathStack) { + if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 { return false } @@ -108,7 +107,7 @@ func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { } func (ns *NavigationStrategyImpl) DebugVisitedNodes() { - log.Debug("%v", ns.visitedNodes) + log.Debug("Visited Nodes:") for _, candidate := range ns.visitedNodes { log.Debug(" - %v", PathStackToString(candidate.PathStack)) } diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index e4665ddb..7c1bd4ba 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -24,6 +24,9 @@ func NewPathParser() PathParser { */ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool { head := nodeContext.Head + if head == "**" || head == "*" { + return true + } var prefixMatch = strings.TrimSuffix(head, "*") if prefixMatch != head { log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch)) From 625cfdac75c6eb7955c9c050ab59d3b805196954 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 31 Dec 2019 15:21:39 +1300 Subject: [PATCH 37/68] wip; --- commands_test.go | 25 ++++++++++++++++++----- pkg/yqlib/data_navigator.go | 35 +++++++++++++++++--------------- pkg/yqlib/lib.go | 10 ++++----- pkg/yqlib/navigation_strategy.go | 3 +++ pkg/yqlib/path_parser.go | 8 ++++++++ 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/commands_test.go b/commands_test.go index 5d0925da..3e897419 100644 --- a/commands_test.go +++ b/commands_test.go @@ -130,6 +130,18 @@ b.e.[1].value: 4 test.AssertResult(t, expectedOutput, result.Output) } +func TestReadDeepSplatWithSuffixCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**.name") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b.e.[0].name: fred +b.e.[1].name: sam +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") @@ -408,15 +420,18 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { if result.Error == nil { t.Error("Expected command to fail due to missing arg") } - expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax` + expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '': strconv.ParseInt: parsing "x": invalid syntax` test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) + if result.Error == nil { + t.Error("Expected command to fail due to missing arg") + } + expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '[0].roles': strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_Error(t *testing.T) { @@ -469,8 +484,8 @@ func TestReadCmd_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) + expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for 'b.d.e': strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_Verbose(t *testing.T) { diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index c17e7e5b..835c924b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -1,9 +1,9 @@ package yqlib import ( - "fmt" "strconv" + errors "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" ) @@ -34,8 +34,15 @@ func (n *navigator) Traverse(value *yaml.Node, path []string) error { func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { log.Debug("head %v", head) DebugNode(value) + var errorDeepSplatting error if head == "**" && value.Kind != yaml.ScalarNode { - return n.recurse(value, head, tail, pathStack) + errorDeepSplatting = n.recurse(value, head, tail, pathStack) + // ignore errors here, we are deep splatting so we may accidently give a string key + // to an array sequence + if len(tail) > 0 { + n.recurse(value, tail[0], tail[1:], pathStack) + } + return errorDeepSplatting } if len(tail) > 0 { @@ -61,11 +68,11 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt log.Debug("its a map with %v entries", len(value.Content)/2) return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: - log.Debug("its a sequence of %v things!, %v", len(value.Content)) + log.Debug("its a sequence of %v things!", len(value.Content)) if head == "*" || head == "**" { return n.splatArray(value, head, tail, pathStack) } else if head == "+" { - return n.appendArray(value, tail, pathStack) + return n.appendArray(value, head, tail, pathStack) } return n.recurseArray(value, head, tail, pathStack) case yaml.AliasNode: @@ -94,7 +101,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true { log.Debug("recurseMap: Going to traverse") traversedEntry = true - contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) + // contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind)) errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) log.Debug("recurseMap: Finished traversing") n.navigationStrategy.DebugVisitedNodes() @@ -109,13 +116,13 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat return errorVisiting } - if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false { + if traversedEntry == true || head == "*" || head == "**" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false { return nil } mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} value.Content = append(value.Content, &mapEntryKey) - mapEntryValue := yaml.Node{Kind: guessKind(tail, 0)} + mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)} value.Content = append(value.Content, &mapEntryValue) log.Debug("adding new node %v", head) return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head)) @@ -206,8 +213,7 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat for index, childValue := range value.Content { log.Debug("processing") DebugNode(childValue) - // head = fmt.Sprintf("%v", index) - childValue = n.getOrReplace(childValue, guessKind(tail, childValue.Kind)) + childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind)) var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) if err != nil { return err @@ -216,25 +222,22 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat return nil } -func (n *navigator) appendArray(value *yaml.Node, tail []string, pathStack []interface{}) error { - var newNode = yaml.Node{Kind: guessKind(tail, 0)} +func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { + var newNode = yaml.Node{Kind: guessKind(head, tail, 0)} value.Content = append(value.Content, &newNode) log.Debug("appending a new node, %v", value.Content) - head := fmt.Sprintf("%v", len(value.Content)-1) return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1)) } func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { - return err + return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, PathStackToString(pathStack)) } if index >= int64(len(value.Content)) { return nil } - value.Content[index] = n.getOrReplace(value.Content[index], guessKind(tail, value.Content[index].Kind)) + value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind)) - // THERES SOMETHING WRONG HERE, ./yq read -p kv examples/sample.yaml b.e.1.* - // THERES SOMETHING WRONG HERE, ./yq read -p kv examples/sample.yaml b.e.1.name return n.doTraverse(value.Content[index], head, tail, append(pathStack, index)) } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 6d851b91..3f7737a6 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -48,7 +48,7 @@ func PathStackToString(pathStack []interface{}) string { return sb.String() } -func guessKind(tail []string, guess yaml.Kind) yaml.Kind { +func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind { log.Debug("tail %v", tail) if len(tail) == 0 && guess == 0 { log.Debug("end of path, must be a scalar") @@ -61,7 +61,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } - if (tail[0] == "*" || tail[0] == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { + if (tail[0] == "*" || tail[0] == "**" || head == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } if guess == yaml.AliasNode { @@ -69,8 +69,8 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { return guess } log.Debug("forcing a mapping node") - log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode) - log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode) + log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode) + log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode) return yaml.MappingNode } @@ -102,7 +102,7 @@ func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { func (l *lib) New(path string) yaml.Node { var paths = l.parser.ParsePath(path) - newNode := yaml.Node{Kind: guessKind(paths, 0)} + newNode := yaml.Node{Kind: guessKind("", paths, 0)} return newNode } diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 91f942d1..740d06f9 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -78,12 +78,15 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { if len(pathStack) == 0 { return true } + log.Debug("tail len %v", len(nodeContext.Tail)) + // SOMETHING HERE! if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 { return false } nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head) parser := NewPathParser() // only visit aliases if its an exact match diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 7c1bd4ba..b18ff5d8 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -1,6 +1,7 @@ package yqlib import ( + "strconv" "strings" ) @@ -27,6 +28,13 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str if head == "**" || head == "*" { return true } + if head == "+" { + log.Debug("head is +, nodeKey is %v", nodeKey) + var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint + if err == nil { + return true + } + } var prefixMatch = strings.TrimSuffix(head, "*") if prefixMatch != head { log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch)) From a065a47b37fcdcfd7ba07fe7d50b23306cfcee98 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 5 Jan 2020 16:22:18 +1300 Subject: [PATCH 38/68] Fixed tests --- commands_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands_test.go b/commands_test.go index 3e897419..dee6722b 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1113,7 +1113,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) { t.Error(result.Error) } expectedOutput := `b: - c: {} + c: thing d: another thing ` test.AssertResult(t, expectedOutput, result.Output) From 1aa5ec1d40d5f5cb7d5b443ed5e8aecffabcfc5e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 5 Jan 2020 17:14:14 +1300 Subject: [PATCH 39/68] Merge! wip --- commands_test.go | 31 ++++++++++----- examples/data1.yaml | 2 + examples/sample2.yaml | 10 +---- pkg/yqlib/lib.go | 14 ++++--- pkg/yqlib/update_navigation_strategy.go | 37 +++++++++--------- yq.go | 50 ++++++++++++++++++------- 6 files changed, 88 insertions(+), 56 deletions(-) diff --git a/commands_test.go b/commands_test.go index dee6722b..4f73533a 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1297,32 +1297,45 @@ something: else` test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) } -func xTestMergeCmd(t *testing.T) { +func TestMergeCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge examples/data1.yaml examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } expectedOutput := `a: simple -b: -- 1 -- 2 +b: [1, 2] +c: + test: 1 + toast: leave + tell: 1 + taco: cool +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestMergeNoAutoCreateCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "merge -c=false examples/data1.yaml examples/data2.yaml") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `a: simple +b: [1, 2] c: test: 1 ` test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeOverwriteCmd(t *testing.T) { +func TestMergeOverwriteCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge -c=false --overwrite examples/data1.yaml examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } expectedOutput := `a: other -b: -- 3 -- 4 +b: [3, 4] c: test: 1 ` diff --git a/examples/data1.yaml b/examples/data1.yaml index c9ad78b4..b0f017b1 100644 --- a/examples/data1.yaml +++ b/examples/data1.yaml @@ -1,2 +1,4 @@ a: simple b: [1, 2] +c: + test: 1 diff --git a/examples/sample2.yaml b/examples/sample2.yaml index e16e0b69..3df90f8b 100644 --- a/examples/sample2.yaml +++ b/examples/sample2.yaml @@ -1,10 +1,2 @@ -a: Easy! as one two three b: - c: things - d: whatever -things: - borg: snorg - thing1: - cat: 'fred' - thing2: - cat: 'sam' + c: things \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 3f7737a6..dbf19183 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -12,10 +12,12 @@ import ( var log = logging.MustGetLogger("yq") +// TODO: enumerate type UpdateCommand struct { - Command string - Path string - Value *yaml.Node + Command string + Path string + Value *yaml.Node + Overwrite bool } func DebugNode(value *yaml.Node) { @@ -76,7 +78,7 @@ func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind { type YqLib interface { Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) - Update(rootNode *yaml.Node, updateCommand UpdateCommand) error + Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error New(path string) yaml.Node } @@ -106,12 +108,12 @@ func (l *lib) New(path string) yaml.Node { return newNode } -func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { +func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error { log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) switch updateCommand.Command { case "update": var paths = l.parser.ParsePath(updateCommand.Path) - navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand.Value)) + navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate)) return navigator.Traverse(rootNode, paths) case "delete": var paths = l.parser.ParsePath(updateCommand.Path) diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go index 281a4033..8eea9069 100644 --- a/pkg/yqlib/update_navigation_strategy.go +++ b/pkg/yqlib/update_navigation_strategy.go @@ -1,32 +1,33 @@ package yqlib -import ( - yaml "gopkg.in/yaml.v3" -) - -func UpdateNavigationStrategy(changesToApply *yaml.Node) NavigationStrategy { +func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, followAlias: func(nodeContext NodeContext) bool { return false }, autoCreateMap: func(nodeContext NodeContext) bool { - return true + return autoCreate }, visit: func(nodeContext NodeContext) error { node := nodeContext.Node - log.Debug("going to update") - DebugNode(node) - log.Debug("with") - DebugNode(changesToApply) - node.Value = changesToApply.Value - node.Tag = changesToApply.Tag - node.Kind = changesToApply.Kind - node.Style = changesToApply.Style - node.Content = changesToApply.Content - node.HeadComment = changesToApply.HeadComment - node.LineComment = changesToApply.LineComment - node.FootComment = changesToApply.FootComment + changesToApply := updateCommand.Value + if updateCommand.Overwrite == true || node.Value == "" { + log.Debug("going to update") + DebugNode(node) + log.Debug("with") + DebugNode(changesToApply) + node.Value = changesToApply.Value + node.Tag = changesToApply.Tag + node.Kind = changesToApply.Kind + node.Style = changesToApply.Style + node.Content = changesToApply.Content + node.HeadComment = changesToApply.HeadComment + node.LineComment = changesToApply.LineComment + node.FootComment = changesToApply.FootComment + } else { + log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite) + } return nil }, } diff --git a/yq.go b/yq.go index 59bb74a2..22cace4c 100644 --- a/yq.go +++ b/yq.go @@ -22,6 +22,7 @@ var printMode = "v" var writeInplace = false var writeScript = "" var overwriteFlag = false +var autoCreateFlag = true var allowEmptyFlag = false var appendFlag = false var verbose = false @@ -235,7 +236,8 @@ Note that if you set both flags only overwrite will take effect. RunE: mergeProperties, } cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - // cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") + cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") + cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") // cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") @@ -256,10 +258,20 @@ func readProperty(cmd *cobra.Command, args []string) error { return errorParsingDocIndex } + matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt) + + if errorReadingStream != nil { + return errorReadingStream + } + + return printResults(matchingNodes, cmd) +} + +func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { var matchingNodes []*yqlib.NodeContext var currentIndex = 0 - var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error { + var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { for { var dataBucket yaml.Node errorReading := decoder.Decode(&dataBucket) @@ -275,12 +287,7 @@ func readProperty(cmd *cobra.Command, args []string) error { currentIndex = currentIndex + 1 } }) - - if errorReadingStream != nil { - return errorReadingStream - } - - return printResults(matchingNodes, cmd) + return matchingNodes, errorReadingStream } func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { @@ -419,7 +426,21 @@ func writeProperty(cmd *cobra.Command, args []string) error { func mergeProperties(cmd *cobra.Command, args []string) error { // first generate update commands from the file - return nil + var filesToMerge = args[1:] + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) + + for _, fileToMerge := range filesToMerge { + matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) + if errorProcessingFile != nil { + return errorProcessingFile + } + for _, matchingNode := range matchingNodes { + mergePath := yqlib.PathStackToString(matchingNode.PathStack) + updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) + } + } + + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) } func newProperty(cmd *cobra.Command, args []string) error { @@ -431,7 +452,7 @@ func newProperty(cmd *cobra.Command, args []string) error { for _, updateCommand := range updateCommands { - errorUpdating := lib.Update(&newNode, updateCommand) + errorUpdating := lib.Update(&newNode, updateCommand, true) if errorUpdating != nil { return errorUpdating @@ -474,7 +495,7 @@ func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucke newNode := lib.New(updateCommand.Path) dataBucket.Content[0] = &newNode - errorUpdating := lib.Update(dataBucket, updateCommand) + errorUpdating := lib.Update(dataBucket, updateCommand, true) if errorUpdating != nil { return errorUpdating } @@ -502,7 +523,8 @@ func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io if updateAll || currentIndex == docIndexInt { log.Debugf("Updating doc %v", currentIndex) for _, updateCommand := range updateCommands { - errorUpdating := lib.Update(dataBucket, updateCommand) + log.Debugf("Processing update to Path %v", updateCommand.Path) + errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) if errorUpdating != nil { return errorUpdating } @@ -605,7 +627,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) log.Debugf("Read write commands file '%v'", parsedCommands) for index := range parsedCommands { parsedCommand := parsedCommands[index] - updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value} + updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} updateCommands = append(updateCommands, updateCommand) } @@ -617,7 +639,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) log.Debug("args %v", args) log.Debug("path %v", args[expectedArgs-2]) log.Debug("Value %v", args[expectedArgs-1]) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag)} + updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true} } return updateCommands, nil } From 1f7f1b0defeb8e10fafb44657387cc556025d9e9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 5 Jan 2020 17:28:24 +1300 Subject: [PATCH 40/68] Merge arrays! --- Upgrade Notes | 10 ++++++++++ commands_test.go | 18 +++++------------- examples/sample_array_2.yaml | 3 ++- pkg/yqlib/lib.go | 11 ++++++++++- yq.go | 4 ++-- 5 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 Upgrade Notes diff --git a/Upgrade Notes b/Upgrade Notes new file mode 100644 index 00000000..153ebf50 --- /dev/null +++ b/Upgrade Notes @@ -0,0 +1,10 @@ +# New Features + - Keeps comments and formatting (e.g. inline arrays)! + - Handles anchors! + - Can specify yaml tags (e.g. !!int) + +# Update scripts file format has changed + +# Merge command +- autocreates missing entries in target by default, new flag to turn that off. + diff --git a/commands_test.go b/commands_test.go index 4f73533a..91b9cffe 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1342,34 +1342,26 @@ c: test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeAppendCmd(t *testing.T) { +func TestMergeAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --append examples/data1.yaml examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } expectedOutput := `a: simple -b: -- 1 -- 2 -- 3 -- 4 +b: [1, 2, 3, 4] c: test: 1 ` test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeArraysCmd(t *testing.T) { +func TestMergeArraysCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml") if result.Error != nil { t.Error(result.Error) } - expectedOutput := `- 1 -- 2 -- 3 -- 4 -- 5 + expectedOutput := `[1, 2, 3, 4, 5] ` test.AssertResult(t, expectedOutput, result.Output) } diff --git a/examples/sample_array_2.yaml b/examples/sample_array_2.yaml index b32037f9..e83ee7a1 100644 --- a/examples/sample_array_2.yaml +++ b/examples/sample_array_2.yaml @@ -1 +1,2 @@ -[4,5] \ No newline at end of file +- 4 +- 5 \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index dbf19183..18689815 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -34,11 +34,20 @@ func DebugNode(value *yaml.Node) { } func PathStackToString(pathStack []interface{}) string { + return MergePathStackToString(pathStack, false) +} + +func MergePathStackToString(pathStack []interface{}, appendArrays bool) string { var sb strings.Builder for index, path := range pathStack { switch path.(type) { case int: - sb.WriteString(fmt.Sprintf("[%v]", path)) + if appendArrays { + sb.WriteString("[+]") + } else { + sb.WriteString(fmt.Sprintf("[%v]", path)) + } + default: sb.WriteString(fmt.Sprintf("%v", path)) } diff --git a/yq.go b/yq.go index 22cace4c..a1333791 100644 --- a/yq.go +++ b/yq.go @@ -238,7 +238,7 @@ Note that if you set both flags only overwrite will take effect. cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") - // cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") + cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") return cmdMerge @@ -435,7 +435,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error { return errorProcessingFile } for _, matchingNode := range matchingNodes { - mergePath := yqlib.PathStackToString(matchingNode.PathStack) + mergePath := yqlib.MergePathStackToString(matchingNode.PathStack, appendFlag) updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) } } From 690da9ee7487e35bdf6a39305319558b00a0a2da Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 6 Jan 2020 10:12:30 +1300 Subject: [PATCH 41/68] Fixed merge new array --- Upgrade Notes | 5 +++++ commands_test.go | 31 ++++++++++++++++++++++--------- examples/data1.yaml | 2 +- examples/data2.yaml | 2 +- pkg/yqlib/data_navigator.go | 6 ++++++ 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Upgrade Notes b/Upgrade Notes index 153ebf50..8567039b 100644 --- a/Upgrade Notes +++ b/Upgrade Notes @@ -1,3 +1,8 @@ +# Update doco / notes + - --autocreate=false to turn off default flags + - add comments to test yaml to ensure they are kept on updating. + - update built in command notes to includes quotes around path args. + # New Features - Keeps comments and formatting (e.g. inline arrays)! - Handles anchors! diff --git a/commands_test.go b/commands_test.go index 91b9cffe..6e4c5271 100644 --- a/commands_test.go +++ b/commands_test.go @@ -807,6 +807,18 @@ func TestNewCmd(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } +func TestNewArrayCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "new b[0] 3") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b: +- 3 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestNewCmd_Error(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "new b.c") @@ -1334,7 +1346,7 @@ func TestMergeOverwriteCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: other + expectedOutput := `a: other # better than the original b: [3, 4] c: test: 1 @@ -1348,7 +1360,7 @@ func TestMergeAppendCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: simple + expectedOutput := `a: simple # just the best b: [1, 2, 3, 4] c: test: 1 @@ -1366,26 +1378,27 @@ func TestMergeArraysCmd(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeCmd_Multi(t *testing.T) { +func TestMergeCmd_Multi(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data1.yaml") if result.Error != nil { t.Error(result.Error) } expectedOutput := `a: Easy! as one two three --- -a: other another: document: here +a: simple # just the best b: -- 3 -- 4 +- 1 +- 2 c: test: 1 --- - 1 -- 2` - test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +- 2 +` + test.AssertResult(t, expectedOutput, result.Output) } func xTestMergeYamlMultiAllCmd(t *testing.T) { diff --git a/examples/data1.yaml b/examples/data1.yaml index b0f017b1..6b87c47f 100644 --- a/examples/data1.yaml +++ b/examples/data1.yaml @@ -1,4 +1,4 @@ -a: simple +a: simple # just the best b: [1, 2] c: test: 1 diff --git a/examples/data2.yaml b/examples/data2.yaml index 93764e94..07088cc1 100644 --- a/examples/data2.yaml +++ b/examples/data2.yaml @@ -1,4 +1,4 @@ -a: other +a: other # better than the original b: [3, 4] c: toast: leave diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 835c924b..86a89a21 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -234,7 +234,13 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, p if err != nil { return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, PathStackToString(pathStack)) } + + for int64(len(value.Content)) <= index { + value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)}) + } + if index >= int64(len(value.Content)) { + log.Debug("index longer than array length, aborting!") return nil } value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind)) From e3f4eedd5183b08ab39e24d6b26e90bc7c1de289 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 6 Jan 2020 10:12:38 +1300 Subject: [PATCH 42/68] Fixed merge new array --- pkg/yqlib/data_navigator.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 86a89a21..2a0ec80c 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -239,10 +239,6 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, p value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)}) } - if index >= int64(len(value.Content)) { - log.Debug("index longer than array length, aborting!") - return nil - } value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind)) return n.doTraverse(value.Content[index], head, tail, append(pathStack, index)) From d8c29b26c1b5d6cf82e5e9634a00b04f8b733ef5 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 6 Jan 2020 16:22:24 +1300 Subject: [PATCH 43/68] Merge can allow empty merges! --- commands_test.go | 119 +++++++++++++++++------------------------------ yq.go | 52 +++------------------ 2 files changed, 49 insertions(+), 122 deletions(-) diff --git a/commands_test.go b/commands_test.go index 6e4c5271..c310d59e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -243,7 +243,7 @@ func TestReadBadDocumentIndexCmd(t *testing.T) { if result.Error == nil { t.Error("Expected command to fail due to invalid path") } - expectedOutput := `asked to process document index 1 but there are only 1 document(s)` + expectedOutput := `Could not process document index 1 as there are only 1 document(s)` test.AssertResult(t, expectedOutput, result.Error.Error()) } @@ -829,18 +829,6 @@ func TestNewCmd_Error(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -func TestNewCmd_Verbose(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "-v new b.c 3") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} - func TestWriteCmd(t *testing.T) { content := `b: c: 3 @@ -998,24 +986,6 @@ func TestWriteCmd_ErrorUnreadableFile(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -func TestWriteCmd_Verbose(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("-v write %s b.c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} - func TestWriteCmd_Inplace(t *testing.T) { content := `b: c: 3 @@ -1193,7 +1163,7 @@ b: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.hi[*].thing", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename)) if result.Error != nil { t.Error(result.Error) } @@ -1224,7 +1194,7 @@ b: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.there*.c", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename)) if result.Error != nil { t.Error(result.Error) } @@ -1315,7 +1285,7 @@ func TestMergeCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: simple + expectedOutput := `a: simple # just the best b: [1, 2] c: test: 1 @@ -1332,7 +1302,7 @@ func TestMergeNoAutoCreateCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: simple + expectedOutput := `a: simple # just the best b: [1, 2] c: test: 1 @@ -1401,7 +1371,7 @@ c: test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeYamlMultiAllCmd(t *testing.T) { +func TestMergeYamlMultiAllCmd(t *testing.T) { content := `b: c: 3 apples: green @@ -1420,17 +1390,18 @@ something: good` if result.Error != nil { t.Error(result.Error) } - expectedOutput := `apples: green -b: + expectedOutput := `b: c: 3 +apples: green something: good --- +something: else apples: red -something: else` - test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +` + test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeYamlMultiAllOverwriteCmd(t *testing.T) { +func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) { content := `b: c: 3 apples: green @@ -1449,17 +1420,18 @@ something: good` if result.Error != nil { t.Error(result.Error) } - expectedOutput := `apples: red -b: + expectedOutput := `b: c: 3 +apples: red something: good --- +something: good apples: red -something: good` - test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +` + test.AssertResult(t, expectedOutput, result.Output) } -func xTestMergeCmd_Error(t *testing.T) { +func TestMergeCmd_Error(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge examples/data1.yaml") if result.Error == nil { @@ -1469,7 +1441,7 @@ func xTestMergeCmd_Error(t *testing.T) { test.AssertResult(t, expectedOutput, result.Error.Error()) } -func xTestMergeCmd_ErrorUnreadableFile(t *testing.T) { +func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge examples/data1.yaml fake-unknown") if result.Error == nil { @@ -1477,30 +1449,14 @@ func xTestMergeCmd_ErrorUnreadableFile(t *testing.T) { } var expectedOutput string if runtime.GOOS == "windows" { - expectedOutput = `Error updating document at index 0: open fake-unknown: The system cannot find the file specified.` + expectedOutput = `open fake-unknown: The system cannot find the file specified.` } else { - expectedOutput = `Error updating document at index 0: open fake-unknown: no such file or directory` + expectedOutput = `open fake-unknown: no such file or directory` } test.AssertResult(t, expectedOutput, result.Error.Error()) } -func xTestMergeCmd_Verbose(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "-v merge examples/data1.yaml examples/data2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: simple -b: -- 1 -- 2 -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func xTestMergeCmd_Inplace(t *testing.T) { +func TestMergeCmd_Inplace(t *testing.T) { filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml")) err := os.Chmod(filename, os.FileMode(int(0666))) if err != nil { @@ -1515,26 +1471,35 @@ func xTestMergeCmd_Inplace(t *testing.T) { } info, _ := os.Stat(filename) gotOutput := test.ReadTempYamlFile(filename) - expectedOutput := `a: simple -b: -- 1 -- 2 + expectedOutput := `a: simple # just the best +b: [1, 2] c: - test: 1` - test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) + test: 1 + toast: leave + tell: 1 + taco: cool +` + test.AssertResult(t, expectedOutput, gotOutput) test.AssertResult(t, os.FileMode(int(0666)), info.Mode()) } -func xTestMergeAllowEmptyCmd(t *testing.T) { +func TestMergeAllowEmptyCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --allow-empty examples/data1.yaml examples/empty.yaml") if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: simple -b: -- 1 -- 2 + expectedOutput := `a: simple # just the best +b: [1, 2] +c: + test: 1 ` test.AssertResult(t, expectedOutput, result.Output) } + +func TestMergeDontAllowEmptyCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "merge examples/data1.yaml examples/empty.yaml") + expectedOutput := `Could not process document index 0 as there are only 0 document(s)` + test.AssertResult(t, expectedOutput, result.Error.Error()) +} diff --git a/yq.go b/yq.go index a1333791..adfd05f6 100644 --- a/yq.go +++ b/yq.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "strconv" + "strings" "github.com/mikefarah/yq/v3/pkg/yqlib" @@ -239,7 +240,7 @@ Note that if you set both flags only overwrite will take effect. cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") - // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") + cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") return cmdMerge } @@ -293,7 +294,7 @@ func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { log.Debugf("done %v / %v", currentIndex, docIndexInt) if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) + return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex) } return nil } @@ -425,13 +426,16 @@ func writeProperty(cmd *cobra.Command, args []string) error { } func mergeProperties(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("Must provide at least 2 yaml files") + } // first generate update commands from the file var filesToMerge = args[1:] var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) for _, fileToMerge := range filesToMerge { matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) - if errorProcessingFile != nil { + if errorProcessingFile != nil && (allowEmptyFlag == false || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { return errorProcessingFile } for _, matchingNode := range matchingNodes { @@ -569,48 +573,6 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) return readStream(inputFile, mapYamlDecoder(updateData, encoder)) } -// func mergeProperties(cmd *cobra.Command, args []string) error { -// if len(args) < 2 { -// return errors.New("Must provide at least 2 yaml files") -// } -// var input = args[0] -// var filesToMerge = args[1:] -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { -// if updateAll || currentIndex == docIndexInt { -// log.Debugf("Merging doc %v", currentIndex) -// var mergedData map[interface{}]interface{} -// // merge only works for maps, so put everything in a temporary -// // map -// var mapDataBucket = make(map[interface{}]interface{}) -// mapDataBucket["root"] = dataBucket -// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { -// return nil, err -// } -// for _, f := range filesToMerge { -// var fileToMerge interface{} -// if err := readData(f, 0, &fileToMerge); err != nil { -// if allowEmptyFlag && err == io.EOF { -// continue -// } -// return nil, err -// } -// mapDataBucket["root"] = fileToMerge -// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { -// return nil, err -// } -// } -// return mergedData["root"], nil -// } -// return dataBucket, nil -// } -// return readAndUpdate(cmd.OutOrStdout(), input, updateData) -// } - type updateCommandParsed struct { Command string Path string From 38d35185bc09d0046cf440ab380511cda79d8b77 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 6 Jan 2020 16:27:00 +1300 Subject: [PATCH 44/68] Can overwrite and append with merge --- commands_test.go | 15 +++++++++++++++ yq.go | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/commands_test.go b/commands_test.go index c310d59e..9f600f38 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1337,6 +1337,21 @@ c: ` test.AssertResult(t, expectedOutput, result.Output) } + +func TestMergeOverwriteAndAppendCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite examples/data1.yaml examples/data2.yaml") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `a: other # better than the original +b: [1, 2, 3, 4] +c: + test: 1 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestMergeArraysCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml") diff --git a/yq.go b/yq.go index adfd05f6..053b4a98 100644 --- a/yq.go +++ b/yq.go @@ -231,8 +231,6 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. - -Note that if you set both flags only overwrite will take effect. `, RunE: mergeProperties, } From 52eef67e37323eabe36801632df56d6c4b6c8d7e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 9 Jan 2020 08:17:56 +1100 Subject: [PATCH 45/68] more tests, some refactoring --- .gitignore | 1 + Upgrade Notes | 2 +- pkg/yqlib/data_navigator_test.go | 396 ------------------------------- pkg/yqlib/lib.go | 2 +- pkg/yqlib/lib_test.go | 264 ++++++++++----------- pkg/yqlib/path_parser_test.go | 53 ++++- pkg/yqlib/value_parser.go | 12 +- pkg/yqlib/value_parser_test.go | 29 ++- scripts/coverage.sh | 4 +- scripts/test.sh | 2 +- yq.go | 5 +- 11 files changed, 215 insertions(+), 555 deletions(-) diff --git a/.gitignore b/.gitignore index 69118014..c9064038 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ _cgo_export.* _testmain.go coverage.out +coverage.html *.exe *.test *.prof diff --git a/Upgrade Notes b/Upgrade Notes index 8567039b..1b7bb513 100644 --- a/Upgrade Notes +++ b/Upgrade Notes @@ -6,7 +6,7 @@ # New Features - Keeps comments and formatting (e.g. inline arrays)! - Handles anchors! - - Can specify yaml tags (e.g. !!int) + - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead. # Update scripts file format has changed diff --git a/pkg/yqlib/data_navigator_test.go b/pkg/yqlib/data_navigator_test.go index d537d665..88c44e97 100644 --- a/pkg/yqlib/data_navigator_test.go +++ b/pkg/yqlib/data_navigator_test.go @@ -1,397 +1 @@ package yqlib - -// import ( -// "fmt" -// "sort" -// "testing" - -// "github.com/mikefarah/yq/v2/test" -// logging "gopkg.in/op/go-logging.v1" -// ) - -// func TestDataNavigator(t *testing.T) { -// var log = logging.MustGetLogger("yq") -// subject := NewDataNavigator(log) - -// t.Run("TestReadMap_simple", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// c: 2 -// `) -// got, _ := subject.ReadChildValue(data, []string{"b", "c"}) -// test.AssertResult(t, 2, got) -// }) - -// t.Run("TestReadMap_numberKey", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// 200: things -// `) -// got, _ := subject.ReadChildValue(data, []string{"200"}) -// test.AssertResult(t, "things", got) -// }) - -// t.Run("TestReadMap_splat", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// mapSplat: -// item1: things -// item2: whatever -// otherThing: cat -// `) -// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"}) -// test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res)) -// }) - -// t.Run("TestReadMap_prefixSplat", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// mapSplat: -// item1: things -// item2: whatever -// otherThing: cat -// `) -// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"}) -// test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res)) -// }) - -// t.Run("TestReadMap_deep_splat", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// mapSplatDeep: -// item1: -// cats: bananas -// item2: -// cats: apples -// `) - -// res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"}) -// result := res.([]interface{}) -// var actual = []string{result[0].(string), result[1].(string)} -// sort.Strings(actual) -// test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual)) -// }) - -// t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// c: 2 -// `) -// got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"}) -// test.AssertResult(t, nil, got) -// }) - -// t.Run("TestReadMap_recurse_against_string", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// a: cat -// `) -// got, _ := subject.ReadChildValue(data, []string{"a", "b"}) -// test.AssertResult(t, nil, got) -// }) - -// t.Run("TestReadMap_with_array", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// d: -// - 3 -// - 4 -// `) -// got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"}) -// test.AssertResult(t, 4, got) -// }) - -// t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// d: -// - 3 -// - 4 -// `) -// _, err := subject.ReadChildValue(data, []string{"b", "d", "x"}) -// if err == nil { -// t.Fatal("Expected error due to invalid path") -// } -// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` -// test.AssertResult(t, expectedOutput, err.Error()) -// }) - -// t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// d: -// e: -// - 3 -// - 4 -// f: -// - 1 -// - 2 -// `) -// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"}) -// if err == nil { -// t.Fatal("Expected error due to invalid path") -// } -// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` -// test.AssertResult(t, expectedOutput, err.Error()) -// }) - -// t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// d: -// - names: -// - fred -// - smith -// - names: -// - sam -// - bo -// `) -// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"}) -// if err == nil { -// t.Fatal("Expected error due to invalid path") -// } -// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` -// test.AssertResult(t, expectedOutput, err.Error()) -// }) - -// t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// d: -// - 3 -// - 4 -// `) -// got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"}) -// test.AssertResult(t, nil, got) -// }) - -// t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) { -// var data = test.ParseData(` -// --- -// b: -// d: -// - 3 -// - 4 -// `) -// got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"}) -// test.AssertResult(t, nil, got) -// }) - -// t.Run("TestReadMap_with_array_splat", func(t *testing.T) { -// var data = test.ParseData(` -// e: -// - -// name: Fred -// thing: cat -// - -// name: Sam -// thing: dog -// `) -// got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"}) -// test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got)) -// }) - -// t.Run("TestWrite_really_simple", func(t *testing.T) { -// var data = test.ParseData(` -// b: 2 -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b"}, "4") -// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_simple", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// c: 2 -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4") -// test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_new", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// c: 2 -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4") -// test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_new_deep", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// c: 2 -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4") -// test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_array", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// - aa -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb") - -// test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_new_array", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// c: 2 -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4") -// test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_new_array_deep", func(t *testing.T) { -// var data = test.ParseData(` -// a: apple -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4") -// test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_new_map_array_deep", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// c: 2 -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4") -// test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_add_to_array", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// - aa -// `) - -// updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb") -// test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWrite_with_no_tail", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// c: 2 -// `) -// updated := subject.UpdatedChildValue(data, []string{"b"}, "4") - -// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) -// }) - -// t.Run("TestWriteMap_no_paths", func(t *testing.T) { -// var data = test.ParseData(` -// b: 5 -// `) -// var new = test.ParseData(` -// c: 4 -// `) -// result := subject.UpdatedChildValue(data, []string{}, new) -// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestWriteArray_no_paths", func(t *testing.T) { -// var data = make([]interface{}, 1) -// data[0] = "mike" -// var new = test.ParseData(` -// c: 4 -// `) -// result := subject.UpdatedChildValue(data, []string{}, new) -// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestDelete_MapItem", func(t *testing.T) { -// var data = test.ParseData(` -// a: 123 -// b: 456 -// `) -// var expected = test.ParseData(` -// b: 456 -// `) - -// result, _ := subject.DeleteChildValue(data, []string{"a"}) -// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) -// }) - -// // Ensure deleting an index into a string does nothing -// t.Run("TestDelete_index_to_string", func(t *testing.T) { -// var data = test.ParseData(` -// a: mystring -// `) -// result, _ := subject.DeleteChildValue(data, []string{"a", "0"}) -// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestDelete_list_index", func(t *testing.T) { -// var data = test.ParseData(` -// a: [3, 4] -// `) -// var expected = test.ParseData(` -// a: [3] -// `) -// result, _ := subject.DeleteChildValue(data, []string{"a", "1"}) -// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) { -// var data = test.ParseData(` -// a: [3, 4] -// `) -// result, _ := subject.DeleteChildValue(data, []string{"a", "5"}) -// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) { -// var data = test.ParseData(` -// a: [3, 4] -// `) -// result, _ := subject.DeleteChildValue(data, []string{"a", "2"}) -// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestDelete_no_paths", func(t *testing.T) { -// var data = test.ParseData(` -// a: [3, 4] -// b: -// - name: test -// `) -// result, _ := subject.DeleteChildValue(data, []string{}) -// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -// }) - -// t.Run("TestDelete_array_map_item", func(t *testing.T) { -// var data = test.ParseData(` -// b: -// - name: fred -// value: blah -// - name: john -// value: test -// `) -// var expected = test.ParseData(` -// b: -// - value: blah -// - name: john -// value: test -// `) -// result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"}) -// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) -// }) -// } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 18689815..1675e60f 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -96,7 +96,7 @@ type lib struct { parser PathParser } -func NewYqLib(l *logging.Logger) YqLib { +func NewYqLib() YqLib { return &lib{ parser: NewPathParser(), } diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index b9c3e4de..77411470 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -1,166 +1,162 @@ package yqlib import ( - "fmt" "testing" - - "github.com/mikefarah/yq/v3/test" - logging "gopkg.in/op/go-logging.v1" ) func TestLib(t *testing.T) { - var log = logging.MustGetLogger("yq") - subject := NewYqLib(log) + // var log = logging.MustGetLogger("yq") + // subject := NewYqLib(log) - t.Run("TestReadPath", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - 2: c -`) + // t.Run("TestReadPath", func(t *testing.T) { + // var data = test.ParseData(` + // --- + // b: + // 2: c + // `) - got, _ := subject.ReadPath(data, "b.2") - test.AssertResult(t, `c`, got) - }) + // got, _ := subject.ReadPath(data, "b.2") + // test.AssertResult(t, `c`, got) + // }) - t.Run("TestReadPath_WithError", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - - c -`) + // t.Run("TestReadPath_WithError", func(t *testing.T) { + // var data = test.ParseData(` + // --- + // b: + // - c + // `) - _, err := subject.ReadPath(data, "b.[a]") - if err == nil { - t.Fatal("Expected error due to invalid path") - } - }) + // _, err := subject.ReadPath(data, "b.[a]") + // if err == nil { + // t.Fatal("Expected error due to invalid path") + // } + // }) - t.Run("TestWritePath", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - 2: c -`) + // t.Run("TestWritePath", func(t *testing.T) { + // var data = test.ParseData(` + // --- + // b: + // 2: c + // `) - got := subject.WritePath(data, "b.3", "a") - test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got)) - }) + // got := subject.WritePath(data, "b.3", "a") + // test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got)) + // }) - t.Run("TestPrefixPath", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - 2: c -`) + // t.Run("TestPrefixPath", func(t *testing.T) { + // var data = test.ParseData(` + // --- + // b: + // 2: c + // `) - got := subject.PrefixPath(data, "a.d") - test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got)) - }) + // got := subject.PrefixPath(data, "a.d") + // test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got)) + // }) - t.Run("TestDeletePath", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - 2: c - 3: a -`) + // t.Run("TestDeletePath", func(t *testing.T) { + // var data = test.ParseData(` + // --- + // b: + // 2: c + // 3: a + // `) - got, _ := subject.DeletePath(data, "b.2") - test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) - }) + // got, _ := subject.DeletePath(data, "b.2") + // test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) + // }) - t.Run("TestDeletePath_WithError", func(t *testing.T) { - var data = test.ParseData(` ---- -b: - - c -`) + // t.Run("TestDeletePath_WithError", func(t *testing.T) { + // var data = test.ParseData(` + // --- + // b: + // - c + // `) - _, err := subject.DeletePath(data, "b.[a]") - if err == nil { - t.Fatal("Expected error due to invalid path") - } - }) + // _, err := subject.DeletePath(data, "b.[a]") + // if err == nil { + // t.Fatal("Expected error due to invalid path") + // } + // }) - t.Run("TestMerge", func(t *testing.T) { - var dst = test.ParseData(` ---- -a: b -c: d -`) - var src = test.ParseData(` ---- -a: 1 -b: 2 -`) + // t.Run("TestMerge", func(t *testing.T) { + // var dst = test.ParseData(` + // --- + // a: b + // c: d + // `) + // var src = test.ParseData(` + // --- + // a: 1 + // b: 2 + // `) - var mergedData = make(map[interface{}]interface{}) - mergedData["root"] = dst - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = src + // var mergedData = make(map[interface{}]interface{}) + // mergedData["root"] = dst + // var mapDataBucket = make(map[interface{}]interface{}) + // mapDataBucket["root"] = src - err := subject.Merge(&mergedData, mapDataBucket, false, false) - if err != nil { - t.Fatal("Unexpected error") - } - test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) - }) + // err := subject.Merge(&mergedData, mapDataBucket, false, false) + // if err != nil { + // t.Fatal("Unexpected error") + // } + // test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) + // }) - t.Run("TestMerge_WithOverwrite", func(t *testing.T) { - var dst = test.ParseData(` ---- -a: b -c: d -`) - var src = test.ParseData(` ---- -a: 1 -b: 2 -`) + // t.Run("TestMerge_WithOverwrite", func(t *testing.T) { + // var dst = test.ParseData(` + // --- + // a: b + // c: d + // `) + // var src = test.ParseData(` + // --- + // a: 1 + // b: 2 + // `) - var mergedData = make(map[interface{}]interface{}) - mergedData["root"] = dst - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = src + // var mergedData = make(map[interface{}]interface{}) + // mergedData["root"] = dst + // var mapDataBucket = make(map[interface{}]interface{}) + // mapDataBucket["root"] = src - err := subject.Merge(&mergedData, mapDataBucket, true, false) - if err != nil { - t.Fatal("Unexpected error") - } - test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) - }) + // err := subject.Merge(&mergedData, mapDataBucket, true, false) + // if err != nil { + // t.Fatal("Unexpected error") + // } + // test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) + // }) - t.Run("TestMerge_WithAppend", func(t *testing.T) { - var dst = test.ParseData(` ---- -a: b -c: d -`) - var src = test.ParseData(` ---- -a: 1 -b: 2 -`) + // t.Run("TestMerge_WithAppend", func(t *testing.T) { + // var dst = test.ParseData(` + // --- + // a: b + // c: d + // `) + // var src = test.ParseData(` + // --- + // a: 1 + // b: 2 + // `) - var mergedData = make(map[interface{}]interface{}) - mergedData["root"] = dst - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = src + // var mergedData = make(map[interface{}]interface{}) + // mergedData["root"] = dst + // var mapDataBucket = make(map[interface{}]interface{}) + // mapDataBucket["root"] = src - err := subject.Merge(&mergedData, mapDataBucket, false, true) - if err != nil { - t.Fatal("Unexpected error") - } - test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) - }) + // err := subject.Merge(&mergedData, mapDataBucket, false, true) + // if err != nil { + // t.Fatal("Unexpected error") + // } + // test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) + // }) - t.Run("TestMerge_WithError", func(t *testing.T) { - err := subject.Merge(nil, nil, false, false) - if err == nil { - t.Fatal("Expected error due to nil") - } - }) + // t.Run("TestMerge_WithError", func(t *testing.T) { + // err := subject.Merge(nil, nil, false, false) + // if err == nil { + // t.Fatal("Expected error due to nil") + // } + // }) } diff --git a/pkg/yqlib/path_parser_test.go b/pkg/yqlib/path_parser_test.go index 460d3fe9..f4212275 100644 --- a/pkg/yqlib/path_parser_test.go +++ b/pkg/yqlib/path_parser_test.go @@ -6,11 +6,15 @@ import ( "github.com/mikefarah/yq/v3/test" ) +var parser = NewPathParser() + var parsePathsTests = []struct { path string expectedPaths []string }{ {"a.b", []string{"a", "b"}}, + {"a.b.**", []string{"a", "b", "**"}}, + {"a.b.*", []string{"a", "b", "*"}}, {"a.b[0]", []string{"a", "b", "0"}}, {"a.b.d[+]", []string{"a", "b", "d", "+"}}, {"a", []string{"a"}}, @@ -22,8 +26,53 @@ var parsePathsTests = []struct { {"[0]", []string{"0"}}, } -func TestParsePath(t *testing.T) { +func TestPathParserParsePath(t *testing.T) { for _, tt := range parsePathsTests { - test.AssertResultComplex(t, tt.expectedPaths, NewPathParser().ParsePath(tt.path)) + test.AssertResultComplex(t, tt.expectedPaths, parser.ParsePath(tt.path)) } } + +func TestPathParserMatchesNextPathElementSplat(t *testing.T) { + var node = NodeContext{Head: "*"} + test.AssertResult(t, true, parser.MatchesNextPathElement(node, "")) +} + +func TestPathParserMatchesNextPathElementDeepSplat(t *testing.T) { + var node = NodeContext{Head: "**"} + test.AssertResult(t, true, parser.MatchesNextPathElement(node, "")) +} + +func TestPathParserMatchesNextPathElementAppendArrayValid(t *testing.T) { + var node = NodeContext{Head: "+"} + test.AssertResult(t, true, parser.MatchesNextPathElement(node, "3")) +} + +func TestPathParserMatchesNextPathElementAppendArrayInvalid(t *testing.T) { + var node = NodeContext{Head: "+"} + test.AssertResult(t, false, parser.MatchesNextPathElement(node, "cat")) +} + +func TestPathParserMatchesNextPathElementPrefixMatchesWhole(t *testing.T) { + var node = NodeContext{Head: "cat*"} + test.AssertResult(t, true, parser.MatchesNextPathElement(node, "cat")) +} + +func TestPathParserMatchesNextPathElementPrefixMatchesStart(t *testing.T) { + var node = NodeContext{Head: "cat*"} + test.AssertResult(t, true, parser.MatchesNextPathElement(node, "caterpillar")) +} + +func TestPathParserMatchesNextPathElementPrefixMismatch(t *testing.T) { + var node = NodeContext{Head: "cat*"} + test.AssertResult(t, false, parser.MatchesNextPathElement(node, "dog")) +} + +func TestPathParserMatchesNextPathElementExactMatch(t *testing.T) { + var node = NodeContext{Head: "farahtek"} + test.AssertResult(t, true, parser.MatchesNextPathElement(node, "farahtek")) +} + +func TestPathParserMatchesNextPathElementExactMismatch(t *testing.T) { + var node = NodeContext{Head: "farahtek"} + test.AssertResult(t, false, parser.MatchesNextPathElement(node, "othertek")) +} diff --git a/pkg/yqlib/value_parser.go b/pkg/yqlib/value_parser.go index 7a51d7c9..6f145a75 100644 --- a/pkg/yqlib/value_parser.go +++ b/pkg/yqlib/value_parser.go @@ -3,7 +3,6 @@ package yqlib import ( "strconv" - logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" ) @@ -12,20 +11,17 @@ type ValueParser interface { } type valueParser struct { - log *logging.Logger } -func NewValueParser(l *logging.Logger) ValueParser { - return &valueParser{log: l} +func NewValueParser() ValueParser { + return &valueParser{} } func (v *valueParser) Parse(argument string, customTag string) *yaml.Node { var err interface{} var tag = customTag - var inQuotes = len(argument) > 0 && argument[0] == '"' - if tag == "" && !inQuotes { - + if tag == "" { _, err = strconv.ParseBool(argument) if err == nil { tag = "!!bool" @@ -46,6 +42,6 @@ func (v *valueParser) Parse(argument string, customTag string) *yaml.Node { return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} } } - v.log.Debugf("parsed value '%v', tag: '%v'", argument, tag) + log.Debugf("parsed value '%v', tag: '%v'", argument, tag) return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} } diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go index 24468e37..e4930974 100644 --- a/pkg/yqlib/value_parser_test.go +++ b/pkg/yqlib/value_parser_test.go @@ -4,22 +4,35 @@ import ( "testing" "github.com/mikefarah/yq/v3/test" + yaml "gopkg.in/yaml.v3" ) var parseValueTests = []struct { argument string - expectedResult interface{} + customTag string + expectedTag string testDescription string }{ - {"true", true, "boolean"}, - {"\"true\"", "true", "boolean as string"}, - {"3.4", 3.4, "number"}, - {"\"3.4\"", "3.4", "number as string"}, - {"", "", "empty string"}, + {"true", "", "!!bool", "boolean"}, + {"true", "!!string", "!!string", "boolean forced as string"}, + {"3.4", "", "!!float", "float"}, + {"1212121", "", "!!int", "big number"}, + {"1212121.1", "", "!!float", "big float number"}, + {"3", "", "!!int", "int"}, + {"null", "", "!!null", "null"}, } -func TestParseValue(t *testing.T) { +func TestValueParserParse(t *testing.T) { for _, tt := range parseValueTests { - test.AssertResultWithContext(t, tt.expectedResult, NewValueParser().ParseValue(tt.argument), tt.testDescription) + actual := NewValueParser().Parse(tt.argument, tt.customTag) + test.AssertResultWithContext(t, tt.argument, actual.Value, tt.testDescription) + test.AssertResultWithContext(t, tt.expectedTag, actual.Tag, tt.testDescription) + test.AssertResult(t, yaml.ScalarNode, actual.Kind) } } + +func TestValueParserParseEmptyArray(t *testing.T) { + actual := NewValueParser().Parse("[]", "") + test.AssertResult(t, "!!seq", actual.Tag) + test.AssertResult(t, yaml.SequenceNode, actual.Kind) +} diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 1f806696..88b4b974 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -2,5 +2,5 @@ set -e -go test -coverprofile=coverage.out ./... -go tool cover -html=coverage.out -o cover/coverage.html +go test -coverprofile=coverage.out -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test') +go tool cover -html=coverage.out -o coverage.html diff --git a/scripts/test.sh b/scripts/test.sh index fcba05e2..2ea6262d 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,3 +1,3 @@ #!/bin/bash -go test -v $(go list ./... | grep -v -E 'examples') +go test -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test') diff --git a/yq.go b/yq.go index 053b4a98..a5da09b7 100644 --- a/yq.go +++ b/yq.go @@ -30,8 +30,8 @@ var verbose = false var version = false var docIndex = "0" var log = logging.MustGetLogger("yq") -var lib = yqlib.NewYqLib(log) -var valueParser = yqlib.NewValueParser(log) +var lib = yqlib.NewYqLib() +var valueParser = yqlib.NewValueParser() func main() { cmd := newCommandCLI() @@ -210,6 +210,7 @@ Note that you can give a create script to perform more sophisticated yaml. This RunE: newProperty, } cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") + cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") return cmdNew } From 924eb6c4622d99702409732fae900dfc1639d3cd Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 9 Jan 2020 21:18:24 +1100 Subject: [PATCH 46/68] Added missing functions to interface --- pkg/yqlib/data_navigator.go | 4 ++-- pkg/yqlib/lib.go | 18 ++++++++++++++---- pkg/yqlib/lib_test.go | 3 +++ pkg/yqlib/navigation_strategy.go | 8 ++++---- yq.go | 6 +++--- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 2a0ec80c..cfbfece8 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -96,7 +96,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat newPathStack := append(pathStack, contents[indexInMap].Value) log.Debug("appended %v", contents[indexInMap].Value) n.navigationStrategy.DebugVisitedNodes() - log.Debug("should I traverse? %v, %v", head, PathStackToString(newPathStack)) + log.Debug("should I traverse? %v, %v", head, pathStackToString(newPathStack)) DebugNode(value) if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true { log.Debug("recurseMap: Going to traverse") @@ -232,7 +232,7 @@ func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pa func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { - return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, PathStackToString(pathStack)) + return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, pathStackToString(pathStack)) } for int64(len(value.Content)) <= index { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 1675e60f..8429a126 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -12,7 +12,6 @@ import ( var log = logging.MustGetLogger("yq") -// TODO: enumerate type UpdateCommand struct { Command string Path string @@ -33,11 +32,11 @@ func DebugNode(value *yaml.Node) { } } -func PathStackToString(pathStack []interface{}) string { - return MergePathStackToString(pathStack, false) +func pathStackToString(pathStack []interface{}) string { + return mergePathStackToString(pathStack, false) } -func MergePathStackToString(pathStack []interface{}, appendArrays bool) string { +func mergePathStackToString(pathStack []interface{}, appendArrays bool) string { var sb strings.Builder for index, path := range pathStack { switch path.(type) { @@ -89,6 +88,9 @@ type YqLib interface { Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error New(path string) yaml.Node + + PathStackToString(pathStack []interface{}) string + MergePathStackToString(pathStack []interface{}, appendArrays bool) string } type lib struct { @@ -111,6 +113,14 @@ func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { } +func (l *lib) PathStackToString(pathStack []interface{}) string { + return pathStackToString(pathStack) +} + +func (l *lib) MergePathStackToString(pathStack []interface{}, appendArrays bool) string { + return mergePathStackToString(pathStack, appendArrays) +} + func (l *lib) New(path string) yaml.Node { var paths = l.parser.ParsePath(path) newNode := yaml.Node{Kind: guessKind("", paths, 0)} diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index 77411470..23f4d85f 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -6,6 +6,9 @@ import ( func TestLib(t *testing.T) { + // PathStackToString + // MergePathStackToString (with true) + // var log = logging.MustGetLogger("yq") // subject := NewYqLib(log) diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 740d06f9..d1d72ca0 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -95,7 +95,7 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { } func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { - log.Debug("Visit?, %v, %v", nodeContext.Head, PathStackToString(nodeContext.PathStack)) + log.Debug("Visit?, %v, %v", nodeContext.Head, pathStackToString(nodeContext.PathStack)) DebugNode(nodeContext.Node) if ns.shouldVisit(nodeContext) { log.Debug("yep, visiting") @@ -112,12 +112,12 @@ func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { func (ns *NavigationStrategyImpl) DebugVisitedNodes() { log.Debug("Visited Nodes:") for _, candidate := range ns.visitedNodes { - log.Debug(" - %v", PathStackToString(candidate.PathStack)) + log.Debug(" - %v", pathStackToString(candidate.PathStack)) } } func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { - log.Debug("checking already visited pathStack: %v", PathStackToString(pathStack)) + log.Debug("checking already visited pathStack: %v", pathStackToString(pathStack)) for _, candidate := range ns.visitedNodes { candidatePathStack := candidate.PathStack if patchStacksMatch(candidatePathStack, pathStack) { @@ -131,7 +131,7 @@ func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { } func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool { - log.Debug("checking against path: %v", PathStackToString(path1)) + log.Debug("checking against path: %v", pathStackToString(path1)) if len(path1) != len(path2) { return false diff --git a/yq.go b/yq.go index a5da09b7..021784cf 100644 --- a/yq.go +++ b/yq.go @@ -335,7 +335,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error for index, mappedDoc := range matchingNodes { switch printMode { case "k": - cmd.Print(yqlib.PathStackToString(mappedDoc.PathStack)) + cmd.Print(lib.PathStackToString(mappedDoc.PathStack)) if index < len(matchingNodes)-1 { cmd.Print("\n") } @@ -343,7 +343,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error // put it into a node and print that. var parentNode = yaml.Node{Kind: yaml.MappingNode} parentNode.Content = make([]*yaml.Node, 2) - parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: yqlib.PathStackToString(mappedDoc.PathStack)} + parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)} parentNode.Content[1] = mappedDoc.Node if err := printValue(&parentNode, cmd); err != nil { return err @@ -438,7 +438,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error { return errorProcessingFile } for _, matchingNode := range matchingNodes { - mergePath := yqlib.MergePathStackToString(matchingNode.PathStack, appendFlag) + mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag) updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) } } From 0621307391baec9dd012c4ec8dad69bc11ce4306 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 9 Jan 2020 21:27:52 +1100 Subject: [PATCH 47/68] Fixed linting errors --- pkg/yqlib/data_navigator.go | 10 +++++----- pkg/yqlib/delete_navigation_strategy.go | 2 +- pkg/yqlib/lib.go | 8 +++++--- pkg/yqlib/update_navigation_strategy.go | 2 +- yq.go | 6 +++--- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index cfbfece8..440331ce 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -40,7 +40,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pat // ignore errors here, we are deep splatting so we may accidently give a string key // to an array sequence if len(tail) > 0 { - n.recurse(value, tail[0], tail[1:], pathStack) + _ = n.recurse(value, tail[0], tail[1:], pathStack) } return errorDeepSplatting } @@ -78,7 +78,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt case yaml.AliasNode: log.Debug("its an alias!") DebugNode(value.Alias) - if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) == true { + if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) { log.Debug("following the alias") return n.recurse(value.Alias, head, tail, pathStack) } @@ -98,7 +98,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat n.navigationStrategy.DebugVisitedNodes() log.Debug("should I traverse? %v, %v", head, pathStackToString(newPathStack)) DebugNode(value) - if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true { + if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) { log.Debug("recurseMap: Going to traverse") traversedEntry = true // contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind)) @@ -116,7 +116,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat return errorVisiting } - if traversedEntry == true || head == "*" || head == "**" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false { + if traversedEntry || head == "*" || head == "**" || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) { return nil } @@ -156,7 +156,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st // if we don't find a match directly on this node first. errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) - if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) == false { + if errorVisitedDirectEntries != nil || !n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) { return errorVisitedDirectEntries } return n.visitAliases(contents, head, tail, pathStack, visit) diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go index b4f95c3a..fc83c9e2 100644 --- a/pkg/yqlib/delete_navigation_strategy.go +++ b/pkg/yqlib/delete_navigation_strategy.go @@ -38,7 +38,7 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int for index := 0; index < len(contents); index = index + 2 { keyNode := contents[index] valueNode := contents[index+1] - if pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) == false { + if !pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) { log.Debug("adding node %v", keyNode.Value) newContents = append(newContents, keyNode, valueNode) } else { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 8429a126..e455d7ff 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -25,7 +25,10 @@ func DebugNode(value *yaml.Node) { } else if log.IsEnabledFor(logging.DEBUG) { buf := new(bytes.Buffer) encoder := yaml.NewEncoder(buf) - encoder.Encode(value) + errorEncoding := encoder.Encode(value) + if errorEncoding != nil { + log.Error("Error debugging node, %v", errorEncoding.Error()) + } encoder.Close() log.Debug("Tag: %v", value.Tag) log.Debug("%v", buf.String()) @@ -94,8 +97,7 @@ type YqLib interface { } type lib struct { - navigator DataNavigator - parser PathParser + parser PathParser } func NewYqLib() YqLib { diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go index 8eea9069..e6be8b71 100644 --- a/pkg/yqlib/update_navigation_strategy.go +++ b/pkg/yqlib/update_navigation_strategy.go @@ -12,7 +12,7 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi visit: func(nodeContext NodeContext) error { node := nodeContext.Node changesToApply := updateCommand.Value - if updateCommand.Overwrite == true || node.Value == "" { + if updateCommand.Overwrite || node.Value == "" { log.Debug("going to update") DebugNode(node) log.Debug("with") diff --git a/yq.go b/yq.go index 021784cf..c36ba59d 100644 --- a/yq.go +++ b/yq.go @@ -434,7 +434,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error { for _, fileToMerge := range filesToMerge { matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) - if errorProcessingFile != nil && (allowEmptyFlag == false || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { + if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { return errorProcessingFile } for _, matchingNode := range matchingNodes { @@ -464,9 +464,9 @@ func newProperty(cmd *cobra.Command, args []string) error { var encoder = yaml.NewEncoder(cmd.OutOrStdout()) encoder.SetIndent(2) - encoder.Encode(&newNode) + errorEncoding := encoder.Encode(&newNode) encoder.Close() - return nil + return errorEncoding } func prefixProperty(cmd *cobra.Command, args []string) error { From feba7b04faf86354a36d324e11eb97279d3ec753 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 9 Jan 2020 21:36:05 +1100 Subject: [PATCH 48/68] Added path stack to string test --- pkg/yqlib/lib_test.go | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index 23f4d85f..4adf7df5 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -2,26 +2,37 @@ package yqlib import ( "testing" + + "github.com/mikefarah/yq/v3/test" ) func TestLib(t *testing.T) { - // PathStackToString - // MergePathStackToString (with true) + subject := NewYqLib() - // var log = logging.MustGetLogger("yq") - // subject := NewYqLib(log) + t.Run("PathStackToString_Empty", func(t *testing.T) { + emptyArray := make([]interface{}, 0) + got := subject.PathStackToString(emptyArray) + test.AssertResult(t, ``, got) + }) - // t.Run("TestReadPath", func(t *testing.T) { - // var data = test.ParseData(` - // --- - // b: - // 2: c - // `) + t.Run("PathStackToString", func(t *testing.T) { + array := make([]interface{}, 3) + array[0] = "a" + array[1] = 0 + array[2] = "b" + got := subject.PathStackToString(array) + test.AssertResult(t, `a.[0].b`, got) + }) - // got, _ := subject.ReadPath(data, "b.2") - // test.AssertResult(t, `c`, got) - // }) + t.Run("MergePathStackToString", func(t *testing.T) { + array := make([]interface{}, 3) + array[0] = "a" + array[1] = 0 + array[2] = "b" + got := subject.MergePathStackToString(array, true) + test.AssertResult(t, `a.[+].b`, got) + }) // t.Run("TestReadPath_WithError", func(t *testing.T) { // var data = test.ParseData(` From 854f5f0fc9041fa618de9800ffbd0aead1f57a88 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 10 Jan 2020 22:01:59 +1100 Subject: [PATCH 49/68] wip json encoding --- Upgrade Notes | 9 ++--- commands_test.go | 2 +- examples/instruction_sample.yaml | 5 +-- examples/sample.yaml | 2 +- pkg/yqlib/encoder.go | 44 +++++++++++++++++++++ pkg/yqlib/value_parser_test.go | 2 +- yq.go | 65 +++++++++++++++++++++----------- 7 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 pkg/yqlib/encoder.go diff --git a/Upgrade Notes b/Upgrade Notes index 1b7bb513..38f97368 100644 --- a/Upgrade Notes +++ b/Upgrade Notes @@ -1,15 +1,12 @@ -# Update doco / notes - - --autocreate=false to turn off default flags - - add comments to test yaml to ensure they are kept on updating. - - update built in command notes to includes quotes around path args. # New Features - Keeps comments and formatting (e.g. inline arrays)! - Handles anchors! - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead. + - JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line. -# Update scripts file format has changed +# Update scripts file format has changed to be more powerful. Comments can be added, and delete commands have been introduced. # Merge command -- autocreates missing entries in target by default, new flag to turn that off. +- New flag 'autocreates' missing entries in target by default, new flag to turn that off. diff --git a/commands_test.go b/commands_test.go index 9f600f38..bf0b438f 100644 --- a/commands_test.go +++ b/commands_test.go @@ -494,7 +494,7 @@ func TestReadCmd_Verbose(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "2", result.Output) + test.AssertResult(t, "2\n", result.Output) } // func TestReadCmd_ToJson(t *testing.T) { diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml index 24445bca..d531a302 100644 --- a/examples/instruction_sample.yaml +++ b/examples/instruction_sample.yaml @@ -6,6 +6,5 @@ - command: update path: b.e[+].name value: Mike Farah -- command: update - path: d.a - value: Cow \ No newline at end of file +- command: delete + path: b.d \ No newline at end of file diff --git a/examples/sample.yaml b/examples/sample.yaml index a08d1d1f..1fb93352 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,4 +1,4 @@ -a: Easy! as one two three +a: true b: c: 2 d: [3, 4, 5] diff --git a/pkg/yqlib/encoder.go b/pkg/yqlib/encoder.go new file mode 100644 index 00000000..affd213e --- /dev/null +++ b/pkg/yqlib/encoder.go @@ -0,0 +1,44 @@ +package yqlib + +import ( + "encoding/json" + "io" + + yaml "gopkg.in/yaml.v3" +) + +type Encoder interface { + Encode(node *yaml.Node) error +} + +type yamlEncoder struct { + encoder *yaml.Encoder +} + +func NewYamlEncoder(destination io.Writer) Encoder { + var encoder = yaml.NewEncoder(destination) + encoder.SetIndent(2) + return &yamlEncoder{encoder} +} + +func (ye *yamlEncoder) Encode(node *yaml.Node) error { + return ye.encoder.Encode(node) +} + +type jsonEncoder struct { + encoder *json.Encoder +} + +func NewJsonEncoder(destination io.Writer) Encoder { + var encoder = json.NewEncoder(destination) + return &jsonEncoder{encoder} +} + +func (je *jsonEncoder) Encode(node *yaml.Node) error { + var dataBucket interface{} + errorDecoding := node.Decode(&dataBucket) + if errorDecoding != nil { + return errorDecoding + } + return je.encoder.Encode(dataBucket) +} diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go index e4930974..fb439d69 100644 --- a/pkg/yqlib/value_parser_test.go +++ b/pkg/yqlib/value_parser_test.go @@ -14,7 +14,7 @@ var parseValueTests = []struct { testDescription string }{ {"true", "", "!!bool", "boolean"}, - {"true", "!!string", "!!string", "boolean forced as string"}, + {"true", "!!str", "!!str", "boolean forced as string"}, {"3.4", "", "!!float", "float"}, {"1212121", "", "!!int", "big number"}, {"1212121.1", "", "!!float", "big float number"}, diff --git a/yq.go b/yq.go index c36ba59d..5df65ffe 100644 --- a/yq.go +++ b/yq.go @@ -22,6 +22,7 @@ var customTag = "" var printMode = "v" var writeInplace = false var writeScript = "" +var outputToJSON = false var overwriteFlag = false var autoCreateFlag = true var allowEmptyFlag = false @@ -73,6 +74,7 @@ func newCommandCLI() *cobra.Command { } rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") + rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") rootCmd.AddCommand( @@ -97,9 +99,9 @@ func createReadCmd() *cobra.Command { yq read things.yaml a.b.c yq r - a.b.c (reads from stdin) yq r things.yaml a.*.c -yq r -d1 things.yaml a.array[0].blah -yq r things.yaml a.array[*].blah -yq r -- things.yaml --key-starting-with-dashes +yq r -d1 things.yaml 'a.array[0].blah' +yq r things.yaml 'a.array[*].blah' +yq r -- things.yaml --key-starting-with-dashes.blah `, Long: "Outputs the value of the given path in the yaml file to STDOUT", RunE: readProperty, @@ -115,13 +117,15 @@ func createWriteCmd() *cobra.Command { Aliases: []string{"w"}, Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", Example: ` -yq write things.yaml a.b.c cat +yq write things.yaml a.b.c true +yq write things.yaml a.b.c --tag '!!str' true +yq write things.yaml a.b.c --tag '!!float' 3 yq write --inplace -- things.yaml a.b.c --cat yq w -i things.yaml a.b.c cat yq w --script update_script.yaml things.yaml yq w -i -s update_script.yaml things.yaml -yq w --doc 2 things.yaml a.b.d[+] foo -yq w -d2 things.yaml a.b.d[+] foo +yq w --doc 2 things.yaml 'a.b.d[+]' foo +yq w -d2 things.yaml 'a.b.d[+]' foo `, Long: `Updates the yaml file w.r.t the given path and value. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. @@ -129,12 +133,16 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd Append value to array adds the value to the end of array. Update Scripts: -Note that you can give an update script to perform more sophisticated updated. Update script -format is a yaml map where the key is the path and the value is..well the value. e.g.: +Note that you can give an update script to perform more sophisticated update. Update script +format is list of update commands (update or delete) like so: --- -a.b.c: true, -a.b.e: - - name: bob +- command: update + path: b.c + value: + #great + things: frog # wow! +- command: delete + path: b.d `, RunE: writeProperty, } @@ -198,6 +206,7 @@ func createNewCmd() *cobra.Command { Example: ` yq new a.b.c cat yq n a.b.c cat +yq n a.b[+] --tag '!!str' true yq n -- --key-starting-with-dash cat yq n --script create_script.yaml `, @@ -226,6 +235,7 @@ yq m -i things.yaml other.yaml yq m --overwrite things.yaml other.yaml yq m -i -x things.yaml other.yaml yq m -i -a things.yaml other.yaml +yq m -i --autocreate=false things.yaml other.yaml `, Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. @@ -313,16 +323,18 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml. } func printValue(node *yaml.Node, cmd *cobra.Command) error { - if node.Kind == yaml.ScalarNode { - cmd.Print(node.Value) - return nil + bufferedWriter := bufio.NewWriter(cmd.OutOrStdout()) + defer safelyFlush(bufferedWriter) + + var encoder yqlib.Encoder + if outputToJSON { + encoder = yqlib.NewJsonEncoder(bufferedWriter) + } else { + encoder = yqlib.NewYamlEncoder(bufferedWriter) } - var encoder = yaml.NewEncoder(cmd.OutOrStdout()) - encoder.SetIndent(2) if err := encoder.Encode(node); err != nil { return err } - encoder.Close() return nil } @@ -376,7 +388,7 @@ func parseDocumentIndex() (bool, int, error) { type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error -func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn { +func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn { return func(decoder *yaml.Decoder) error { var dataBucket yaml.Node var errorReading error @@ -561,14 +573,21 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) safelyRenameFile(tempFile.Name(), inputFile) }() } else { - var writer = bufio.NewWriter(stdOut) - destination = writer + destination = stdOut destinationName = "Stdout" - defer safelyFlush(writer) } - var encoder = yaml.NewEncoder(destination) - encoder.SetIndent(2) + log.Debugf("Writing to %v from %v", destinationName, inputFile) + + bufferedWriter := bufio.NewWriter(destination) + defer safelyFlush(bufferedWriter) + + var encoder yqlib.Encoder + if outputToJSON { + encoder = yqlib.NewJsonEncoder(bufferedWriter) + } else { + encoder = yqlib.NewYamlEncoder(bufferedWriter) + } return readStream(inputFile, mapYamlDecoder(updateData, encoder)) } From 728cbe991a661e836780d620504e4b54b6b1da64 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 09:07:39 +1100 Subject: [PATCH 50/68] Print path is more accurate than keys (i think) --- Upgrade Notes | 50 +++++++++++++++++++++++++++++++++++++++++++++--- commands_test.go | 50 ++++++++++++++++++++++++++++++++++-------------- yq.go | 19 +++++++++++++----- 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/Upgrade Notes b/Upgrade Notes index 38f97368..9f2eecdf 100644 --- a/Upgrade Notes +++ b/Upgrade Notes @@ -1,11 +1,55 @@ # New Features - - Keeps comments and formatting (e.g. inline arrays)! - - Handles anchors! + - Keeps yaml comments and formatting (string blocks are saved, number formatting is preserved, so it won't drop off trailing 0s for values like 0.10, which is important when that's a version entry ) + + - Handles anchors! (doc link) - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead. + - Can print out matching paths and values when splatting (doc link) - JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line. + - Deep splat (**) to match arbitrary paths, (doc link) -# Update scripts file format has changed to be more powerful. Comments can be added, and delete commands have been introduced. + +# Breaking changes + +## Update scripts file format has changed to be more powerful. +Comments can be added, and delete commands have been introduced. + +## Reading and splatting, matching results are printed once per line. + e.g: + +```json +parent: + childA: + no: matches here + childB: + there: matches + hi: no match + there2: also matches +``` + +yq r sample.yaml 'parent.*.there*' + +old +```yaml +- null +- - matches + - also matches +``` + +new +```yaml +matches +also matches +``` + +and you can print the matching paths: + +yq r --printMode pv sample.yaml 'parent.*.there*' + +```yaml +parent.childB.there: matches +parent.childB.there2: also matches +``` # Merge command - New flag 'autocreates' missing entries in target by default, new flag to turn that off. diff --git a/commands_test.go b/commands_test.go index bf0b438f..487bb7fd 100644 --- a/commands_test.go +++ b/commands_test.go @@ -96,7 +96,7 @@ func TestReadCmd(t *testing.T) { func TestReadWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -105,7 +105,7 @@ func TestReadWithKeyAndValueCmd(t *testing.T) { func TestReadArrayCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.e.1.name") + result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.e.1.name") if result.Error != nil { t.Error(result.Error) } @@ -114,7 +114,7 @@ func TestReadArrayCmd(t *testing.T) { func TestReadDeepSplatCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**") + result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**") if result.Error != nil { t.Error(result.Error) } @@ -132,7 +132,7 @@ b.e.[1].value: 4 func TestReadDeepSplatWithSuffixCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**.name") + result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**.name") if result.Error != nil { t.Error(result.Error) } @@ -144,7 +144,7 @@ b.e.[1].name: sam func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -p p examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -162,7 +162,7 @@ func TestReadAnchorsCmd(t *testing.T) { func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv examples/simple-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read -p pv examples/simple-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -189,7 +189,7 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) { func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "r -p kv examples/merge-anchor.yaml foobar.th*") + result := test.RunCmd(cmd, "r -p pv examples/merge-anchor.yaml foobar.th*") if result.Error != nil { t.Error(result.Error) } @@ -271,7 +271,7 @@ func TestReadMultiCmd(t *testing.T) { func TestReadMultiWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p vk -d 1 examples/multiple_docs.yaml another.document") + result := test.RunCmd(cmd, "read -p vp -d 1 examples/multiple_docs.yaml another.document") if result.Error != nil { t.Error(result.Error) } @@ -292,7 +292,7 @@ third document`, result.Output) func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv -d* examples/multiple_docs.yaml commonKey") + result := test.RunCmd(cmd, "read -p pv -d* examples/multiple_docs.yaml commonKey") if result.Error != nil { t.Error(result.Error) } @@ -372,7 +372,7 @@ gather_facts: true func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p kv examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -p pv examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -394,7 +394,7 @@ func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p k examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -p p examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -494,7 +494,7 @@ func TestReadCmd_Verbose(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - test.AssertResult(t, "2\n", result.Output) + test.AssertResult(t, "2", result.Output) } // func TestReadCmd_ToJson(t *testing.T) { @@ -559,7 +559,7 @@ b: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p kv %s b.there*.c", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s b.there*.c", filename)) if result.Error != nil { t.Error(result.Error) } @@ -587,7 +587,7 @@ b: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p k %s b.there*.c", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read -p p %s b.there*.c", filename)) if result.Error != nil { t.Error(result.Error) } @@ -871,6 +871,28 @@ func TestWriteCmdScript(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } +func TestWriteCmdEmptyScript(t *testing.T) { + content := `b: + c: 3 +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + updateScript := `` + scriptFilename := test.WriteTempYamlFile(updateScript) + defer test.RemoveTempYamlFile(scriptFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b: + c: 3 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestWriteMultiCmd(t *testing.T) { content := `b: c: 3 diff --git a/yq.go b/yq.go index 5df65ffe..59d26480 100644 --- a/yq.go +++ b/yq.go @@ -107,7 +107,7 @@ yq r -- things.yaml --key-starting-with-dashes.blah RunE: readProperty, } cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), k (keys), kv (key and value pairs)") + cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") return cmdRead } @@ -218,7 +218,7 @@ Note that you can give a create script to perform more sophisticated yaml. This `, RunE: newProperty, } - cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") + cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") return cmdNew } @@ -323,6 +323,11 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml. } func printValue(node *yaml.Node, cmd *cobra.Command) error { + if node.Kind == yaml.ScalarNode { + cmd.Print(node.Value) + return nil + } + bufferedWriter := bufio.NewWriter(cmd.OutOrStdout()) defer safelyFlush(bufferedWriter) @@ -346,12 +351,12 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error for index, mappedDoc := range matchingNodes { switch printMode { - case "k": + case "p": cmd.Print(lib.PathStackToString(mappedDoc.PathStack)) if index < len(matchingNodes)-1 { cmd.Print("\n") } - case "kv", "vk": + case "pv", "vp": // put it into a node and print that. var parentNode = yaml.Node{Kind: yaml.MappingNode} parentNode.Content = make([]*yaml.Node, 2) @@ -601,9 +606,13 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) if writeScript != "" { var parsedCommands = make([]updateCommandParsed, 0) - if err := readData(writeScript, 0, &parsedCommands); err != nil { + + err := readData(writeScript, 0, &parsedCommands) + + if err != nil && err != io.EOF { return nil, err } + log.Debugf("Read write commands file '%v'", parsedCommands) for index := range parsedCommands { parsedCommand := parsedCommands[index] From 24dcb56466081b28019d99b92fb3547829742748 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 09:13:42 +1100 Subject: [PATCH 51/68] Inc version - fix help text --- snap/snapcraft.yaml | 2 +- version.go | 2 +- yq.go | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ad171bd3..05a0545f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: yq -version: '2.4.1' +version: '3.0.0' summary: A lightweight and portable command-line YAML processor description: | The aim of the project is to be the jq or sed of yaml files. diff --git a/version.go b/version.go index eca405f0..4d0226b0 100644 --- a/version.go +++ b/version.go @@ -11,7 +11,7 @@ var ( GitDescribe string // Version is main version number that is being run at the moment. - Version = "2.4.1" + Version = "3.0.0" // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/yq.go b/yq.go index 59d26480..931be12a 100644 --- a/yq.go +++ b/yq.go @@ -99,6 +99,7 @@ func createReadCmd() *cobra.Command { yq read things.yaml a.b.c yq r - a.b.c (reads from stdin) yq r things.yaml a.*.c +yq r things.yaml a.**.c yq r -d1 things.yaml 'a.array[0].blah' yq r things.yaml 'a.array[*].blah' yq r -- things.yaml --key-starting-with-dashes.blah @@ -118,6 +119,8 @@ func createWriteCmd() *cobra.Command { Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", Example: ` yq write things.yaml a.b.c true +yq write things.yaml 'a.*.c' true +yq write things.yaml 'a.**' true yq write things.yaml a.b.c --tag '!!str' true yq write things.yaml a.b.c --tag '!!float' 3 yq write --inplace -- things.yaml a.b.c --cat @@ -183,6 +186,8 @@ func createDeleteCmd() *cobra.Command { Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", Example: ` yq delete things.yaml a.b.c +yq delete things.yaml a.*.c +yq delete things.yaml a.** yq delete --inplace things.yaml a.b.c yq delete --inplace -- things.yaml --key-starting-with-dash yq d -i things.yaml a.b.c From 9361b8b3e9e59e60a1d07a1b2fbfc8a259f67555 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 09:14:32 +1100 Subject: [PATCH 52/68] Beta --- snap/snapcraft.yaml | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 05a0545f..1f96b906 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: yq -version: '3.0.0' +version: '3.0.0-beta' summary: A lightweight and portable command-line YAML processor description: | The aim of the project is to be the jq or sed of yaml files. diff --git a/version.go b/version.go index 4d0226b0..588f6f73 100644 --- a/version.go +++ b/version.go @@ -16,7 +16,7 @@ var ( // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release // such as "dev" (in development), "beta", "rc1", etc. - VersionPrerelease = "" + VersionPrerelease = "beta" ) // ProductName is the name of the product From 96955ffa9c670f1d5d448a1761f34629d91cc999 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 09:55:24 +1100 Subject: [PATCH 53/68] release notes --- Upgrade Notes | 40 +++++++++++++++++++++++--------- examples/instruction_sample.yaml | 3 --- scripts/publish.sh | 2 +- scripts/xcompile.sh | 4 ++-- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/Upgrade Notes b/Upgrade Notes index 9f2eecdf..bdd8f99a 100644 --- a/Upgrade Notes +++ b/Upgrade Notes @@ -1,12 +1,14 @@ +Major release! Upgraded underlying yaml parser, re-written majority of yq. This has brought on a number of features that have been in demand for a while (see below). + +This is in beta and needs some community feedback and testing :) + # New Features - - Keeps yaml comments and formatting (string blocks are saved, number formatting is preserved, so it won't drop off trailing 0s for values like 0.10, which is important when that's a version entry ) - - - Handles anchors! (doc link) - - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead. - - Can print out matching paths and values when splatting (doc link) + - Keeps yaml comments and formatting, can specify yaml tags when updating. https://github.com/mikefarah/yq/issues/19, https://github.com/mikefarah/yq/issues/169, https://github.com/mikefarah/yq/issues/107, https://github.com/mikefarah/yq/issues/171, https://github.com/mikefarah/yq/issues/245, https://github.com/mikefarah/yq/issues/303,https://github.com/mikefarah/yq/issues/308,https://github.com/mikefarah/yq/issues/314 + - Handles anchors! https://github.com/mikefarah/yq/issues/310, https://github.com/mikefarah/yq/issues/178 + - Can print out matching paths and values when splatting https://github.com/mikefarah/yq/issues/20 - JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line. - - Deep splat (**) to match arbitrary paths, (doc link) + - Deep splat (**) to match arbitrary paths # Breaking changes @@ -14,6 +16,24 @@ ## Update scripts file format has changed to be more powerful. Comments can be added, and delete commands have been introduced. +Before: +```yaml +b.e[+].name: Mike Farah +``` + +After: +```yaml +- command: update + path: b.e[+].thing + value: + #great + things: frog # wow! +- command: delete + path: b.d +``` + +https://github.com/mikefarah/yq/issues/305 + ## Reading and splatting, matching results are printed once per line. e.g: @@ -27,7 +47,9 @@ parent: there2: also matches ``` +```bash yq r sample.yaml 'parent.*.there*' +``` old ```yaml @@ -49,8 +71,4 @@ yq r --printMode pv sample.yaml 'parent.*.there*' ```yaml parent.childB.there: matches parent.childB.there2: also matches -``` - -# Merge command -- New flag 'autocreates' missing entries in target by default, new flag to turn that off. - +``` \ No newline at end of file diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml index d531a302..652e0228 100644 --- a/examples/instruction_sample.yaml +++ b/examples/instruction_sample.yaml @@ -3,8 +3,5 @@ value: #great things: frog # wow! -- command: update - path: b.e[+].name - value: Mike Farah - command: delete path: b.d \ No newline at end of file diff --git a/scripts/publish.sh b/scripts/publish.sh index c878481e..40b290f6 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -32,5 +32,5 @@ upload() { done < <(find ./build -mindepth 1 -maxdepth 1 -print0) } -release +# release upload diff --git a/scripts/xcompile.sh b/scripts/xcompile.sh index 29d30f2a..1ecc2bfb 100755 --- a/scripts/xcompile.sh +++ b/scripts/xcompile.sh @@ -3,7 +3,7 @@ # This assumes that gonative and gox is installed as per the 'one time setup' instructions # at https://github.com/inconshreveable/gonative -gox -ldflags "${LDFLAGS}" -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" +gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}" # include non-default linux builds too -gox -ldflags "${LDFLAGS}" -os=linux -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" +gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}" From 74c7a4e0274e390a41fd30c9cabf87a5a5f582c9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 18:52:15 +1100 Subject: [PATCH 54/68] it works! wip --- commands_test.go | 9 +++++++ examples/sample.yaml | 13 +++------- pkg/yqlib/data_navigator.go | 15 +++++++---- ...ilter_matching_node_navigation_strategy.go | 20 +++++++++++++++ pkg/yqlib/lib.go | 6 ++--- pkg/yqlib/navigation_strategy.go | 13 +++++----- pkg/yqlib/path_parser.go | 25 ++++++++++++++++++- 7 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 pkg/yqlib/filter_matching_node_navigation_strategy.go diff --git a/commands_test.go b/commands_test.go index 487bb7fd..370a0a97 100644 --- a/commands_test.go +++ b/commands_test.go @@ -94,6 +94,15 @@ func TestReadCmd(t *testing.T) { test.AssertResult(t, "2", result.Output) } +func TestReadWithAdvancedFilterCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/sample.yaml b.e(name == sam).value") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "4", result.Output) +} + func TestReadWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c") diff --git a/examples/sample.yaml b/examples/sample.yaml index 1fb93352..603dc54d 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,9 +1,4 @@ -a: true -b: - c: 2 - d: [3, 4, 5] - e: - - name: fred - value: 3 - - name: sam - value: 4 \ No newline at end of file +- name: fred + value: 3 +- name: sam + value: 4 \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 440331ce..8dedf9a8 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -2,6 +2,7 @@ package yqlib import ( "strconv" + "strings" errors "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" @@ -69,7 +70,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) - if head == "*" || head == "**" { + if head == "*" || head == "**" || strings.Contains(head, "==") { return n.splatArray(value, head, tail, pathStack) } else if head == "+" { return n.appendArray(value, head, tail, pathStack) @@ -96,7 +97,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat newPathStack := append(pathStack, contents[indexInMap].Value) log.Debug("appended %v", contents[indexInMap].Value) n.navigationStrategy.DebugVisitedNodes() - log.Debug("should I traverse? %v, %v", head, pathStackToString(newPathStack)) + log.Debug("should I traverse? head: %v, path: %v", head, pathStackToString(newPathStack)) DebugNode(value) if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) { log.Debug("recurseMap: Going to traverse") @@ -214,9 +215,13 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat log.Debug("processing") DebugNode(childValue) childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind)) - var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) - if err != nil { - return err + + newPathStack := append(pathStack, index) + if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) { + var err = n.doTraverse(childValue, head, tail, newPathStack) + if err != nil { + return err + } } } return nil diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go new file mode 100644 index 00000000..dde3b8a3 --- /dev/null +++ b/pkg/yqlib/filter_matching_node_navigation_strategy.go @@ -0,0 +1,20 @@ +package yqlib + +func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy { + return &NavigationStrategyImpl{ + visitedNodes: []*NodeContext{}, + followAlias: func(nodeContext NodeContext) bool { + return true + }, + autoCreateMap: func(nodeContext NodeContext) bool { + return false + }, + visit: func(nodeContext NodeContext) error { + return nil + }, + shouldVisitExtraFn: func(nodeContext NodeContext) bool { + log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value) + return nodeContext.Node.Value == value + }, + } +} diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index e455d7ff..108ef3ac 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -108,10 +108,10 @@ func NewYqLib() YqLib { func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { var paths = l.parser.ParsePath(path) - NavigationStrategy := ReadNavigationStrategy() - navigator := NewDataNavigator(NavigationStrategy) + navigationStrategy := ReadNavigationStrategy() + navigator := NewDataNavigator(navigationStrategy) error := navigator.Traverse(rootNode, paths) - return NavigationStrategy.GetVisitedNodes(), error + return navigationStrategy.GetVisitedNodes(), error } diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index d1d72ca0..a5ae1f1f 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -39,10 +39,11 @@ type NavigationStrategy interface { } type NavigationStrategyImpl struct { - followAlias func(nodeContext NodeContext) bool - autoCreateMap func(nodeContext NodeContext) bool - visit func(nodeContext NodeContext) error - visitedNodes []*NodeContext + followAlias func(nodeContext NodeContext) bool + autoCreateMap func(nodeContext NodeContext) bool + visit func(nodeContext NodeContext) error + shouldVisitExtraFn func(nodeContext NodeContext) bool + visitedNodes []*NodeContext } func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext { @@ -90,8 +91,8 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { parser := NewPathParser() // only visit aliases if its an exact match - return (nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" && - parser.MatchesNextPathElement(nodeContext, nodeKey)) + return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" && + parser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext)) } func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index b18ff5d8..112d7912 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -28,6 +28,26 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str if head == "**" || head == "*" { return true } + if strings.Contains(head, "==") { + log.Debug("ooh deep recursion time") + result := strings.SplitN(head, "==", 2) + path := strings.TrimSpace(result[0]) + value := strings.TrimSpace(result[1]) + log.Debug("path %v", path) + log.Debug("value %v", value) + DebugNode(nodeContext.Node) + navigationStrategy := FilterMatchingNodesNavigationStrategy(value) + + navigator := NewDataNavigator(navigationStrategy) + err := navigator.Traverse(nodeContext.Node, p.ParsePath(path)) + if err != nil { + log.Error(err.Error()) + } + //crap handle error + log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes())) + return len(navigationStrategy.GetVisitedNodes()) > 0 + } + if head == "+" { log.Debug("head is +, nodeKey is %v", nodeKey) var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint @@ -66,9 +86,12 @@ func (p *pathParser) nextYamlPath(path string) (pathElement string, remaining st case '"': // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" return p.search(path[1:], []uint8{'"'}, true) + case '(': + // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" + return p.search(path[1:], []uint8{')'}, true) default: // e.g "a.blah.cat" -> return "a" and "blah.cat" - return p.search(path[0:], []uint8{'.', '['}, false) + return p.search(path[0:], []uint8{'.', '[', '"', '('}, false) } } From 2d237e7e8e5dd749b9399c7f98e3319cd5204523 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 19:13:52 +1100 Subject: [PATCH 55/68] it works! wip --- commands_test.go | 2 +- examples/sample.yaml | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/commands_test.go b/commands_test.go index 370a0a97..d7cebecb 100644 --- a/commands_test.go +++ b/commands_test.go @@ -96,7 +96,7 @@ func TestReadCmd(t *testing.T) { func TestReadWithAdvancedFilterCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/sample.yaml b.e(name == sam).value") + result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e(name==sam).value") if result.Error != nil { t.Error(result.Error) } diff --git a/examples/sample.yaml b/examples/sample.yaml index 603dc54d..1fb93352 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,4 +1,9 @@ -- name: fred - value: 3 -- name: sam - value: 4 \ No newline at end of file +a: true +b: + c: 2 + d: [3, 4, 5] + e: + - name: fred + value: 3 + - name: sam + value: 4 \ No newline at end of file From 35fd5b7ae42cad5e35d9cebdc0eb84ff1b1ec802 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 19:30:27 +1100 Subject: [PATCH 56/68] Extracted out is path expression checking logic --- pkg/yqlib/data_navigator.go | 5 ++--- pkg/yqlib/delete_navigation_strategy.go | 1 + .../filter_matching_node_navigation_strategy.go | 1 + pkg/yqlib/lib.go | 3 ++- pkg/yqlib/navigation_strategy.go | 13 ++++++++----- pkg/yqlib/path_parser.go | 5 +++++ pkg/yqlib/read_navigation_strategy.go | 1 + pkg/yqlib/update_navigation_strategy.go | 1 + 8 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 8dedf9a8..b3932163 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -2,7 +2,6 @@ package yqlib import ( "strconv" - "strings" errors "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" @@ -70,7 +69,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) - if head == "*" || head == "**" || strings.Contains(head, "==") { + if n.navigationStrategy.GetPathParser().IsPathExpression(head) { return n.splatArray(value, head, tail, pathStack) } else if head == "+" { return n.appendArray(value, head, tail, pathStack) @@ -117,7 +116,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat return errorVisiting } - if traversedEntry || head == "*" || head == "**" || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) { + if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) { return nil } diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go index fc83c9e2..becad835 100644 --- a/pkg/yqlib/delete_navigation_strategy.go +++ b/pkg/yqlib/delete_navigation_strategy.go @@ -10,6 +10,7 @@ func DeleteNavigationStrategy(pathElementToDelete string) NavigationStrategy { parser := NewPathParser() return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, + pathParser: parser, followAlias: func(nodeContext NodeContext) bool { return false }, diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go index dde3b8a3..ad4ede0c 100644 --- a/pkg/yqlib/filter_matching_node_navigation_strategy.go +++ b/pkg/yqlib/filter_matching_node_navigation_strategy.go @@ -3,6 +3,7 @@ package yqlib func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, + pathParser: NewPathParser(), followAlias: func(nodeContext NodeContext) bool { return true }, diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 108ef3ac..196e3ddf 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -74,7 +74,8 @@ func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } - if (tail[0] == "*" || tail[0] == "**" || head == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { + pathParser := NewPathParser() + if (pathParser.IsPathExpression(tail[0]) || head == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } if guess == yaml.AliasNode { diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index a5ae1f1f..892988a7 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -36,6 +36,7 @@ type NavigationStrategy interface { ShouldTraverse(nodeContext NodeContext, nodeKey string) bool GetVisitedNodes() []*NodeContext DebugVisitedNodes() + GetPathParser() PathParser } type NavigationStrategyImpl struct { @@ -44,6 +45,11 @@ type NavigationStrategyImpl struct { visit func(nodeContext NodeContext) error shouldVisitExtraFn func(nodeContext NodeContext) bool visitedNodes []*NodeContext + pathParser PathParser +} + +func (ns *NavigationStrategyImpl) GetPathParser() PathParser { + return ns.pathParser } func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext { @@ -68,10 +74,8 @@ func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKe return false } - parser := NewPathParser() - return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" && - parser.MatchesNextPathElement(nodeContext, nodeKey)) + ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey)) } func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { @@ -88,11 +92,10 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head) - parser := NewPathParser() // only visit aliases if its an exact match return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" && - parser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext)) + ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext)) } func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 112d7912..38464522 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -8,6 +8,7 @@ import ( type PathParser interface { ParsePath(path string) []string MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool + IsPathExpression(pathElement string) bool } type pathParser struct{} @@ -16,6 +17,10 @@ func NewPathParser() PathParser { return &pathParser{} } +func (p *pathParser) IsPathExpression(pathElement string) bool { + return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==") +} + /** * node: node that we may traverse/visit * head: path element expression to match against diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go index 70ecfb7d..4040e506 100644 --- a/pkg/yqlib/read_navigation_strategy.go +++ b/pkg/yqlib/read_navigation_strategy.go @@ -3,6 +3,7 @@ package yqlib func ReadNavigationStrategy() NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, + pathParser: NewPathParser(), followAlias: func(nodeContext NodeContext) bool { return true }, diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go index e6be8b71..9cb4ac92 100644 --- a/pkg/yqlib/update_navigation_strategy.go +++ b/pkg/yqlib/update_navigation_strategy.go @@ -3,6 +3,7 @@ package yqlib func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, + pathParser: NewPathParser(), followAlias: func(nodeContext NodeContext) bool { return false }, From a3f8f9df10f46076dc5e02f556183c9ff1dc92b3 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 19:38:16 +1100 Subject: [PATCH 57/68] more bits --- commands_test.go | 12 ++++++++++++ yq.go | 3 +++ 2 files changed, 15 insertions(+) diff --git a/commands_test.go b/commands_test.go index d7cebecb..bacc4960 100644 --- a/commands_test.go +++ b/commands_test.go @@ -103,6 +103,18 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) { test.AssertResult(t, "4", result.Output) } +func TestReadWithAdvancedFilterMapCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fred]") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `name: fred +value: 3 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestReadWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c") diff --git a/yq.go b/yq.go index 931be12a..2a821756 100644 --- a/yq.go +++ b/yq.go @@ -100,6 +100,7 @@ yq read things.yaml a.b.c yq r - a.b.c (reads from stdin) yq r things.yaml a.*.c yq r things.yaml a.**.c +yq r things.yaml a.(child.subchild==cool).c yq r -d1 things.yaml 'a.array[0].blah' yq r things.yaml 'a.array[*].blah' yq r -- things.yaml --key-starting-with-dashes.blah @@ -121,6 +122,7 @@ func createWriteCmd() *cobra.Command { yq write things.yaml a.b.c true yq write things.yaml 'a.*.c' true yq write things.yaml 'a.**' true +yq write things.yaml a.(child.subchild==cool).c true yq write things.yaml a.b.c --tag '!!str' true yq write things.yaml a.b.c --tag '!!float' 3 yq write --inplace -- things.yaml a.b.c --cat @@ -187,6 +189,7 @@ func createDeleteCmd() *cobra.Command { Example: ` yq delete things.yaml a.b.c yq delete things.yaml a.*.c +yq delete things.yaml a.(child.subchild==cool).c yq delete things.yaml a.** yq delete --inplace things.yaml a.b.c yq delete --inplace -- things.yaml --key-starting-with-dash From 350a8343e9cc6e165afc208922505c8250cfe156 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 11 Jan 2020 19:52:33 +1100 Subject: [PATCH 58/68] adv search with prefix! --- commands_test.go | 2 +- .../filter_matching_node_navigation_strategy.go | 2 +- pkg/yqlib/path_parser.go | 17 +++++++++++------ yq.go | 6 +++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/commands_test.go b/commands_test.go index bacc4960..9a514d66 100644 --- a/commands_test.go +++ b/commands_test.go @@ -105,7 +105,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) { func TestReadWithAdvancedFilterMapCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fred]") + result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fr*]") if result.Error != nil { t.Error(result.Error) } diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go index ad4ede0c..ac49e1c4 100644 --- a/pkg/yqlib/filter_matching_node_navigation_strategy.go +++ b/pkg/yqlib/filter_matching_node_navigation_strategy.go @@ -15,7 +15,7 @@ func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy { }, shouldVisitExtraFn: func(nodeContext NodeContext) bool { log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value) - return nodeContext.Node.Value == value + return matchesString(value, nodeContext.Node.Value) }, } } diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 38464522..a06ff048 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -17,6 +17,15 @@ func NewPathParser() PathParser { return &pathParser{} } +func matchesString(expression string, value string) bool { + var prefixMatch = strings.TrimSuffix(expression, "*") + if prefixMatch != expression { + log.Debug("prefix match, %v", strings.HasPrefix(value, prefixMatch)) + return strings.HasPrefix(value, prefixMatch) + } + return value == expression +} + func (p *pathParser) IsPathExpression(pathElement string) bool { return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==") } @@ -60,12 +69,8 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str return true } } - var prefixMatch = strings.TrimSuffix(head, "*") - if prefixMatch != head { - log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch)) - return strings.HasPrefix(nodeKey, prefixMatch) - } - return nodeKey == head + + return matchesString(head, nodeKey) } func (p *pathParser) ParsePath(path string) []string { diff --git a/yq.go b/yq.go index 2a821756..fc160538 100644 --- a/yq.go +++ b/yq.go @@ -100,7 +100,7 @@ yq read things.yaml a.b.c yq r - a.b.c (reads from stdin) yq r things.yaml a.*.c yq r things.yaml a.**.c -yq r things.yaml a.(child.subchild==cool).c +yq r things.yaml a.(child.subchild==co*).c yq r -d1 things.yaml 'a.array[0].blah' yq r things.yaml 'a.array[*].blah' yq r -- things.yaml --key-starting-with-dashes.blah @@ -122,7 +122,7 @@ func createWriteCmd() *cobra.Command { yq write things.yaml a.b.c true yq write things.yaml 'a.*.c' true yq write things.yaml 'a.**' true -yq write things.yaml a.(child.subchild==cool).c true +yq write things.yaml a.(child.subchild==co*).c true yq write things.yaml a.b.c --tag '!!str' true yq write things.yaml a.b.c --tag '!!float' 3 yq write --inplace -- things.yaml a.b.c --cat @@ -189,7 +189,7 @@ func createDeleteCmd() *cobra.Command { Example: ` yq delete things.yaml a.b.c yq delete things.yaml a.*.c -yq delete things.yaml a.(child.subchild==cool).c +yq delete things.yaml a.(child.subchild==co*).c yq delete things.yaml a.** yq delete --inplace things.yaml a.b.c yq delete --inplace -- things.yaml --key-starting-with-dash From 2d7be26ad53c22d78eb05c1add71eaca9f045033 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 13 Jan 2020 16:58:11 +1100 Subject: [PATCH 59/68] wip update docs --- README.md | 2 +- docs/404.html | 12 + docs/convert/index.html | 33 +- docs/create/index.html | 82 +-- docs/delete/index.html | 261 +------ docs/index.html | 14 +- docs/merge/index.html | 13 +- docs/path_expressions/index.html | 862 +++++++++++++++++++++++ docs/prefix/index.html | 18 +- docs/read/index.html | 72 +- docs/search/search_index.json | 2 +- docs/sitemap.xml | 21 +- docs/sitemap.xml.gz | Bin 201 -> 201 bytes docs/snippets/niche/index.html | 447 ------------ docs/snippets/works_with_json/index.html | 385 ---------- docs/write/index.html | 72 +- examples/sample.yaml | 22 +- mkdocs.yml | 1 + mkdocs/convert.md | 28 +- mkdocs/create.md | 29 +- mkdocs/delete.md | 141 +--- mkdocs/index.md | 2 +- mkdocs/merge.md | 29 +- mkdocs/path_expressions.md | 208 ++++++ mkdocs/prefix.md | 39 +- mkdocs/read.md | 105 +-- mkdocs/snippets/niche.md | 35 - mkdocs/snippets/works_with_json.md | 1 - mkdocs/write.md | 137 +--- yq.go | 77 +- 30 files changed, 1401 insertions(+), 1749 deletions(-) create mode 100644 docs/path_expressions/index.html delete mode 100644 docs/snippets/niche/index.html delete mode 100644 docs/snippets/works_with_json/index.html create mode 100644 mkdocs/path_expressions.md delete mode 100644 mkdocs/snippets/niche.md delete mode 100644 mkdocs/snippets/works_with_json.md diff --git a/README.md b/README.md index 69f3c5df..c64d83e8 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ sudo apt install yq -y ``` ### or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: ``` -GO111MODULE=on go get github.com/mikefarah/yq/v2 +GO111MODULE=on go get github.com/mikefarah/yq/v3 ``` ## Run with Docker diff --git a/docs/404.html b/docs/404.html index d9cfab02..a6337614 100644 --- a/docs/404.html +++ b/docs/404.html @@ -248,6 +248,18 @@ +
  • + + Path Expressions + +
  • + + + + + + +
  • Write/Update diff --git a/docs/convert/index.html b/docs/convert/index.html index cc2f4922..7ed7ef07 100644 --- a/docs/convert/index.html +++ b/docs/convert/index.html @@ -252,6 +252,18 @@ +
  • + + Path Expressions + +
  • + + + + + + +
  • Write/Update @@ -414,20 +426,37 @@

    Convert

    Yaml to Json

    -

    To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command.

    +

    To convert output to json, use the --tojson (or -j) flag. This is supported by all commands.

    +

    Each matching yaml node will be converted to json and printed out on a separate line.

    Given a sample.yaml file of:

    b:
       c: 2
     

    then

    -
    yq r -j sample.yaml b.c
    +
    yq r -j sample.yaml
     

    will output

    {"b":{"c":2}}
     
    +

    Given a sample.yaml file of:

    +
    bob:
    +  c: 2
    +bab:
    +  c: 5
    +
    + +

    then

    +
    yq r -j sample.yaml b*
    +
    + +

    will output

    +
    {"c":2}
    +{"c":5}
    +
    +

    Json to Yaml

    To read in json, just pass in a json file instead of yaml, it will just work :)

    e.g given a json file

    diff --git a/docs/create/index.html b/docs/create/index.html index 643060d3..7114b530 100644 --- a/docs/create/index.html +++ b/docs/create/index.html @@ -252,6 +252,18 @@ +
  • + + Path Expressions + +
  • + + + + + + +
  • Write/Update @@ -325,20 +337,6 @@
  • -
  • - - Keys with dots - - -
  • - -
  • - - Keys (and values) with leading dashes - - -
  • - @@ -406,20 +404,6 @@
  • -
  • - - Keys with dots - - -
  • - -
  • - - Keys (and values) with leading dashes - - -
  • - @@ -441,10 +425,11 @@

    Create

    -

    Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.

    -
    yq n <path> <new value>
    +                
    yq n <path_expression> <new value>
     
    +

    Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.

    +

    See docs for path expression

    Creating a simple yaml file

    yq n b.c cat
     
    @@ -457,8 +442,11 @@

    Creating using a create script

    Create scripts follow the same format as the update scripts.

    Given a script create_instructions.yaml of:

    -
    b.c: 3
    -b.e[+].name: Howdy Partner
    +
    - command: update 
    +  path: b.c
    +  value:
    +    #great 
    +    things: frog # wow!
     

    then

    @@ -467,38 +455,14 @@ b.e[+].name: Howdy Partner

    will output:

    b:
    -  c: 3
    -  e:
    -    - name: Howdy Partner
    +  c:
    +    #great
    +    things: frog # wow!
     

    You can also pipe the instructions in:

    cat create_instructions.yaml | yq n -s -
     
    - -

    Keys with dots

    -

    When specifying a key that has a dot use key lookup indicator.

    -
    b:
    -  foo.bar: 7
    -
    - -
    yaml r sample.yaml 'b[foo.bar]'
    -
    - -
    yaml w sample.yaml 'b[foo.bar]' 9
    -
    - -

    Any valid yaml key can be specified as part of a key lookup.

    -

    Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

    -

    Keys (and values) with leading dashes

    -

    If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).

    -

    To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:

    -
    yq n -t -- --key --value
    -
    - -

    Will result in

    -

    ` ---key: --value

    diff --git a/docs/delete/index.html b/docs/delete/index.html index e23d0abc..42b4d97f 100644 --- a/docs/delete/index.html +++ b/docs/delete/index.html @@ -94,7 +94,7 @@ - + Skip to content @@ -252,6 +252,18 @@ +
  • + + Path Expressions + +
  • + + + + + + +
  • Write/Update @@ -299,25 +311,11 @@
      -
    • - - To Stdout - - -
    • -
    • From STDIN -
    • - -
    • - - Deleting array elements - -
    • @@ -325,27 +323,6 @@ Deleting nodes in-place -
    • - -
    • - - Splat - - -
    • - -
    • - - Prefix Splat - - -
    • - -
    • - - Array Splat - -
    • @@ -362,20 +339,6 @@
    • -
    • - - Keys with dots - - -
    • - -
    • - - Keys (and values) with leading dashes - - -
    • - @@ -441,25 +404,11 @@
        -
      • - - To Stdout - - -
      • -
      • From STDIN -
      • - -
      • - - Deleting array elements - -
      • @@ -467,27 +416,6 @@ Deleting nodes in-place -
      • - -
      • - - Splat - - -
      • - -
      • - - Prefix Splat - - -
      • - -
      • - - Array Splat - -
      • @@ -504,20 +432,6 @@
      • -
      • - - Keys with dots - - -
      • - -
      • - - Keys (and values) with leading dashes - - -
      • - @@ -539,49 +453,16 @@

        Delete

        -
        yq d <yaml_file> <path_to_delete>
        -
        - -

        To Stdout

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -  apples: green
        -
        - -

        then

        -
        yq d sample.yaml b.c
        -
        - -

        will output:

        -
        b:
        -  apples: green
        +                
        yq delete <yaml_file|-> <path_expression>
         
        +

        The delete command will delete all the matching nodes for the path expression in the given yaml input.

        +

        See docs for path expression for more details.

        From STDIN

        +

        Use "-" (without quotes) inplace of a file name if you wish to pipe in input from STDIN.

        cat sample.yaml | yq d - b.c
         
        -

        Deleting array elements

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 
        -    - 1
        -    - 2
        -    - 3
        -
        - -

        then

        -
        yq d sample.yaml 'b.c[1]'
        -
        - -

        will output:

        -
        b:
        -  c:
        -  - 1
        -  - 3
        -
        -

        Deleting nodes in-place

        Given a sample.yaml file of:

        b:
        @@ -594,91 +475,6 @@
         

        will update the sample.yaml file so that the 'c' node is deleted

        -

        Splat

        -

        Given a sample.yaml file of:

        -
        ---
        -bob:
        -  item1:
        -    cats: bananas
        -    dogs: woof
        -  item2:
        -    cats: apples
        -    dogs: woof2
        -  thing:
        -    cats: oranges
        -    dogs: woof3
        -
        - -

        then

        -
        yq d sample.yaml bob.*.cats
        -
        - -

        will output:

        -
        ---
        -bob:
        -  item1:
        -    dogs: woof
        -  item2:
        -    dogs: woof2
        -  thing:
        -    dogs: woof3
        -
        - -

        Prefix Splat

        -

        Given a sample.yaml file of:

        -
        ---
        -bob:
        -  item1:
        -    cats: bananas
        -    dogs: woof
        -  item2:
        -    cats: apples
        -    dogs: woof2
        -  thing:
        -    cats: oranges
        -    dogs: woof3
        -
        - -

        then

        -
        yq d sample.yaml bob.item*.cats
        -
        - -

        will output:

        -
        ---
        -bob:
        -  item1:
        -    dogs: woof
        -  item2:
        -    dogs: woof2
        -  thing:
        -    cats: oranges
        -    dogs: woof3
        -
        - -

        Array Splat

        -

        Given a sample.yaml file of:

        -
        ---
        -bob:
        -- cats: bananas
        -  dogs: woof
        -- cats: apples
        -  dogs: woof2
        -- cats: oranges
        -  dogs: woof3
        -
        - -

        then

        -
        yq d sample.yaml bob.[*].cats
        -
        - -

        will output:

        -
        ---
        -bob:
        -- dogs: woof
        -- dogs: woof2
        -- dogs: woof3
        -
        -

        Multiple Documents - delete from single document

        Given a sample.yaml file of:

        something: else
        @@ -723,29 +519,6 @@ b:
         

        Note that '*' is in quotes to avoid being interpreted by your shell.

        -

        Keys with dots

        -

        When specifying a key that has a dot use key lookup indicator.

        -
        b:
        -  foo.bar: 7
        -
        - -
        yaml r sample.yaml 'b[foo.bar]'
        -
        - -
        yaml w sample.yaml 'b[foo.bar]' 9
        -
        - -

        Any valid yaml key can be specified as part of a key lookup.

        -

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Keys (and values) with leading dashes

        -

        If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).

        -

        To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:

        -
        yq n -t -- --key --value
        -
        - -

        Will result in

        -

        ` ---key: --value

        diff --git a/docs/index.html b/docs/index.html index 4c1b3d95..3528e93d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -290,6 +290,18 @@ +
      • + + Path Expressions + +
      • + + + + + + +
      • Write/Update @@ -422,7 +434,7 @@ sudo apt install yq -y
      • or, Download latest binary or alternatively:

        -
        go get gopkg.in/mikefarah/yq.v2
        +
        GO111MODULE=on go get github.com/mikefarah/yq/v3
         

        View on GitHub

        diff --git a/docs/merge/index.html b/docs/merge/index.html index ce5fa44a..0d30aee9 100644 --- a/docs/merge/index.html +++ b/docs/merge/index.html @@ -252,6 +252,18 @@ +
      • + + Path Expressions + +
      • + + + + + + +
      • Write/Update @@ -608,7 +620,6 @@ d: hi
      • Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).

        -

        Append cannot be used with overwrite, if both flags are given then append is ignored.

        Multiple Documents - merge into single document

        Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected.

        Given a data1.yaml file of:

        diff --git a/docs/path_expressions/index.html b/docs/path_expressions/index.html new file mode 100644 index 00000000..1b36cb77 --- /dev/null +++ b/docs/path_expressions/index.html @@ -0,0 +1,862 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path Expressions - Yq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to content + + + +
        + +
        + +
        + + + + +
        +
        + + +
        +
        +
        + +
        +
        +
        + + +
        +
        +
        + + +
        +
        +
        + + +
        +
        + + + + + +

        Path Expressions

        + +

        Path expressions are used to deeply navigate and match particular yaml nodes.

        +

        As a general rule, you should wrap paths in quotes in the CLI to prevent your interpreter from processing '*, []' and other special characters.

        +

        Simple expressions

        +

        Maps

        +

        a.b.c

        +
        a:
        +  b:
        +    c: thing # MATCHES
        +
        + +

        Arrays

        +

        a.b[1].c

        +
        a:
        +  b:
        +  - c: thing0 
        +  - c: thing1 # MATCHES
        +  - c: thing2
        +
        + +

        Appending to arrays

        +

        (e.g. when using the write command)

        +

        a.b[+].c

        +
        a:
        +  b:
        +  - c: thing0 
        +
        + +

        Will add a new entry:

        +
        a:
        +  b:
        +  - c: thing0 
        +  - c: thing1 # NEW entry from [+] on B array.
        +
        + +

        Splat

        +

        Maps

        +

        a.*.c

        +
        a:
        +  b1:
        +    c: thing # MATCHES
        +  b2:
        +    c: thing # MATCHES
        +
        + +

        Arrays

        +

        a.b[*].c

        +
        a:
        +  b:
        +  - c: thing0 # MATCHES
        +  - c: thing1 # MATCHES
        +  - c: thing2 # MATCHES
        +
        + +

        Deep Splat

        +

        '**' will match arbitrary nodes for both maps and arrays:

        +

        a.**.c

        +
        a:
        +  b1:
        +    c: thing1 # MATCHES
        +  b2:
        +    c: thing2 # MATCHES
        +  b3:
        +    d:
        +    - f:
        +        c: thing3 # MATCHES
        +    - f:
        +        g:
        +          c: thing4 # MATCHES
        +
        + +

        Finding parents with particular children nodes

        +

        a.(b.d==cat).b.c

        +
        a:
        +  - b:
        +      c: thing0
        +      d: leopard
        +    ba: fast
        +  - b:
        +      c: thing1 # MATCHES
        +      d: cat
        +    ba: meowy
        +  - b:
        +      c: thing2
        +      d: caterpillar
        +    ba: icky
        +  - b:
        +      c: thing3 # MATCHES
        +      d: cat
        +    ba: also meowy
        +
        + +

        With prefixes

        +

        a.(b.d==cat*).c

        +
        a:
        +  - b:
        +      c: thing0
        +      d: leopard
        +    ba: fast
        +  - b:
        +      c: thing1 # MATCHES
        +      d: cat
        +    ba: meowy
        +  - b:
        +      c: thing2 # MATCHES
        +      d: caterpillar
        +    ba: icky
        +  - b:
        +      c: thing3 # MATCHES
        +      d: cat
        +    ba: also meowy
        +
        + +

        Special Characters

        +

        Keys with dots

        +

        When specifying a key that has a dot use key lookup indicator.

        +
        b:
        +  foo.bar: 7
        +
        + +
        yaml r sample.yaml 'b[foo.bar]'
        +
        + +
        yaml w sample.yaml 'b[foo.bar]' 9
        +
        + +

        Any valid yaml key can be specified as part of a key lookup.

        +

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        +

        Keys (and values) with leading dashes

        +

        If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).

        +

        To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:

        +
        yq n -t -- --key --value
        +
        + +

        Will result in

        +
        --key: --value
        +
        + + + + + + + + + +
        +
        +
        +
        + + + + +
        + + + + + + + + \ No newline at end of file diff --git a/docs/prefix/index.html b/docs/prefix/index.html index 44c83045..e83b0179 100644 --- a/docs/prefix/index.html +++ b/docs/prefix/index.html @@ -252,6 +252,18 @@ +
      • + + Path Expressions + +
      • + + + + + + +
      • Write/Update @@ -455,11 +467,11 @@

        Prefix

        -

        Paths can be prefixed using the 'prefix' command. -The complete yaml content will be nested inside the new prefix path.

        -
        yq p <yaml_file> <path>
        +                
        yq p <yaml_file> <path>
         
        +

        Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path.

        +

        See docs for path expression for more details.

        To Stdout

        Given a data1.yaml file of:

        a: simple
        diff --git a/docs/read/index.html b/docs/read/index.html
        index 504b7296..9383334b 100644
        --- a/docs/read/index.html
        +++ b/docs/read/index.html
        @@ -319,20 +319,6 @@
           
         
      • -
      • - - Keys with dots - - -
      • - -
      • - - Keys (and values) with leading dashes - - -
      • - @@ -349,6 +335,18 @@ +
      • + + Path Expressions + +
      • + + + + + + +
      • Write/Update @@ -490,20 +488,6 @@
      • -
      • - - Keys with dots - - -
      • - -
      • - - Keys (and values) with leading dashes - - -
      • - @@ -525,10 +509,11 @@

        Read

        -
        yq r <yaml_file|json_file> <path>
        +                
        yq r <yaml_file|json_file> <path_expression>
         
        -

        This command can take a json file as input too, and will output yaml unless specified to export as json (-j)

        +

        Returns the matching nodes of the path expression for the given yaml file (or STDIN).

        +

        See docs for path expression for more details.

        Basic

        Given a sample.yaml file of:

        b:
        @@ -662,29 +647,6 @@ e.g.: given a sample file of

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Keys with dots

        -

        When specifying a key that has a dot use key lookup indicator.

        -
        b:
        -  foo.bar: 7
        -
        - -
        yaml r sample.yaml 'b[foo.bar]'
        -
        - -
        yaml w sample.yaml 'b[foo.bar]' 9
        -
        - -

        Any valid yaml key can be specified as part of a key lookup.

        -

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Keys (and values) with leading dashes

        -

        If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).

        -

        To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:

        -
        yq n -t -- --key --value
        -
        - -

        Will result in

        -

        ` ---key: --value

        @@ -720,13 +682,13 @@ e.g.: given a sample file of

        -
        - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Niche

        - -

        Keys with dots

        -

        When specifying a key that has a dot use key lookup indicator.

        -
        b:
        -  foo.bar: 7
        -
        - -
        yaml r sample.yaml 'b[foo.bar]'
        -
        - -
        yaml w sample.yaml 'b[foo.bar]' 9
        -
        - -

        Any valid yaml key can be specified as part of a key lookup.

        -

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Keys (and values) with leading dashes

        -

        If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).

        -

        To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:

        -
        yq n -t -- --key --value
        -
        - -

        Will result in

        -
        --key: --value
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/snippets/works_with_json/index.html b/docs/snippets/works_with_json/index.html deleted file mode 100644 index 173e64ba..00000000 --- a/docs/snippets/works_with_json/index.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Works with json - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - - -
        -
        - - - - - -

        Works with json

        - -

        This command can take a json file as input too, and will output yaml unless specified to export as json (-j)

        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/write/index.html b/docs/write/index.html index d094d64e..7d556779 100644 --- a/docs/write/index.html +++ b/docs/write/index.html @@ -251,6 +251,18 @@ + +
      • + + Path Expressions + +
      • + + + + + + @@ -359,20 +371,6 @@ -
      • - - Keys with dots - - -
      • - -
      • - - Keys (and values) with leading dashes - - -
      • - @@ -546,20 +544,6 @@ -
      • - - Keys with dots - - -
      • - -
      • - - Keys (and values) with leading dashes - - -
      • - @@ -581,9 +565,11 @@

        Write/Update

        -
        yq w <yaml_file> <path> <new value>
        +                
        yq w <yaml_file> <path_expression> <new value>
         
        +

        Updates all the matching nodes of path expression to the supplied value.

        +

        See docs for path expression for more details.

        To Stdout

        Given a sample.yaml file of:

        b:
        @@ -808,30 +794,6 @@ b.e[+].name: Howdy Partner
         
        my:
           path: -3
         
        - -

        Keys with dots

        -

        When specifying a key that has a dot use key lookup indicator.

        -
        b:
        -  foo.bar: 7
        -
        - -
        yaml r sample.yaml 'b[foo.bar]'
        -
        - -
        yaml w sample.yaml 'b[foo.bar]' 9
        -
        - -

        Any valid yaml key can be specified as part of a key lookup.

        -

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Keys (and values) with leading dashes

        -

        If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).

        -

        To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:

        -
        yq n -t -- --key --value
        -
        - -

        Will result in

        -

        ` ---key: --value

        @@ -852,7 +814,7 @@ b.e[+].name: Howdy Partner diff --git a/examples/sample.yaml b/examples/sample.yaml index 1fb93352..4540dde4 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,9 +1,13 @@ -a: true -b: - c: 2 - d: [3, 4, 5] - e: - - name: fred - value: 3 - - name: sam - value: 4 \ No newline at end of file +bob: + item: + cats: bananas + something: + cats: lemons + itemThing: + cats: bananas + item2: + cats: apples + thing: + cats: oranges +my: + path: -3 diff --git a/mkdocs.yml b/mkdocs.yml index 99164cbf..d2d651d4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,6 +5,7 @@ theme: 'material' pages: - Install: index.md - Read: read.md + - Path Expressions: path_expressions.md - Write/Update: write.md - Prefix: prefix.md - Delete: delete.md diff --git a/mkdocs/convert.md b/mkdocs/convert.md index 369bc576..eb159335 100644 --- a/mkdocs/convert.md +++ b/mkdocs/convert.md @@ -1,5 +1,7 @@ -### Yaml to Json -To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command. +## Yaml to Json +To convert output to json, use the --tojson (or -j) flag. This is supported by all commands. + +Each matching yaml node will be converted to json and printed out on a separate line. Given a sample.yaml file of: ```yaml @@ -8,7 +10,7 @@ b: ``` then ```bash -yq r -j sample.yaml b.c +yq r -j sample.yaml ``` will output @@ -16,7 +18,25 @@ will output {"b":{"c":2}} ``` -### Json to Yaml +Given a sample.yaml file of: +```yaml +bob: + c: 2 +bab: + c: 5 +``` +then +```bash +yq r -j sample.yaml b* +``` + +will output +```json +{"c":2} +{"c":5} +``` + +## Json to Yaml To read in json, just pass in a json file instead of yaml, it will just work :) e.g given a json file diff --git a/mkdocs/create.md b/mkdocs/create.md index 302c01e1..457e29bc 100644 --- a/mkdocs/create.md +++ b/mkdocs/create.md @@ -1,10 +1,12 @@ +``` +yq n +``` + Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file. -``` -yq n -``` +See docs for [path expression](path_expressions.md) -### Creating a simple yaml file +## Creating a simple yaml file ```bash yq n b.c cat ``` @@ -14,13 +16,16 @@ b: c: cat ``` -### Creating using a create script +## Creating using a create script Create scripts follow the same format as the update scripts. Given a script create_instructions.yaml of: ```yaml -b.c: 3 -b.e[+].name: Howdy Partner +- command: update + path: b.c + value: + #great + things: frog # wow! ``` then @@ -30,15 +35,13 @@ yq n -s create_instructions.yaml will output: ```yaml b: - c: 3 - e: - - name: Howdy Partner + c: + #great + things: frog # wow! ``` You can also pipe the instructions in: ```bash cat create_instructions.yaml | yq n -s - -``` - -{!snippets/niche.md!} +``` \ No newline at end of file diff --git a/mkdocs/delete.md b/mkdocs/delete.md index f1f7883e..c80548e5 100644 --- a/mkdocs/delete.md +++ b/mkdocs/delete.md @@ -1,8 +1,13 @@ ``` -yq d +yq delete ``` -### To Stdout +The delete command will delete all the matching nodes for the path expression in the given yaml input. + +See docs for [path expression](path_expressions.md) for more details. + + +## Deleting from a simple document Given a sample.yaml file of: ```yaml b: @@ -13,141 +18,29 @@ then ```bash yq d sample.yaml b.c ``` -will output: +will output ```yaml b: apples: green ``` -### From STDIN +## From STDIN +Use "-" (without quotes) in-place of a file name if you wish to pipe in input from STDIN. + ```bash cat sample.yaml | yq d - b.c ``` -### Deleting array elements -Given a sample.yaml file of: -```yaml -b: - c: - - 1 - - 2 - - 3 -``` -then -```bash -yq d sample.yaml 'b.c[1]' -``` -will output: -```yaml -b: - c: - - 1 - - 3 -``` - -### Deleting nodes in-place -Given a sample.yaml file of: -```yaml -b: - c: 2 - apples: green -``` -then +## Deleting in-place ```bash yq d -i sample.yaml b.c ``` will update the sample.yaml file so that the 'c' node is deleted -### Splat -Given a sample.yaml file of: -```yaml ---- -bob: - item1: - cats: bananas - dogs: woof - item2: - cats: apples - dogs: woof2 - thing: - cats: oranges - dogs: woof3 -``` -then -```bash -yq d sample.yaml bob.*.cats -``` -will output: -```yaml ---- -bob: - item1: - dogs: woof - item2: - dogs: woof2 - thing: - dogs: woof3 -``` +## Multiple Documents -### Prefix Splat -Given a sample.yaml file of: -```yaml ---- -bob: - item1: - cats: bananas - dogs: woof - item2: - cats: apples - dogs: woof2 - thing: - cats: oranges - dogs: woof3 -``` -then -```bash -yq d sample.yaml bob.item*.cats -``` -will output: -```yaml ---- -bob: - item1: - dogs: woof - item2: - dogs: woof2 - thing: - cats: oranges - dogs: woof3 -``` - -### Array Splat -Given a sample.yaml file of: -```yaml ---- -bob: -- cats: bananas - dogs: woof -- cats: apples - dogs: woof2 -- cats: oranges - dogs: woof3 -``` -then -```bash -yq d sample.yaml bob.[*].cats -``` -will output: -```yaml ---- -bob: -- dogs: woof -- dogs: woof2 -- dogs: woof3 -``` - -### Multiple Documents - delete from single document +### Delete from single document Given a sample.yaml file of: ```yaml something: else @@ -170,7 +63,7 @@ b: c: 2 ``` -### Multiple Documents - delete from all documents +### Delete from all documents Given a sample.yaml file of: ```yaml something: else @@ -191,7 +84,3 @@ something: else b: c: 2 ``` - -Note that '*' is in quotes to avoid being interpreted by your shell. - -{!snippets/niche.md!} diff --git a/mkdocs/index.md b/mkdocs/index.md index 455058ea..50df4490 100644 --- a/mkdocs/index.md +++ b/mkdocs/index.md @@ -20,7 +20,7 @@ sudo apt install yq -y ``` or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: ``` -go get gopkg.in/mikefarah/yq.v2 +GO111MODULE=on go get github.com/mikefarah/yq/v3 ``` [View on GitHub](https://github.com/mikefarah/yq) diff --git a/mkdocs/merge.md b/mkdocs/merge.md index 41d7740f..22aa79a8 100644 --- a/mkdocs/merge.md +++ b/mkdocs/merge.md @@ -6,7 +6,7 @@ yq m ... ``` -### To Stdout +## Merge example Given a data1.yaml file of: ```yaml a: simple @@ -30,25 +30,13 @@ c: test: 1 ``` -### Updating files in-place -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -``` -and data2.yaml file of: -```yaml -a: other -c: - test: 1 -``` -then +## Updating files in-place ```bash yq m -i data1.yaml data2.yaml ``` -will update the data1.yaml file so that the value of 'c' is 'test: 1'. +will update the data1.yaml file with the merged result. -### Overwrite values +## Overwrite values Given a data1.yaml file of: ```yaml a: simple @@ -102,7 +90,7 @@ d: false Notice that 'b' does not result in the merging of the values within an array. -### Append values with arrays +## Append values with arrays Given a data1.yaml file of: ```yaml a: simple @@ -133,9 +121,8 @@ d: hi Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a). -Append cannot be used with overwrite, if both flags are given then append is ignored. - -### Multiple Documents - merge into single document +## Multiple Documents +### Merge into single document Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected. Given a data1.yaml file of: @@ -161,7 +148,7 @@ a: simple b: dog ``` -### Multiple Documents - merge into all documents +### Merge into all documents Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected. Given a data1.yaml file of: diff --git a/mkdocs/path_expressions.md b/mkdocs/path_expressions.md new file mode 100644 index 00000000..8fc038c7 --- /dev/null +++ b/mkdocs/path_expressions.md @@ -0,0 +1,208 @@ +Path expressions are used to deeply navigate and match particular yaml nodes. + +_As a general rule, you should wrap paths in quotes to prevent your CLI from processing '*, []' and other special characters._ + +## Simple expressions + +### Maps + +a.b.c + +```yaml +a: + b: + c: thing # MATCHES +``` + +### Arrays + +a.b[1].c + +```yaml +a: + b: + - c: thing0 + - c: thing1 # MATCHES + - c: thing2 +``` + +#### Appending to arrays +(e.g. when using the write command) + +a.b[+].c + + +```yaml +a: + b: + - c: thing0 +``` + +Will add a new entry: + +```yaml +a: + b: + - c: thing0 + - c: thing1 # NEW entry from [+] on B array. +``` + + +## Splat + +### Maps +a.*.c + +```yaml +a: + b1: + c: thing # MATCHES + d: whatever + b2: + c: thing # MATCHES + f: something irrelevant +``` + +#### Prefix splat + +bob.item*.cats + +```yaml +bob: + item: + cats: bananas # MATCHES + something: + cats: lemons + itemThing: + cats: more bananas # MATCHES + item2: + cats: apples # MATCHES + thing: + cats: oranges +``` + +### Arrays +a.b[*].c + +```yaml +a: + b: + - c: thing0 # MATCHES + d: what..ever + - c: thing1 # MATCHES + d: blarh + - c: thing2 # MATCHES + f: thingamabob +``` + +## Deep Splat + +'**' will match arbitrary nodes for both maps and arrays: + +a.**.c + +```yaml +a: + b1: + c: thing1 # MATCHES + d: cat cat + b2: + c: thing2 # MATCHES + d: dog dog + b3: + d: + - f: + c: thing3 # MATCHES + d: beep + - f: + g: + c: thing4 # MATCHES + d: boop + - d: mooo +``` + + +## Search by children nodes + +a.(b.d==cat).b.c + +```yaml +a: + - b: + c: thing0 + d: leopard + ba: fast + - b: + c: thing1 # MATCHES + d: cat + ba: meowy + - b: + c: thing2 + d: caterpillar + ba: icky + - b: + c: thing3 # MATCHES + d: cat + ba: also meowy +``` + +### With prefixes + +a.(b.d==cat*).c + +```yaml +a: + - b: + c: thing0 + d: leopard + ba: fast + - b: + c: thing1 # MATCHES + d: cat + ba: meowy + - b: + c: thing2 # MATCHES + d: caterpillar + ba: icky + - b: + c: thing3 # MATCHES + d: cat + ba: also meowy +``` + + +## Special Characters + + +### Keys with dots +When specifying a key that has a dot use key lookup indicator. + +```yaml +b: + foo.bar: 7 +``` + +```bash +yaml r sample.yaml 'b[foo.bar]' +``` + +```bash +yaml w sample.yaml 'b[foo.bar]' 9 +``` + +Any valid yaml key can be specified as part of a key lookup. + +Note that the path is in quotes to avoid the square brackets being interpreted by your shell. + +### Keys (and values) with leading dashes +The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags, if they start if a dash. + +```bash +yq n -j -- --key --value +``` + +Will result in + +``` +--key: --value +``` \ No newline at end of file diff --git a/mkdocs/prefix.md b/mkdocs/prefix.md index bd374e52..76832408 100644 --- a/mkdocs/prefix.md +++ b/mkdocs/prefix.md @@ -1,28 +1,12 @@ -Paths can be prefixed using the 'prefix' command. -The complete yaml content will be nested inside the new prefix path. - ``` yq p ``` -### To Stdout -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -``` -then -```bash -yq p data1.yaml c -``` -will output: -```yaml -c: - a: simple - b: [1, 2] -``` +Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path. -### Arbitrary depth +See docs for [path expression](path_expressions.md) for more details. + +## Prefix a document Given a data1.yaml file of: ```yaml a: @@ -40,19 +24,14 @@ c: b: [1, 2] ``` -### Updating files in-place -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -``` -then +## Updating files in-place ```bash yq p -i data1.yaml c ``` -will update the data1.yaml file so that the path 'c' is prefixed to all other paths. +will update the data1.yaml file so that the path 'c' prefixes the document. -### Multiple Documents - prefix a single document +## Multiple Documents +### Prefix a single document Given a data1.yaml file of: ```yaml something: else @@ -73,7 +52,7 @@ c: b: cat ``` -### Multiple Documents - prefix all documents +### Prefix all documents Given a data1.yaml file of: ```yaml something: else diff --git a/mkdocs/read.md b/mkdocs/read.md index ca526993..124e7815 100644 --- a/mkdocs/read.md +++ b/mkdocs/read.md @@ -1,10 +1,14 @@ ``` -yq r +yq r ``` -{!snippets/works_with_json.md!} +TALK PRINTING ABOUT KEYS AND VALUES -### Basic +Returns the matching nodes of the path expression for the given yaml file (or STDIN). + +See docs for [path expression](path_expressions.md) for more details. + +## Basic Given a sample.yaml file of: ```yaml b: @@ -16,59 +20,16 @@ yq r sample.yaml b.c ``` will output the value of '2'. -### From Stdin +## From Stdin Given a sample.yaml file of: ```bash cat sample.yaml | yq r - b.c ``` will output the value of '2'. -### Splat -Given a sample.yaml file of: -```yaml ---- -bob: - item1: - cats: bananas - item2: - cats: apples - thing: - cats: oranges -``` -then -```bash -yq r sample.yaml bob.*.cats -``` -will output -```yaml -- bananas -- apples -- oranges -``` -### Prefix Splat -Given a sample.yaml file of: -```yaml ---- -bob: - item1: - cats: bananas - item2: - cats: apples - thing: - cats: oranges -``` -then -```bash -yq r sample.yaml bob.item*.cats -``` -will output -```yaml -- bananas -- apples -``` - -### Multiple Documents - specify a single document +## Multiple Documents +### Reading from a single document Given a sample.yaml file of: ```yaml something: else @@ -82,7 +43,7 @@ yq r -d1 sample.yaml b.c ``` will output the value of '2'. -### Multiple Documents - read all documents +### Read from all documents Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired. Given a sample.yaml file of: @@ -105,46 +66,4 @@ will output: - Fred - Stella - Android -``` - -### Arrays -You can give an index to access a specific element: -e.g.: given a sample file of -```yaml -b: - e: - - name: fred - value: 3 - - name: sam - value: 4 -``` -then -``` -yq r sample.yaml 'b.e[1].name' -``` -will output 'sam' - -Note that the path is in quotes to avoid the square brackets being interpreted by your shell. - -### Array Splat -e.g.: given a sample file of -```yaml -b: - e: - - name: fred - value: 3 - - name: sam - value: 4 -``` -then -``` -yq r sample.yaml 'b.e[*].name' -``` -will output: -``` -- fred -- sam -``` -Note that the path is in quotes to avoid the square brackets being interpreted by your shell. - -{!snippets/niche.md!} +``` \ No newline at end of file diff --git a/mkdocs/snippets/niche.md b/mkdocs/snippets/niche.md deleted file mode 100644 index f045519d..00000000 --- a/mkdocs/snippets/niche.md +++ /dev/null @@ -1,35 +0,0 @@ -### Keys with dots -When specifying a key that has a dot use key lookup indicator. - -```yaml -b: - foo.bar: 7 -``` - -```bash -yaml r sample.yaml 'b[foo.bar]' -``` - -```bash -yaml w sample.yaml 'b[foo.bar]' 9 -``` - -Any valid yaml key can be specified as part of a key lookup. - -Note that the path is in quotes to avoid the square brackets being interpreted by your shell. - -### Keys (and values) with leading dashes -If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error). - -To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so: - - -```bash -yq n -t -- --key --value -``` - -Will result in - -``` ---key: --value -``` \ No newline at end of file diff --git a/mkdocs/snippets/works_with_json.md b/mkdocs/snippets/works_with_json.md deleted file mode 100644 index 8786c045..00000000 --- a/mkdocs/snippets/works_with_json.md +++ /dev/null @@ -1 +0,0 @@ -This command can take a json file as input too, and will output yaml unless specified to export as json (-j) diff --git a/mkdocs/write.md b/mkdocs/write.md index 0959081c..cc6679d4 100644 --- a/mkdocs/write.md +++ b/mkdocs/write.md @@ -1,8 +1,12 @@ ``` -yq w +yq w ``` -### To Stdout +Updates all the matching nodes of path expression to the supplied value. + +See docs for [path expression](path_expressions.md) for more details. + +## Basic Given a sample.yaml file of: ```yaml b: @@ -18,12 +22,18 @@ b: c: cat ``` -### From STDIN +### Updating files in-place +```bash +yq w -i sample.yaml b.c cat +``` +will update the sample.yaml file so that the value of 'c' is cat. + +## From STDIN ```bash cat sample.yaml | yq w - b.c blah ``` -### Adding new fields +## Adding new fields Any missing fields in the path will be created on the fly. Given a sample.yaml file of: @@ -43,85 +53,7 @@ b: - new thing ``` -### Splat -Given a sample.yaml file of: -```yaml ---- -bob: - item1: - cats: bananas - item2: - cats: apples - thing: - cats: oranges -``` -then -```bash -yq w sample.yaml bob.*.cats meow -``` -will output: -```yaml ---- -bob: - item1: - cats: meow - item2: - cats: meow - thing: - cats: meow -``` - -### Prefix Splat -Given a sample.yaml file of: -```yaml ---- -bob: - item1: - cats: bananas - item2: - cats: apples - thing: - cats: oranges -``` -then -```bash -yq w sample.yaml bob.item*.cats meow -``` -will output: -```yaml ---- -bob: - item1: - cats: meow - item2: - cats: meow - thing: - cats: oranges -``` - -### Array Splat -Given a sample.yaml file of: -```yaml ---- -bob: -- cats: bananas -- cats: apples -- cats: oranges -``` -then -```bash -yq w sample.yaml bob[*].cats meow -``` -will output: -```yaml ---- -bob: -- cats: meow -- cats: meow -- cats: meow -``` - -### Appending value to an array field +## Appending value to an array field Given a sample.yaml file of: ```yaml b: @@ -146,7 +78,8 @@ b: Note that the path is in quotes to avoid the square brackets being interpreted by your shell. -### Multiple Documents - update a single document +## Multiple Documents +### Update a single document Given a sample.yaml file of: ```yaml something: else @@ -166,7 +99,7 @@ b: c: 5 ``` -### Multiple Documents - update all documents +### Update all documents Given a sample.yaml file of: ```yaml something: else @@ -188,22 +121,11 @@ b: c: 5 ``` -Note that '*' is in quotes to avoid being interpreted by your shell. +UPDATE THIS +UPDATE THIS +INCLUDE DELETE EXAMPLE -### Updating files in-place -Given a sample.yaml file of: -```yaml -b: - c: 2 -``` -then -```bash -yq w -i sample.yaml b.c cat -``` -will update the sample.yaml file so that the value of 'c' is cat. - - -### Updating multiple values with a script +## Updating multiple values with a script Given a sample.yaml file of: ```yaml b: @@ -233,18 +155,3 @@ And, of course, you can pipe the instructions in using '-': ```bash cat update_instructions.yaml | yq w -s - sample.yaml ``` - -### Values starting with a hyphen (or dash) -The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags: - -``` -yq w -- my.path -3 -``` - -will output -```yaml -my: - path: -3 -``` - -{!snippets/niche.md!} diff --git a/yq.go b/yq.go index fc160538..ed3a65a9 100644 --- a/yq.go +++ b/yq.go @@ -92,18 +92,18 @@ func newCommandCLI() *cobra.Command { func createReadCmd() *cobra.Command { var cmdRead = &cobra.Command{ - Use: "read [yaml_file] [path]", + Use: "read [yaml_file] [path_expression]", Aliases: []string{"r"}, - Short: "yq r [--doc/-d index] sample.yaml a.b.c", + Short: "yq r [--doc/-d index] sample.yaml 'a.b.c'", Example: ` -yq read things.yaml a.b.c -yq r - a.b.c (reads from stdin) -yq r things.yaml a.*.c -yq r things.yaml a.**.c -yq r things.yaml a.(child.subchild==co*).c +yq read things.yaml 'a.b.c' +yq r - 'a.b.c' # reads from stdin +yq r things.yaml 'a.*.c' +yq r things.yaml 'a.**.c' # deep splat +yq r things.yaml 'a.(child.subchild==co*).c' yq r -d1 things.yaml 'a.array[0].blah' yq r things.yaml 'a.array[*].blah' -yq r -- things.yaml --key-starting-with-dashes.blah +yq r -- things.yaml '--key-starting-with-dashes.blah' `, Long: "Outputs the value of the given path in the yaml file to STDOUT", RunE: readProperty, @@ -115,24 +115,24 @@ yq r -- things.yaml --key-starting-with-dashes.blah func createWriteCmd() *cobra.Command { var cmdWrite = &cobra.Command{ - Use: "write [yaml_file] [path] [value]", + Use: "write [yaml_file] [path_expression] [value]", Aliases: []string{"w"}, - Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", + Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'a.b.c' newValue", Example: ` -yq write things.yaml a.b.c true +yq write things.yaml 'a.b.c' true yq write things.yaml 'a.*.c' true yq write things.yaml 'a.**' true -yq write things.yaml a.(child.subchild==co*).c true -yq write things.yaml a.b.c --tag '!!str' true -yq write things.yaml a.b.c --tag '!!float' 3 -yq write --inplace -- things.yaml a.b.c --cat -yq w -i things.yaml a.b.c cat +yq write things.yaml 'a.(child.subchild==co*).c' true +yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +yq write things.yaml 'a.b.c' --tag '!!float' 3 +yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags +yq w -i things.yaml 'a.b.c' cat yq w --script update_script.yaml things.yaml yq w -i -s update_script.yaml things.yaml -yq w --doc 2 things.yaml 'a.b.d[+]' foo -yq w -d2 things.yaml 'a.b.d[+]' foo +yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array +yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file `, - Long: `Updates the yaml file w.r.t the given path and value. + Long: `Updates the matching nodes of path expression to the specified value Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Append value to array adds the value to the end of array. @@ -164,12 +164,12 @@ func createPrefixCmd() *cobra.Command { Aliases: []string{"p"}, Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", Example: ` -yq prefix things.yaml a.b.c -yq prefix --inplace things.yaml a.b.c -yq prefix --inplace -- things.yaml --key-starting-with-dash -yq p -i things.yaml a.b.c -yq p --doc 2 things.yaml a.b.d -yq p -d2 things.yaml a.b.d +yq prefix things.yaml 'a.b.c' +yq prefix --inplace things.yaml 'a.b.c' +yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +yq p -i things.yaml 'a.b.c' +yq p --doc 2 things.yaml 'a.b.d' +yq p -d2 things.yaml 'a.b.d' `, Long: `Prefixes w.r.t to the yaml file at the given path. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. @@ -183,20 +183,19 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd func createDeleteCmd() *cobra.Command { var cmdDelete = &cobra.Command{ - Use: "delete [yaml_file] [path]", + Use: "delete [yaml_file] [path_expression]", Aliases: []string{"d"}, Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", Example: ` -yq delete things.yaml a.b.c -yq delete things.yaml a.*.c -yq delete things.yaml a.(child.subchild==co*).c -yq delete things.yaml a.** -yq delete --inplace things.yaml a.b.c -yq delete --inplace -- things.yaml --key-starting-with-dash -yq d -i things.yaml a.b.c -yq d things.yaml a.b.c +yq delete things.yaml 'a.b.c' +yq delete things.yaml 'a.*.c' +yq delete things.yaml 'a.(child.subchild==co*).c' +yq delete things.yaml 'a.**' +yq delete --inplace things.yaml 'a.b.c' +yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +yq d -i things.yaml 'a.b.c' `, - Long: `Deletes the given path from the YAML file. + Long: `Deletes the nodes matching the given path expression from the YAML file. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. `, RunE: deleteProperty, @@ -212,10 +211,10 @@ func createNewCmd() *cobra.Command { Aliases: []string{"n"}, Short: "yq n [--script/-s script_file] a.b.c newValue", Example: ` -yq new a.b.c cat -yq n a.b.c cat -yq n a.b[+] --tag '!!str' true -yq n -- --key-starting-with-dash cat +yq new 'a.b.c' cat +yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +yq n 'a.b[+]' cat +yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags yq n --script create_script.yaml `, Long: `Creates a new yaml w.r.t the given path and value. From d203ec7b565e8ef3bf204ae7a024d6a9ac5ec46c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 15 Jan 2020 08:55:01 +1100 Subject: [PATCH 60/68] Accidently changed sample.yaml --- examples/sample.yaml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/sample.yaml b/examples/sample.yaml index 4540dde4..1fb93352 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,13 +1,9 @@ -bob: - item: - cats: bananas - something: - cats: lemons - itemThing: - cats: bananas - item2: - cats: apples - thing: - cats: oranges -my: - path: -3 +a: true +b: + c: 2 + d: [3, 4, 5] + e: + - name: fred + value: 3 + - name: sam + value: 4 \ No newline at end of file From 56ba7c9a43ba6a78ffcccf597adac7ad34aeb47a Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 15 Jan 2020 09:01:08 +1100 Subject: [PATCH 61/68] Updated travis ci build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c64d83e8..a5cef938 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # yq -[![Build Status](https://travis-ci.org/mikefarah/yq.svg?branch=master)](https://travis-ci.org/mikefarah/yq) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq) +[![Build Status](https://api.travis-ci.com/mikefarah/yq.svg?branch=master)](https://travis-ci.com/mikefarah/yq/) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq) a lightweight and portable command-line YAML processor From 64d38e9f0356b892d7b1b5b45996f6c2adbbf7f3 Mon Sep 17 00:00:00 2001 From: Ryan SIU Date: Mon, 13 Jan 2020 17:11:56 +0800 Subject: [PATCH 62/68] #323 Refactor the cobra command with standard structure --- commands_test.go => cmd/commands_test.go | 98 ++-- cmd/constant.go | 22 + cmd/delete.go | 41 ++ cmd/merge.go | 61 ++ cmd/new.go | 54 ++ cmd/prefix.go | 50 ++ cmd/read.go | 52 ++ cmd/root.go | 55 ++ cmd/utils.go | 372 ++++++++++++ version.go => cmd/version.go | 2 +- version_test.go => cmd/version_test.go | 2 +- cmd/write.go | 57 ++ yq.go | 717 +---------------------- 13 files changed, 819 insertions(+), 764 deletions(-) rename commands_test.go => cmd/commands_test.go (90%) create mode 100644 cmd/constant.go create mode 100644 cmd/delete.go create mode 100644 cmd/merge.go create mode 100644 cmd/new.go create mode 100644 cmd/prefix.go create mode 100644 cmd/read.go create mode 100644 cmd/root.go create mode 100644 cmd/utils.go rename version.go => cmd/version.go (98%) rename version_test.go => cmd/version_test.go (98%) create mode 100644 cmd/write.go diff --git a/commands_test.go b/cmd/commands_test.go similarity index 90% rename from commands_test.go rename to cmd/commands_test.go index 9a514d66..592ab03c 100644 --- a/commands_test.go +++ b/cmd/commands_test.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" @@ -12,7 +12,7 @@ import ( ) func getRootCommand() *cobra.Command { - return newCommandCLI() + return New() } func TestRootCmd(t *testing.T) { @@ -87,7 +87,7 @@ func TestRootCmd_VersionLong(t *testing.T) { func TestReadCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -117,7 +117,7 @@ value: 3 func TestReadWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -126,7 +126,7 @@ func TestReadWithKeyAndValueCmd(t *testing.T) { func TestReadArrayCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.e.1.name") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.e.1.name") if result.Error != nil { t.Error(result.Error) } @@ -135,7 +135,7 @@ func TestReadArrayCmd(t *testing.T) { func TestReadDeepSplatCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.**") if result.Error != nil { t.Error(result.Error) } @@ -153,7 +153,7 @@ b.e.[1].value: 4 func TestReadDeepSplatWithSuffixCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**.name") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.**.name") if result.Error != nil { t.Error(result.Error) } @@ -165,7 +165,7 @@ b.e.[1].name: sam func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p p examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -p p ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -174,7 +174,7 @@ func TestReadWithKeyCmd(t *testing.T) { func TestReadAnchorsCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read ../examples/simple-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -183,7 +183,7 @@ func TestReadAnchorsCmd(t *testing.T) { func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/simple-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read -p pv ../examples/simple-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -192,7 +192,7 @@ func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { func TestReadMergeAnchorsOriginalCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -201,7 +201,7 @@ func TestReadMergeAnchorsOriginalCmd(t *testing.T) { func TestReadMergeAnchorsOverrideCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.thing") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobar.thing") if result.Error != nil { t.Error(result.Error) } @@ -210,7 +210,7 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) { func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "r -p pv examples/merge-anchor.yaml foobar.th*") + result := test.RunCmd(cmd, "r -p pv ../examples/merge-anchor.yaml foobar.th*") if result.Error != nil { t.Error(result.Error) } @@ -223,7 +223,7 @@ foobar.thirsty: yep func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.a") if result.Error != nil { t.Error(result.Error) } @@ -232,7 +232,7 @@ func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.thing") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.thing") if result.Error != nil { t.Error(result.Error) } @@ -241,7 +241,7 @@ func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.c") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.c") if result.Error != nil { t.Error(result.Error) } @@ -250,7 +250,7 @@ func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -df ../examples/sample.yaml b.c") if result.Error == nil { t.Error("Expected command to fail due to invalid path") } @@ -260,7 +260,7 @@ func TestReadInvalidDocumentIndexCmd(t *testing.T) { func TestReadBadDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d1 examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -d1 ../examples/sample.yaml b.c") if result.Error == nil { t.Error("Expected command to fail due to invalid path") } @@ -270,7 +270,7 @@ func TestReadBadDocumentIndexCmd(t *testing.T) { func TestReadOrderCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/order.yaml") + result := test.RunCmd(cmd, "read ../examples/order.yaml") if result.Error != nil { t.Error(result.Error) } @@ -283,7 +283,7 @@ application: MyApp func TestReadMultiCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d 1 examples/multiple_docs.yaml another.document") + result := test.RunCmd(cmd, "read -d 1 ../examples/multiple_docs.yaml another.document") if result.Error != nil { t.Error(result.Error) } @@ -292,7 +292,7 @@ func TestReadMultiCmd(t *testing.T) { func TestReadMultiWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p vp -d 1 examples/multiple_docs.yaml another.document") + result := test.RunCmd(cmd, "read -p vp -d 1 ../examples/multiple_docs.yaml another.document") if result.Error != nil { t.Error(result.Error) } @@ -301,7 +301,7 @@ func TestReadMultiWithKeyAndValueCmd(t *testing.T) { func TestReadMultiAllCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d* examples/multiple_docs.yaml commonKey") + result := test.RunCmd(cmd, "read -d* ../examples/multiple_docs.yaml commonKey") if result.Error != nil { t.Error(result.Error) } @@ -313,7 +313,7 @@ third document`, result.Output) func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv -d* examples/multiple_docs.yaml commonKey") + result := test.RunCmd(cmd, "read -p pv -d* ../examples/multiple_docs.yaml commonKey") if result.Error != nil { t.Error(result.Error) } @@ -326,7 +326,7 @@ commonKey: third document func TestReadCmd_ArrayYaml(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [0].gather_facts") + result := test.RunCmd(cmd, "read ../examples/array.yaml [0].gather_facts") if result.Error != nil { t.Error(result.Error) } @@ -335,7 +335,7 @@ func TestReadCmd_ArrayYaml(t *testing.T) { func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml") + result := test.RunCmd(cmd, "read ../examples/array.yaml") if result.Error != nil { t.Error(result.Error) } @@ -355,7 +355,7 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { func TestReadCmd_ArrayYaml_OneElement(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [0]") + result := test.RunCmd(cmd, "read ../examples/array.yaml [0]") if result.Error != nil { t.Error(result.Error) } @@ -373,7 +373,7 @@ serial: 1 func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*]") + result := test.RunCmd(cmd, "read ../examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -393,7 +393,7 @@ gather_facts: true func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -p pv ../examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -415,7 +415,7 @@ func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p p examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -p p ../examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -426,7 +426,7 @@ func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts") + result := test.RunCmd(cmd, "read ../examples/array.yaml [*].gather_facts") if result.Error != nil { t.Error(result.Error) } @@ -437,7 +437,7 @@ true` func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") + result := test.RunCmd(cmd, "read ../examples/array.yaml [x].gather_facts") if result.Error == nil { t.Error("Expected command to fail due to missing arg") } @@ -447,7 +447,7 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") + result := test.RunCmd(cmd, "read ../examples/array.yaml [*].roles[x]") if result.Error == nil { t.Error("Expected command to fail due to missing arg") } @@ -511,7 +511,7 @@ func TestReadCmd_ErrorBadPath(t *testing.T) { func TestReadCmd_Verbose(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -v ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -520,7 +520,7 @@ func TestReadCmd_Verbose(t *testing.T) { // func TestReadCmd_ToJson(t *testing.T) { // cmd := getRootCommand() -// result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c") +// result := test.RunCmd(cmd, "read -j ../examples/sample.yaml b.c") // if result.Error != nil { // t.Error(result.Error) // } @@ -529,7 +529,7 @@ func TestReadCmd_Verbose(t *testing.T) { // func TestReadCmd_ToJsonLong(t *testing.T) { // cmd := getRootCommand() -// result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c") +// result := test.RunCmd(cmd, "read --tojson ../examples/sample.yaml b.c") // if result.Error != nil { // t.Error(result.Error) // } @@ -875,7 +875,7 @@ func TestWriteCmdScript(t *testing.T) { filename := test.WriteTempYamlFile(content) defer test.RemoveTempYamlFile(filename) - updateScript := `- command: update + updateScript := `- command: update path: b.c value: 7` scriptFilename := test.WriteTempYamlFile(updateScript) @@ -1324,7 +1324,7 @@ something: else` func TestMergeCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1341,7 +1341,7 @@ c: func TestMergeNoAutoCreateCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -c=false examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge -c=false ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1355,7 +1355,7 @@ c: func TestMergeOverwriteCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -c=false --overwrite examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge -c=false --overwrite ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1369,7 +1369,7 @@ c: func TestMergeAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --append examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --append ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1383,7 +1383,7 @@ c: func TestMergeOverwriteAndAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1397,7 +1397,7 @@ c: func TestMergeArraysCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml") + result := test.RunCmd(cmd, "merge --append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1408,7 +1408,7 @@ func TestMergeArraysCmd(t *testing.T) { func TestMergeCmd_Multi(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data1.yaml") + result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1491,7 +1491,7 @@ apples: red func TestMergeCmd_Error(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml") if result.Error == nil { t.Error("Expected command to fail due to missing arg") } @@ -1501,7 +1501,7 @@ func TestMergeCmd_Error(t *testing.T) { func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml fake-unknown") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml fake-unknown") if result.Error == nil { t.Error("Expected command to fail due to unknown file") } @@ -1515,7 +1515,7 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { } func TestMergeCmd_Inplace(t *testing.T) { - filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml")) + filename := test.WriteTempYamlFile(test.ReadTempYamlFile("../examples/data1.yaml")) err := os.Chmod(filename, os.FileMode(int(0666))) if err != nil { t.Error(err) @@ -1523,7 +1523,7 @@ func TestMergeCmd_Inplace(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s examples/data2.yaml", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s ../examples/data2.yaml", filename)) if result.Error != nil { t.Error(result.Error) } @@ -1543,7 +1543,7 @@ c: func TestMergeAllowEmptyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --allow-empty examples/data1.yaml examples/empty.yaml") + result := test.RunCmd(cmd, "merge --allow-empty ../examples/data1.yaml ../examples/empty.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1557,7 +1557,7 @@ c: func TestMergeDontAllowEmptyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml examples/empty.yaml") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/empty.yaml") expectedOutput := `Could not process document index 0 as there are only 0 document(s)` test.AssertResult(t, expectedOutput, result.Error.Error()) } diff --git a/cmd/constant.go b/cmd/constant.go new file mode 100644 index 00000000..8302a624 --- /dev/null +++ b/cmd/constant.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + logging "gopkg.in/op/go-logging.v1" +) + +var customTag = "" +var printMode = "v" +var writeInplace = false +var writeScript = "" +var outputToJSON = false +var overwriteFlag = false +var autoCreateFlag = true +var allowEmptyFlag = false +var appendFlag = false +var verbose = false +var version = false +var docIndex = "0" +var log = logging.MustGetLogger("yq") +var lib = yqlib.NewYqLib() +var valueParser = yqlib.NewValueParser() diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 00000000..35ac8808 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func createDeleteCmd() *cobra.Command { + var cmdDelete = &cobra.Command{ + Use: "delete [yaml_file] [path_expression]", + Aliases: []string{"d"}, + Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq delete things.yaml 'a.b.c' +yq delete things.yaml 'a.*.c' +yq delete things.yaml 'a.(child.subchild==co*).c' +yq delete things.yaml 'a.**' +yq delete --inplace things.yaml 'a.b.c' +yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +yq d -i things.yaml 'a.b.c' + `, + Long: `Deletes the nodes matching the given path expression from the YAML file. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: deleteProperty, + } + cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdDelete +} + +func deleteProperty(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("Must provide ") + } + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) + updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} + + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} diff --git a/cmd/merge.go b/cmd/merge.go new file mode 100644 index 00000000..9ea736ae --- /dev/null +++ b/cmd/merge.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" + "strings" +) + +func createMergeCmd() *cobra.Command { + var cmdMerge = &cobra.Command{ + Use: "merge [initial_yaml_file] [additional_yaml_file]...", + Aliases: []string{"m"}, + Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", + Example: ` +yq merge things.yaml other.yaml +yq merge --inplace things.yaml other.yaml +yq m -i things.yaml other.yaml +yq m --overwrite things.yaml other.yaml +yq m -i -x things.yaml other.yaml +yq m -i -a things.yaml other.yaml +yq m -i --autocreate=false things.yaml other.yaml + `, + Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. + +If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. +If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. +`, + RunE: mergeProperties, + } + cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") + cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") + cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") + cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") + cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdMerge +} + +func mergeProperties(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("Must provide at least 2 yaml files") + } + // first generate update commands from the file + var filesToMerge = args[1:] + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) + + for _, fileToMerge := range filesToMerge { + matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) + if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { + return errorProcessingFile + } + for _, matchingNode := range matchingNodes { + mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag) + updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) + } + } + + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} diff --git a/cmd/new.go b/cmd/new.go new file mode 100644 index 00000000..3ca842d0 --- /dev/null +++ b/cmd/new.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" +) + +func createNewCmd() *cobra.Command { + var cmdNew = &cobra.Command{ + Use: "new [path] [value]", + Aliases: []string{"n"}, + Short: "yq n [--script/-s script_file] a.b.c newValue", + Example: ` +yq new 'a.b.c' cat +yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +yq n 'a.b[+]' cat +yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags +yq n --script create_script.yaml + `, + Long: `Creates a new yaml w.r.t the given path and value. +Outputs to STDOUT + +Create Scripts: +Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. +`, + RunE: newProperty, + } + cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") + cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") + return cmdNew +} + +func newProperty(cmd *cobra.Command, args []string) error { + var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") + if updateCommandsError != nil { + return updateCommandsError + } + newNode := lib.New(updateCommands[0].Path) + + for _, updateCommand := range updateCommands { + + errorUpdating := lib.Update(&newNode, updateCommand, true) + + if errorUpdating != nil { + return errorUpdating + } + } + + var encoder = yaml.NewEncoder(cmd.OutOrStdout()) + encoder.SetIndent(2) + errorEncoding := encoder.Encode(&newNode) + encoder.Close() + return errorEncoding +} diff --git a/cmd/prefix.go b/cmd/prefix.go new file mode 100644 index 00000000..ae5c28ae --- /dev/null +++ b/cmd/prefix.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" +) + +func createPrefixCmd() *cobra.Command { + var cmdPrefix = &cobra.Command{ + Use: "prefix [yaml_file] [path]", + Aliases: []string{"p"}, + Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq prefix things.yaml 'a.b.c' +yq prefix --inplace things.yaml 'a.b.c' +yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +yq p -i things.yaml 'a.b.c' +yq p --doc 2 things.yaml 'a.b.d' +yq p -d2 things.yaml 'a.b.d' + `, + Long: `Prefixes w.r.t to the yaml file at the given path. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: prefixProperty, + } + cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdPrefix +} + +func prefixProperty(cmd *cobra.Command, args []string) error { + + if len(args) < 2 { + return errors.New("Must provide ") + } + updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} + log.Debugf("args %v", args) + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + var updateData = func(dataBucket *yaml.Node, currentIndex int) error { + return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) + } + return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +} diff --git a/cmd/read.go b/cmd/read.go new file mode 100644 index 00000000..a669a080 --- /dev/null +++ b/cmd/read.go @@ -0,0 +1,52 @@ +package cmd + +import ( + errors "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func createReadCmd() *cobra.Command { + var cmdRead = &cobra.Command{ + Use: "read [yaml_file] [path_expression]", + Aliases: []string{"r"}, + Short: "yq r [--doc/-d index] sample.yaml 'a.b.c'", + Example: ` +yq read things.yaml 'a.b.c' +yq r - 'a.b.c' # reads from stdin +yq r things.yaml 'a.*.c' +yq r things.yaml 'a.**.c' # deep splat +yq r things.yaml 'a.(child.subchild==co*).c' +yq r -d1 things.yaml 'a.array[0].blah' +yq r things.yaml 'a.array[*].blah' +yq r -- things.yaml '--key-starting-with-dashes.blah' + `, + Long: "Outputs the value of the given path in the yaml file to STDOUT", + RunE: readProperty, + } + cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") + return cmdRead +} + +func readProperty(cmd *cobra.Command, args []string) error { + var path = "" + + if len(args) < 1 { + return errors.New("Must provide filename") + } else if len(args) > 1 { + path = args[1] + } + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt) + + if errorReadingStream != nil { + return errorReadingStream + } + + return printResults(matchingNodes, cmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..ca1c5264 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "github.com/spf13/cobra" + logging "gopkg.in/op/go-logging.v1" + "os" +) + +func New() *cobra.Command { + var rootCmd = &cobra.Command{ + Use: "yq", + Short: "yq is a lightweight and portable command-line YAML processor.", + Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`, + RunE: func(cmd *cobra.Command, args []string) error { + if version { + cmd.Print(GetVersionDisplay()) + return nil + } + cmd.Println(cmd.UsageString()) + + return nil + }, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var format = logging.MustStringFormatter( + `%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`, + ) + var backend = logging.AddModuleLevel( + logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format)) + + if verbose { + backend.SetLevel(logging.DEBUG, "") + } else { + backend.SetLevel(logging.ERROR, "") + } + + logging.SetBackend(backend) + }, + } + + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") + rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") + rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") + + rootCmd.AddCommand( + createReadCmd(), + createWriteCmd(), + createPrefixCmd(), + createDeleteCmd(), + createNewCmd(), + createMergeCmd(), + ) + rootCmd.SetOutput(os.Stdout) + + return rootCmd +} diff --git a/cmd/utils.go b/cmd/utils.go new file mode 100644 index 00000000..eb7b7531 --- /dev/null +++ b/cmd/utils.go @@ -0,0 +1,372 @@ +package cmd + +import ( + "bufio" + "fmt" + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" + "io" + "io/ioutil" + "os" + "strconv" +) + +func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { + var matchingNodes []*yqlib.NodeContext + + var currentIndex = 0 + var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { + for { + var dataBucket yaml.Node + errorReading := decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + return handleEOF(updateAll, docIndexInt, currentIndex) + } + var errorParsing error + matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex) + if errorParsing != nil { + return errorParsing + } + currentIndex = currentIndex + 1 + } + }) + return matchingNodes, errorReadingStream +} + +func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { + log.Debugf("done %v / %v", currentIndex, docIndexInt) + if !updateAll && currentIndex <= docIndexInt { + return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex) + } + return nil +} + +func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) { + log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) + yqlib.DebugNode(&dataBucket) + if !updateAll && currentIndex != docIndexInt { + return originalMatchingNodes, nil + } + log.Debugf("reading %v in document %v", path, currentIndex) + matchingNodes, errorParsing := lib.Get(&dataBucket, path) + if errorParsing != nil { + return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) + } + return append(originalMatchingNodes, matchingNodes...), nil +} + +func printValue(node *yaml.Node, cmd *cobra.Command) error { + if node.Kind == yaml.ScalarNode { + cmd.Print(node.Value) + return nil + } + + bufferedWriter := bufio.NewWriter(cmd.OutOrStdout()) + defer safelyFlush(bufferedWriter) + + var encoder yqlib.Encoder + if outputToJSON { + encoder = yqlib.NewJsonEncoder(bufferedWriter) + } else { + encoder = yqlib.NewYamlEncoder(bufferedWriter) + } + if err := encoder.Encode(node); err != nil { + return err + } + return nil +} + +func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error { + if len(matchingNodes) == 0 { + log.Debug("no matching results, nothing to print") + return nil + } + + for index, mappedDoc := range matchingNodes { + switch printMode { + case "p": + cmd.Print(lib.PathStackToString(mappedDoc.PathStack)) + if index < len(matchingNodes)-1 { + cmd.Print("\n") + } + case "pv", "vp": + // put it into a node and print that. + var parentNode = yaml.Node{Kind: yaml.MappingNode} + parentNode.Content = make([]*yaml.Node, 2) + parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)} + parentNode.Content[1] = mappedDoc.Node + if err := printValue(&parentNode, cmd); err != nil { + return err + } + default: + if err := printValue(mappedDoc.Node, cmd); err != nil { + return err + } + // Printing our Scalars does not print a new line at the end + // we only want to do that if there are more values (so users can easily script extraction of values in the yaml) + if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode { + cmd.Print("\n") + } + } + } + + return nil +} + +func parseDocumentIndex() (bool, int, error) { + if docIndex == "*" { + return true, -1, nil + } + docIndexInt64, err := strconv.ParseInt(docIndex, 10, 32) + if err != nil { + return false, -1, errors.Wrapf(err, "Document index %v is not a integer or *", docIndex) + } + return false, int(docIndexInt64), nil +} + +type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error + +func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn { + return func(decoder *yaml.Decoder) error { + var dataBucket yaml.Node + var errorReading error + var errorWriting error + var errorUpdating error + var currentIndex = 0 + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + for { + log.Debugf("Read doc %v", currentIndex) + errorReading = decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + if !updateAll && currentIndex <= docIndexInt { + return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) + } + return nil + } else if errorReading != nil { + return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) + } + errorUpdating = updateData(&dataBucket, currentIndex) + if errorUpdating != nil { + return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) + } + + errorWriting = encoder.Encode(&dataBucket) + + if errorWriting != nil { + return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) + } + currentIndex = currentIndex + 1 + } + } +} + +func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Prefixing document %v", currentIndex) + yqlib.DebugNode(dataBucket) + updateCommand.Value = dataBucket.Content[0] + dataBucket.Content = make([]*yaml.Node, 1) + + newNode := lib.New(updateCommand.Path) + dataBucket.Content[0] = &newNode + + errorUpdating := lib.Update(dataBucket, updateCommand, true) + if errorUpdating != nil { + return errorUpdating + } + } + return nil +} + +func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + var updateData = func(dataBucket *yaml.Node, currentIndex int) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Updating doc %v", currentIndex) + for _, updateCommand := range updateCommands { + log.Debugf("Processing update to Path %v", updateCommand.Path) + errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) + if errorUpdating != nil { + return errorUpdating + } + } + } + return nil + } + return readAndUpdate(writer, inputFile, updateData) +} + +func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { + var destination io.Writer + var destinationName string + if writeInplace { + info, err := os.Stat(inputFile) + if err != nil { + return err + } + tempFile, err := ioutil.TempFile("", "temp") + if err != nil { + return err + } + destinationName = tempFile.Name() + err = os.Chmod(destinationName, info.Mode()) + if err != nil { + return err + } + destination = tempFile + defer func() { + safelyCloseFile(tempFile) + safelyRenameFile(tempFile.Name(), inputFile) + }() + } else { + destination = stdOut + destinationName = "Stdout" + } + + log.Debugf("Writing to %v from %v", destinationName, inputFile) + + bufferedWriter := bufio.NewWriter(destination) + defer safelyFlush(bufferedWriter) + + var encoder yqlib.Encoder + if outputToJSON { + encoder = yqlib.NewJsonEncoder(bufferedWriter) + } else { + encoder = yqlib.NewYamlEncoder(bufferedWriter) + } + return readStream(inputFile, mapYamlDecoder(updateData, encoder)) +} + +type updateCommandParsed struct { + Command string + Path string + Value yaml.Node +} + +func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) + if writeScript != "" { + var parsedCommands = make([]updateCommandParsed, 0) + + err := readData(writeScript, 0, &parsedCommands) + + if err != nil && err != io.EOF { + return nil, err + } + + log.Debugf("Read write commands file '%v'", parsedCommands) + for index := range parsedCommands { + parsedCommand := parsedCommands[index] + updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} + updateCommands = append(updateCommands, updateCommand) + } + + log.Debugf("Read write commands file '%v'", updateCommands) + } else if len(args) < expectedArgs { + return nil, errors.New(badArgsMessage) + } else { + updateCommands = make([]yqlib.UpdateCommand, 1) + log.Debug("args %v", args) + log.Debug("path %v", args[expectedArgs-2]) + log.Debug("Value %v", args[expectedArgs-1]) + updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true} + } + return updateCommands, nil +} + +func safelyRenameFile(from string, to string) { + if renameError := os.Rename(from, to); renameError != nil { + log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) + log.Debug(renameError.Error()) + // can't do this rename when running in docker to a file targeted in a mounted volume, + // so gracefully degrade to copying the entire contents. + if copyError := copyFileContents(from, to); copyError != nil { + log.Errorf("Failed copying from %v to %v", from, to) + log.Error(copyError.Error()) + } else { + removeErr := os.Remove(from) + if removeErr != nil { + log.Errorf("failed removing original file: %s", from) + } + } + } +} + +// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(in) + out, err := os.Create(dst) + if err != nil { + return err + } + defer safelyCloseFile(out) + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + +func safelyFlush(writer *bufio.Writer) { + if err := writer.Flush(); err != nil { + log.Error("Error flushing writer!") + log.Error(err.Error()) + } + +} +func safelyCloseFile(file *os.File) { + err := file.Close() + if err != nil { + log.Error("Error closing file!") + log.Error(err.Error()) + } +} + +type yamlDecoderFn func(*yaml.Decoder) error + +func readStream(filename string, yamlDecoder yamlDecoderFn) error { + if filename == "" { + return errors.New("Must provide filename") + } + + var stream io.Reader + if filename == "-" { + stream = bufio.NewReader(os.Stdin) + } else { + file, err := os.Open(filename) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(file) + stream = file + } + return yamlDecoder(yaml.NewDecoder(stream)) +} + +func readData(filename string, indexToRead int, parsedData interface{}) error { + return readStream(filename, func(decoder *yaml.Decoder) error { + for currentIndex := 0; currentIndex < indexToRead; currentIndex++ { + errorSkipping := decoder.Decode(parsedData) + if errorSkipping != nil { + return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping) + } + } + return decoder.Decode(parsedData) + }) +} diff --git a/version.go b/cmd/version.go similarity index 98% rename from version.go rename to cmd/version.go index 588f6f73..11a00675 100644 --- a/version.go +++ b/cmd/version.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" diff --git a/version_test.go b/cmd/version_test.go similarity index 98% rename from version_test.go rename to cmd/version_test.go index 9f21427f..b012e652 100644 --- a/version_test.go +++ b/cmd/version_test.go @@ -1,4 +1,4 @@ -package main +package cmd import "testing" diff --git a/cmd/write.go b/cmd/write.go new file mode 100644 index 00000000..04422f4b --- /dev/null +++ b/cmd/write.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func createWriteCmd() *cobra.Command { + var cmdWrite = &cobra.Command{ + Use: "write [yaml_file] [path_expression] [value]", + Aliases: []string{"w"}, + Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", + Example: ` +yq write things.yaml 'a.b.c' true +yq write things.yaml 'a.*.c' true +yq write things.yaml 'a.**' true +yq write things.yaml 'a.(child.subchild==co*).c' true +yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +yq write things.yaml 'a.b.c' --tag '!!float' 3 +yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags +yq w -i things.yaml 'a.b.c' cat +yq w -i -s update_script.yaml things.yaml +yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array +yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file + `, + Long: `Updates the yaml file w.r.t the given path and value. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. + +Append value to array adds the value to the end of array. + +Update Scripts: +Note that you can give an update script to perform more sophisticated update. Update script +format is list of update commands (update or delete) like so: +--- +- command: update + path: b.c + value: + #great + things: frog # wow! +- command: delete + path: b.d +`, + RunE: writeProperty, + } + cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") + cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") + cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdWrite +} + +func writeProperty(cmd *cobra.Command, args []string) error { + var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ") + if updateCommandsError != nil { + return updateCommandsError + } + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} diff --git a/yq.go b/yq.go index ed3a65a9..ef17961a 100644 --- a/yq.go +++ b/yq.go @@ -1,725 +1,16 @@ package main import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "os" - "strconv" - "strings" - - "github.com/mikefarah/yq/v3/pkg/yqlib" - - errors "github.com/pkg/errors" - - "github.com/spf13/cobra" + command "github.com/mikefarah/yq/v3/cmd" logging "gopkg.in/op/go-logging.v1" - yaml "gopkg.in/yaml.v3" + "os" ) -var customTag = "" -var printMode = "v" -var writeInplace = false -var writeScript = "" -var outputToJSON = false -var overwriteFlag = false -var autoCreateFlag = true -var allowEmptyFlag = false -var appendFlag = false -var verbose = false -var version = false -var docIndex = "0" -var log = logging.MustGetLogger("yq") -var lib = yqlib.NewYqLib() -var valueParser = yqlib.NewValueParser() - func main() { - cmd := newCommandCLI() + cmd := command.New() + log := logging.MustGetLogger("yq") if err := cmd.Execute(); err != nil { log.Error(err.Error()) os.Exit(1) } } - -func newCommandCLI() *cobra.Command { - var rootCmd = &cobra.Command{ - Use: "yq", - Short: "yq is a lightweight and portable command-line YAML processor.", - Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`, - RunE: func(cmd *cobra.Command, args []string) error { - if version { - cmd.Print(GetVersionDisplay()) - return nil - } - cmd.Println(cmd.UsageString()) - - return nil - }, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - var format = logging.MustStringFormatter( - `%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`, - ) - var backend = logging.AddModuleLevel( - logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format)) - - if verbose { - backend.SetLevel(logging.DEBUG, "") - } else { - backend.SetLevel(logging.ERROR, "") - } - - logging.SetBackend(backend) - }, - } - - rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") - rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") - rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") - - rootCmd.AddCommand( - createReadCmd(), - createWriteCmd(), - createPrefixCmd(), - createDeleteCmd(), - createNewCmd(), - createMergeCmd(), - ) - rootCmd.SetOutput(os.Stdout) - - return rootCmd -} - -func createReadCmd() *cobra.Command { - var cmdRead = &cobra.Command{ - Use: "read [yaml_file] [path_expression]", - Aliases: []string{"r"}, - Short: "yq r [--doc/-d index] sample.yaml 'a.b.c'", - Example: ` -yq read things.yaml 'a.b.c' -yq r - 'a.b.c' # reads from stdin -yq r things.yaml 'a.*.c' -yq r things.yaml 'a.**.c' # deep splat -yq r things.yaml 'a.(child.subchild==co*).c' -yq r -d1 things.yaml 'a.array[0].blah' -yq r things.yaml 'a.array[*].blah' -yq r -- things.yaml '--key-starting-with-dashes.blah' - `, - Long: "Outputs the value of the given path in the yaml file to STDOUT", - RunE: readProperty, - } - cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") - return cmdRead -} - -func createWriteCmd() *cobra.Command { - var cmdWrite = &cobra.Command{ - Use: "write [yaml_file] [path_expression] [value]", - Aliases: []string{"w"}, - Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'a.b.c' newValue", - Example: ` -yq write things.yaml 'a.b.c' true -yq write things.yaml 'a.*.c' true -yq write things.yaml 'a.**' true -yq write things.yaml 'a.(child.subchild==co*).c' true -yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -yq write things.yaml 'a.b.c' --tag '!!float' 3 -yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags -yq w -i things.yaml 'a.b.c' cat -yq w --script update_script.yaml things.yaml -yq w -i -s update_script.yaml things.yaml -yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array -yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file - `, - Long: `Updates the matching nodes of path expression to the specified value -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -Append value to array adds the value to the end of array. - -Update Scripts: -Note that you can give an update script to perform more sophisticated update. Update script -format is list of update commands (update or delete) like so: ---- -- command: update - path: b.c - value: - #great - things: frog # wow! -- command: delete - path: b.d -`, - RunE: writeProperty, - } - cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") - cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") - cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdWrite -} - -func createPrefixCmd() *cobra.Command { - var cmdPrefix = &cobra.Command{ - Use: "prefix [yaml_file] [path]", - Aliases: []string{"p"}, - Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq prefix things.yaml 'a.b.c' -yq prefix --inplace things.yaml 'a.b.c' -yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -yq p -i things.yaml 'a.b.c' -yq p --doc 2 things.yaml 'a.b.d' -yq p -d2 things.yaml 'a.b.d' - `, - Long: `Prefixes w.r.t to the yaml file at the given path. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: prefixProperty, - } - cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdPrefix -} - -func createDeleteCmd() *cobra.Command { - var cmdDelete = &cobra.Command{ - Use: "delete [yaml_file] [path_expression]", - Aliases: []string{"d"}, - Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq delete things.yaml 'a.b.c' -yq delete things.yaml 'a.*.c' -yq delete things.yaml 'a.(child.subchild==co*).c' -yq delete things.yaml 'a.**' -yq delete --inplace things.yaml 'a.b.c' -yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -yq d -i things.yaml 'a.b.c' - `, - Long: `Deletes the nodes matching the given path expression from the YAML file. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: deleteProperty, - } - cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdDelete -} - -func createNewCmd() *cobra.Command { - var cmdNew = &cobra.Command{ - Use: "new [path] [value]", - Aliases: []string{"n"}, - Short: "yq n [--script/-s script_file] a.b.c newValue", - Example: ` -yq new 'a.b.c' cat -yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -yq n 'a.b[+]' cat -yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags -yq n --script create_script.yaml - `, - Long: `Creates a new yaml w.r.t the given path and value. -Outputs to STDOUT - -Create Scripts: -Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. -`, - RunE: newProperty, - } - cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") - cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") - return cmdNew -} - -func createMergeCmd() *cobra.Command { - var cmdMerge = &cobra.Command{ - Use: "merge [initial_yaml_file] [additional_yaml_file]...", - Aliases: []string{"m"}, - Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", - Example: ` -yq merge things.yaml other.yaml -yq merge --inplace things.yaml other.yaml -yq m -i things.yaml other.yaml -yq m --overwrite things.yaml other.yaml -yq m -i -x things.yaml other.yaml -yq m -i -a things.yaml other.yaml -yq m -i --autocreate=false things.yaml other.yaml - `, - Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. -If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. -`, - RunE: mergeProperties, - } - cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") - cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") - cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") - cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") - cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdMerge -} - -func readProperty(cmd *cobra.Command, args []string) error { - var path = "" - - if len(args) < 1 { - return errors.New("Must provide filename") - } else if len(args) > 1 { - path = args[1] - } - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt) - - if errorReadingStream != nil { - return errorReadingStream - } - - return printResults(matchingNodes, cmd) -} - -func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { - var matchingNodes []*yqlib.NodeContext - - var currentIndex = 0 - var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { - for { - var dataBucket yaml.Node - errorReading := decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - return handleEOF(updateAll, docIndexInt, currentIndex) - } - var errorParsing error - matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex) - if errorParsing != nil { - return errorParsing - } - currentIndex = currentIndex + 1 - } - }) - return matchingNodes, errorReadingStream -} - -func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { - log.Debugf("done %v / %v", currentIndex, docIndexInt) - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex) - } - return nil -} - -func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) { - log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - yqlib.DebugNode(&dataBucket) - if !updateAll && currentIndex != docIndexInt { - return originalMatchingNodes, nil - } - log.Debugf("reading %v in document %v", path, currentIndex) - matchingNodes, errorParsing := lib.Get(&dataBucket, path) - if errorParsing != nil { - return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } - return append(originalMatchingNodes, matchingNodes...), nil -} - -func printValue(node *yaml.Node, cmd *cobra.Command) error { - if node.Kind == yaml.ScalarNode { - cmd.Print(node.Value) - return nil - } - - bufferedWriter := bufio.NewWriter(cmd.OutOrStdout()) - defer safelyFlush(bufferedWriter) - - var encoder yqlib.Encoder - if outputToJSON { - encoder = yqlib.NewJsonEncoder(bufferedWriter) - } else { - encoder = yqlib.NewYamlEncoder(bufferedWriter) - } - if err := encoder.Encode(node); err != nil { - return err - } - return nil -} - -func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error { - if len(matchingNodes) == 0 { - log.Debug("no matching results, nothing to print") - return nil - } - - for index, mappedDoc := range matchingNodes { - switch printMode { - case "p": - cmd.Print(lib.PathStackToString(mappedDoc.PathStack)) - if index < len(matchingNodes)-1 { - cmd.Print("\n") - } - case "pv", "vp": - // put it into a node and print that. - var parentNode = yaml.Node{Kind: yaml.MappingNode} - parentNode.Content = make([]*yaml.Node, 2) - parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)} - parentNode.Content[1] = mappedDoc.Node - if err := printValue(&parentNode, cmd); err != nil { - return err - } - default: - if err := printValue(mappedDoc.Node, cmd); err != nil { - return err - } - // Printing our Scalars does not print a new line at the end - // we only want to do that if there are more values (so users can easily script extraction of values in the yaml) - if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode { - cmd.Print("\n") - } - } - } - - return nil -} - -func parseDocumentIndex() (bool, int, error) { - if docIndex == "*" { - return true, -1, nil - } - docIndexInt64, err := strconv.ParseInt(docIndex, 10, 32) - if err != nil { - return false, -1, errors.Wrapf(err, "Document index %v is not a integer or *", docIndex) - } - return false, int(docIndexInt64), nil -} - -type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error - -func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn { - return func(decoder *yaml.Decoder) error { - var dataBucket yaml.Node - var errorReading error - var errorWriting error - var errorUpdating error - var currentIndex = 0 - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - for { - log.Debugf("Read doc %v", currentIndex) - errorReading = decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) - } - return nil - } else if errorReading != nil { - return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) - } - errorUpdating = updateData(&dataBucket, currentIndex) - if errorUpdating != nil { - return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) - } - - errorWriting = encoder.Encode(&dataBucket) - - if errorWriting != nil { - return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) - } - currentIndex = currentIndex + 1 - } - } -} - -func writeProperty(cmd *cobra.Command, args []string) error { - var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ") - if updateCommandsError != nil { - return updateCommandsError - } - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} - -func mergeProperties(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide at least 2 yaml files") - } - // first generate update commands from the file - var filesToMerge = args[1:] - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - - for _, fileToMerge := range filesToMerge { - matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) - if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { - return errorProcessingFile - } - for _, matchingNode := range matchingNodes { - mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag) - updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) - } - } - - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} - -func newProperty(cmd *cobra.Command, args []string) error { - var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") - if updateCommandsError != nil { - return updateCommandsError - } - newNode := lib.New(updateCommands[0].Path) - - for _, updateCommand := range updateCommands { - - errorUpdating := lib.Update(&newNode, updateCommand, true) - - if errorUpdating != nil { - return errorUpdating - } - } - - var encoder = yaml.NewEncoder(cmd.OutOrStdout()) - encoder.SetIndent(2) - errorEncoding := encoder.Encode(&newNode) - encoder.Close() - return errorEncoding -} - -func prefixProperty(cmd *cobra.Command, args []string) error { - - if len(args) < 2 { - return errors.New("Must provide ") - } - updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} - log.Debugf("args %v", args) - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) - } - return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -} - -func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Prefixing document %v", currentIndex) - yqlib.DebugNode(dataBucket) - updateCommand.Value = dataBucket.Content[0] - dataBucket.Content = make([]*yaml.Node, 1) - - newNode := lib.New(updateCommand.Path) - dataBucket.Content[0] = &newNode - - errorUpdating := lib.Update(dataBucket, updateCommand, true) - if errorUpdating != nil { - return errorUpdating - } - } - return nil -} - -func deleteProperty(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide ") - } - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) - updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} - - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} - -func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Updating doc %v", currentIndex) - for _, updateCommand := range updateCommands { - log.Debugf("Processing update to Path %v", updateCommand.Path) - errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) - if errorUpdating != nil { - return errorUpdating - } - } - } - return nil - } - return readAndUpdate(writer, inputFile, updateData) -} - -func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { - var destination io.Writer - var destinationName string - if writeInplace { - info, err := os.Stat(inputFile) - if err != nil { - return err - } - tempFile, err := ioutil.TempFile("", "temp") - if err != nil { - return err - } - destinationName = tempFile.Name() - err = os.Chmod(destinationName, info.Mode()) - if err != nil { - return err - } - destination = tempFile - defer func() { - safelyCloseFile(tempFile) - safelyRenameFile(tempFile.Name(), inputFile) - }() - } else { - destination = stdOut - destinationName = "Stdout" - } - - log.Debugf("Writing to %v from %v", destinationName, inputFile) - - bufferedWriter := bufio.NewWriter(destination) - defer safelyFlush(bufferedWriter) - - var encoder yqlib.Encoder - if outputToJSON { - encoder = yqlib.NewJsonEncoder(bufferedWriter) - } else { - encoder = yqlib.NewYamlEncoder(bufferedWriter) - } - return readStream(inputFile, mapYamlDecoder(updateData, encoder)) -} - -type updateCommandParsed struct { - Command string - Path string - Value yaml.Node -} - -func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - if writeScript != "" { - var parsedCommands = make([]updateCommandParsed, 0) - - err := readData(writeScript, 0, &parsedCommands) - - if err != nil && err != io.EOF { - return nil, err - } - - log.Debugf("Read write commands file '%v'", parsedCommands) - for index := range parsedCommands { - parsedCommand := parsedCommands[index] - updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} - updateCommands = append(updateCommands, updateCommand) - } - - log.Debugf("Read write commands file '%v'", updateCommands) - } else if len(args) < expectedArgs { - return nil, errors.New(badArgsMessage) - } else { - updateCommands = make([]yqlib.UpdateCommand, 1) - log.Debug("args %v", args) - log.Debug("path %v", args[expectedArgs-2]) - log.Debug("Value %v", args[expectedArgs-1]) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true} - } - return updateCommands, nil -} - -func safelyRenameFile(from string, to string) { - if renameError := os.Rename(from, to); renameError != nil { - log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) - log.Debug(renameError.Error()) - // can't do this rename when running in docker to a file targeted in a mounted volume, - // so gracefully degrade to copying the entire contents. - if copyError := copyFileContents(from, to); copyError != nil { - log.Errorf("Failed copying from %v to %v", from, to) - log.Error(copyError.Error()) - } else { - removeErr := os.Remove(from) - if removeErr != nil { - log.Errorf("failed removing original file: %s", from) - } - } - } -} - -// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(in) - out, err := os.Create(dst) - if err != nil { - return err - } - defer safelyCloseFile(out) - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() -} - -func safelyFlush(writer *bufio.Writer) { - if err := writer.Flush(); err != nil { - log.Error("Error flushing writer!") - log.Error(err.Error()) - } - -} -func safelyCloseFile(file *os.File) { - err := file.Close() - if err != nil { - log.Error("Error closing file!") - log.Error(err.Error()) - } -} - -type yamlDecoderFn func(*yaml.Decoder) error - -func readStream(filename string, yamlDecoder yamlDecoderFn) error { - if filename == "" { - return errors.New("Must provide filename") - } - - var stream io.Reader - if filename == "-" { - stream = bufio.NewReader(os.Stdin) - } else { - file, err := os.Open(filename) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(file) - stream = file - } - return yamlDecoder(yaml.NewDecoder(stream)) -} - -func readData(filename string, indexToRead int, parsedData interface{}) error { - return readStream(filename, func(decoder *yaml.Decoder) error { - for currentIndex := 0; currentIndex < indexToRead; currentIndex++ { - errorSkipping := decoder.Decode(parsedData) - if errorSkipping != nil { - return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping) - } - } - return decoder.Decode(parsedData) - }) -} From b7148adf20b31c1c35cd753ae0379a18401049ef Mon Sep 17 00:00:00 2001 From: Ryan SIU Date: Wed, 15 Jan 2020 10:56:29 +0800 Subject: [PATCH 63/68] #323 Fix the unit test --- cmd/commands_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/commands_test.go b/cmd/commands_test.go index 592ab03c..772885a4 100644 --- a/cmd/commands_test.go +++ b/cmd/commands_test.go @@ -96,7 +96,7 @@ func TestReadCmd(t *testing.T) { func TestReadWithAdvancedFilterCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e(name==sam).value") + result := test.RunCmd(cmd, "read -v ../examples/sample.yaml b.e(name==sam).value") if result.Error != nil { t.Error(result.Error) } @@ -105,7 +105,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) { func TestReadWithAdvancedFilterMapCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fr*]") + result := test.RunCmd(cmd, "read -v ../examples/sample.yaml b.e[name==fr*]") if result.Error != nil { t.Error(result.Error) } From 8a65822b0b214da151563df8debe13e30b08d53d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 20 Jan 2020 08:35:03 +1100 Subject: [PATCH 64/68] Updated docs to include value parsing --- docs/404.html | 20 +- docs/convert/index.html | 24 +- docs/create/index.html | 24 +- docs/delete/index.html | 122 +++-- docs/index.html | 24 +- docs/merge/index.html | 131 ++++-- docs/path_expressions/index.html | 112 ++++- docs/prefix/index.html | 127 +++--- docs/read/index.html | 233 +++------- docs/search/search_index.json | 2 +- docs/sitemap.xml | 23 +- docs/sitemap.xml.gz | Bin 201 -> 201 bytes docs/value_parsing/index.html | 759 +++++++++++++++++++++++++++++++ docs/write/index.html | 292 ++++-------- mkdocs.yml | 3 +- mkdocs/value_parsing.md | 161 +++++++ 16 files changed, 1502 insertions(+), 555 deletions(-) create mode 100644 docs/value_parsing/index.html create mode 100644 mkdocs/value_parsing.md diff --git a/docs/404.html b/docs/404.html index a6337614..afb1b529 100644 --- a/docs/404.html +++ b/docs/404.html @@ -237,8 +237,8 @@
      • - - Read + + Path Expressions
      • @@ -249,8 +249,20 @@
      • - - Path Expressions + + Value Parsing + +
      • + + + + + + + +
      • + + Read
      • diff --git a/docs/convert/index.html b/docs/convert/index.html index 7ed7ef07..06db1c82 100644 --- a/docs/convert/index.html +++ b/docs/convert/index.html @@ -241,8 +241,8 @@
      • - - Read + + Path Expressions
      • @@ -253,8 +253,20 @@
      • - - Path Expressions + + Value Parsing + +
      • + + + + + + + +
      • + + Read
      • @@ -425,7 +437,7 @@

        Convert

        -

        Yaml to Json

        +

        Yaml to Json

        To convert output to json, use the --tojson (or -j) flag. This is supported by all commands.

        Each matching yaml node will be converted to json and printed out on a separate line.

        Given a sample.yaml file of:

        @@ -457,7 +469,7 @@ bab: {"c":5}
        -

        Json to Yaml

        +

        Json to Yaml

        To read in json, just pass in a json file instead of yaml, it will just work :)

        e.g given a json file

        {"a":"Easy! as one two three","b":{"c":2,"d":[3,4]}}
        diff --git a/docs/create/index.html b/docs/create/index.html
        index 7114b530..550fc9c9 100644
        --- a/docs/create/index.html
        +++ b/docs/create/index.html
        @@ -241,8 +241,8 @@
         
         
           
      • - - Read + + Path Expressions
      • @@ -253,8 +253,20 @@
      • - - Path Expressions + + Value Parsing + +
      • + + + + + + + +
      • + + Read
      • @@ -430,7 +442,7 @@

        Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.

        See docs for path expression

        -

        Creating a simple yaml file

        +

        Creating a simple yaml file

        yq n b.c cat
         
        @@ -439,7 +451,7 @@ c: cat
        -

        Creating using a create script

        +

        Creating using a create script

        Create scripts follow the same format as the update scripts.

        Given a script create_instructions.yaml of:

        - command: update 
        diff --git a/docs/delete/index.html b/docs/delete/index.html
        index 42b4d97f..87a60ee4 100644
        --- a/docs/delete/index.html
        +++ b/docs/delete/index.html
        @@ -94,7 +94,7 @@
             
             
             
        -      
        +      
                 Skip to content
               
             
        @@ -241,8 +241,8 @@
         
         
           
      • - - Read + + Path Expressions
      • @@ -253,8 +253,20 @@
      • - - Path Expressions + + Value Parsing + +
      • + + + + + + + +
      • + + Read
      • @@ -311,6 +323,13 @@
        - + diff --git a/docs/assets/images/icons/bitbucket.1b09e088.svg b/docs/assets/images/icons/bitbucket.1b09e088.svg index a25435af..cf58c14f 100644 --- a/docs/assets/images/icons/bitbucket.1b09e088.svg +++ b/docs/assets/images/icons/bitbucket.1b09e088.svg @@ -1,20 +1 @@ - - - + \ No newline at end of file diff --git a/docs/assets/images/icons/github.f0b8504a.svg b/docs/assets/images/icons/github.f0b8504a.svg index c009420a..3d13b197 100644 --- a/docs/assets/images/icons/github.f0b8504a.svg +++ b/docs/assets/images/icons/github.f0b8504a.svg @@ -1,18 +1 @@ - - - + \ No newline at end of file diff --git a/docs/assets/images/icons/gitlab.6dd19c00.svg b/docs/assets/images/icons/gitlab.6dd19c00.svg index 9e3d6f05..1d9fffa7 100644 --- a/docs/assets/images/icons/gitlab.6dd19c00.svg +++ b/docs/assets/images/icons/gitlab.6dd19c00.svg @@ -1,38 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/javascripts/application.39abc4af.js b/docs/assets/javascripts/application.39abc4af.js deleted file mode 100644 index 86c09c6e..00000000 --- a/docs/assets/javascripts/application.39abc4af.js +++ /dev/null @@ -1,6 +0,0 @@ -!function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=n,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=13)}([function(e,t,n){"use strict";var r={Listener:function(){function e(e,t,n){var r=this;this.els_=Array.prototype.slice.call("string"==typeof e?document.querySelectorAll(e):[].concat(e)),this.handler_="function"==typeof n?{update:n}:n,this.events_=[].concat(t),this.update_=function(e){return r.handler_.update(e)}}var t=e.prototype;return t.listen=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.addEventListener(e,n.update_,!1)})}),"function"==typeof this.handler_.setup&&this.handler_.setup()},t.unlisten=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.removeEventListener(e,n.update_)})}),"function"==typeof this.handler_.reset&&this.handler_.reset()},e}(),MatchMedia:function(e,t){this.handler_=function(e){e.matches?t.listen():t.unlisten()};var n=window.matchMedia(e);n.addListener(this.handler_),this.handler_(n)}},i={Shadow:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement&&n.parentNode instanceof HTMLElement))throw new ReferenceError;if(this.el_=n.parentNode,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLElement))throw new ReferenceError;this.header_=n,this.height_=0,this.active_=!1}var t=e.prototype;return t.setup=function(){for(var e=this.el_;e=e.previousElementSibling;){if(!(e instanceof HTMLElement))throw new ReferenceError;this.height_+=e.offsetHeight}this.update()},t.update=function(e){if(!e||"resize"!==e.type&&"orientationchange"!==e.type){var t=window.pageYOffset>=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=30&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))throw new TypeError;if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+-43;e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(24)}).call(this,n(4))},function(e,f,d){"use strict";(function(t){var e=d(8),n=setTimeout;function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],u(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void c(t);if("function"==typeof n)return void u((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,c(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,c(e)}function c(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],r.forEach(function(e,t){var n,r=a.docs_.get(t),i=f.createElement("li",{class:"md-search-result__item"},f.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article md-search-result__article--document"},f.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(f.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article"},f.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var o=this.el_.parentNode;if(!(o instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&o.offsetHeight>=o.scrollHeight-16;)this.stack_.shift()();var l=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(l,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),r.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",r.size)}}}else{var u=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(u):u(a.data_)},250)}},e}()}).call(this,r(3))},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(14),c(15),c(16),c(17),c(18),c(19),c(20);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))}).listen(),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&document.activeElement.isContentEditable||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else if(document.activeElement&&!document.activeElement.form){if("TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName)return;70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault())}}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){e.exports=n.p+"assets/images/icons/bitbucket.1b09e088.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/github.f0b8504a.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/gitlab.6dd19c00.svg"},function(e,t){e.exports="/home/travis/build/squidfunk/mkdocs-material/material/application.750b69bd.css"},function(e,t){e.exports="/home/travis/build/squidfunk/mkdocs-material/material/application-palette.224b79ff.css"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return(t=t||{}).bubbles=!!t.bubbles,t.cancelable=!!t.cancelable,(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(22),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=100&sort=updated&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))return[];if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+(5-this.height_);e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(24)}).call(this,n(4))},function(e,d,h){"use strict";(function(t){var e=h(8),n=setTimeout;function c(e){return Boolean(e&&void 0!==e.length)}function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],f(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void l(t);if("function"==typeof n)return void f((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,l(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,l(e)}function l(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],r.forEach(function(e,t){var n,r=a.docs_.get(t),i=f.createElement("li",{class:"md-search-result__item"},f.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article md-search-result__article--document"},f.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(f.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article"},f.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var o=this.el_.parentNode;if(!(o instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&o.offsetHeight>=o.scrollHeight-16;)this.stack_.shift()();var l=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(l,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),r.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",r.size)}}}else{var u=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(u):u(a.data_)},250)}},e}()}).call(this,r(3))},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(14),c(15),c(16),c(17),c(18),c(19),c(20);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))}).listen(),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&document.activeElement.isContentEditable||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else if(document.activeElement&&!document.activeElement.form){if("TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName)return;70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault())}}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){"use strict";n.p},function(e,t,n){"use strict";n.p},function(e,t,n){"use strict";n.p},function(e,t,n){"use strict"},function(e,t,n){"use strict"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return(t=t||{}).bubbles=!!t.bubbles,t.cancelable=!!t.cancelable,(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(22),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos=t&&(e=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,c.find_among_b(o,4)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e)}this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var e,r=c.cursor;return function(){var e,r=c.cursor+3;if(t=c.limit,0<=r&&r<=c.limit){for(i=r;;){if(e=c.cursor,c.in_grouping(d,97,248)){c.cursor=e;break}if((c.cursor=e)>=c.limit)return;c.cursor++}for(;!c.out_grouping(d,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(t=c.cursor)=t&&(r=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,e=c.find_among_b(s,32),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:c.in_grouping_b(u,97,229)&&c.slice_del()}}(),c.cursor=c.limit,l(),c.cursor=c.limit,function(){var e,r,i,n=c.limit-c.cursor;if(c.ket=c.cursor,c.eq_s_b(2,"st")&&(c.bra=c.cursor,c.eq_s_b(2,"ig")&&c.slice_del()),c.cursor=c.limit-n,c.cursor>=t&&(r=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,e=c.find_among_b(a,5),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del(),i=c.limit-c.cursor,l(),c.cursor=c.limit-i;break;case 2:c.slice_from("løs")}}(),c.cursor=c.limit,c.cursor>=t&&(e=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,c.out_grouping_b(d,97,248)?(c.bra=c.cursor,n=c.slice_to(n),c.limit_backward=e,c.eq_v_b(n)&&c.slice_del()):c.limit_backward=e),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.de.js b/docs/assets/javascripts/lunr/lunr.de.js index 1529892c..73e55eb0 100644 --- a/docs/assets/javascripts/lunr/lunr.de.js +++ b/docs/assets/javascripts/lunr/lunr.de.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `German` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var _,p,r;e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=(_=e.stemmerSupport.Among,p=e.stemmerSupport.SnowballProgram,r=new function(){var r,n,i,s=[new _("",-1,6),new _("U",0,2),new _("Y",0,1),new _("ä",0,3),new _("ö",0,4),new _("ü",0,5)],o=[new _("e",-1,2),new _("em",-1,1),new _("en",-1,2),new _("ern",-1,1),new _("er",-1,1),new _("s",-1,3),new _("es",5,2)],c=[new _("en",-1,1),new _("er",-1,1),new _("st",-1,2),new _("est",2,1)],u=[new _("ig",-1,1),new _("lich",-1,1)],a=[new _("end",-1,1),new _("ig",-1,2),new _("ung",-1,1),new _("lich",-1,3),new _("isch",-1,2),new _("ik",-1,2),new _("heit",-1,3),new _("keit",-1,4)],t=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],d=[117,30,5],l=[117,30,4],m=new p;function h(e,r,n){return!(!m.eq_s(1,e)||(m.ket=m.cursor,!m.in_grouping(t,97,252)))&&(m.slice_from(r),m.cursor=n,!0)}function w(){for(;!m.in_grouping(t,97,252);){if(m.cursor>=m.limit)return!0;m.cursor++}for(;!m.out_grouping(t,97,252);){if(m.cursor>=m.limit)return!0;m.cursor++}return!1}function f(){return i<=m.cursor}function b(){return n<=m.cursor}this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var e=m.cursor;return function(){for(var e,r,n,i,s=m.cursor;;)if(e=m.cursor,m.bra=e,m.eq_s(1,"ß"))m.ket=m.cursor,m.slice_from("ss");else{if(e>=m.limit)break;m.cursor=e+1}for(m.cursor=s;;)for(r=m.cursor;;){if(n=m.cursor,m.in_grouping(t,97,252)){if(i=m.cursor,m.bra=i,h("u","U",n))break;if(m.cursor=i,h("y","Y",n))break}if(n>=m.limit)return m.cursor=r;m.cursor=n+1}}(),m.cursor=e,function(){i=m.limit,n=i;var e=m.cursor+3;0<=e&&e<=m.limit&&(r=e,w()||((i=m.cursor)=m.limit)return;m.cursor++}}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.du.js b/docs/assets/javascripts/lunr/lunr.du.js index 52632004..e9c67299 100644 --- a/docs/assets/javascripts/lunr/lunr.du.js +++ b/docs/assets/javascripts/lunr/lunr.du.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Dutch` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var v,q,r;console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=(v=e.stemmerSupport.Among,q=e.stemmerSupport.SnowballProgram,r=new function(){var r,i,u,o=[new v("",-1,6),new v("á",0,1),new v("ä",0,1),new v("é",0,2),new v("ë",0,2),new v("í",0,3),new v("ï",0,3),new v("ó",0,4),new v("ö",0,4),new v("ú",0,5),new v("ü",0,5)],n=[new v("",-1,3),new v("I",0,2),new v("Y",0,1)],t=[new v("dd",-1,-1),new v("kk",-1,-1),new v("tt",-1,-1)],c=[new v("ene",-1,2),new v("se",-1,3),new v("en",-1,2),new v("heden",2,1),new v("s",-1,3)],a=[new v("end",-1,1),new v("ig",-1,2),new v("ing",-1,1),new v("lijk",-1,3),new v("baar",-1,4),new v("bar",-1,5)],l=[new v("aa",-1,-1),new v("ee",-1,-1),new v("oo",-1,-1),new v("uu",-1,-1)],m=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],d=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],f=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],_=new q;function s(e){return(_.cursor=e)>=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return r<=_.cursor}function g(){var e=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-e,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var e;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(e=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-e,_.slice_del(),u=!0,g())))}function k(){var e;b()&&(e=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-e,_.eq_s_b(3,"gem")||(_.cursor=_.limit-e,_.slice_del(),g())))}this.setCurrent=function(e){_.setCurrent(e)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var e=_.cursor;return function(){for(var e,r,i,n=_.cursor;;){if(_.bra=_.cursor,e=_.find_among(o,11))switch(_.ket=_.cursor,e){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(r=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=r);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=r;else if(s(r))break}else if(s(r))break}(),_.cursor=e,i=_.limit,r=i,w()||((i=_.cursor)<3&&(i=3),w()||(r=_.cursor)),_.limit_backward=e,_.cursor=_.limit,function(){var e,r,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,e=_.find_among_b(c,5))switch(_.bra=_.cursor,e){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(r=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-r,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,e=_.find_among_b(a,6))switch(_.bra=_.cursor,e){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var e;;)if(_.bra=_.cursor,e=_.find_among(n,3))switch(_.ket=_.cursor,e){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.es.js b/docs/assets/javascripts/lunr/lunr.es.js index 9de6c09c..2918bd19 100644 --- a/docs/assets/javascripts/lunr/lunr.es.js +++ b/docs/assets/javascripts/lunr/lunr.es.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Spanish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,s){"function"==typeof define&&define.amd?define(s):"object"==typeof exports?module.exports=s():s()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var C,P,s;e.es=function(){this.pipeline.reset(),this.pipeline.add(e.es.trimmer,e.es.stopWordFilter,e.es.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.es.stemmer))},e.es.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.es.trimmer=e.trimmerSupport.generateTrimmer(e.es.wordCharacters),e.Pipeline.registerFunction(e.es.trimmer,"trimmer-es"),e.es.stemmer=(C=e.stemmerSupport.Among,P=e.stemmerSupport.SnowballProgram,s=new function(){var r,n,i,a=[new C("",-1,6),new C("á",0,1),new C("é",0,2),new C("í",0,3),new C("ó",0,4),new C("ú",0,5)],t=[new C("la",-1,-1),new C("sela",0,-1),new C("le",-1,-1),new C("me",-1,-1),new C("se",-1,-1),new C("lo",-1,-1),new C("selo",5,-1),new C("las",-1,-1),new C("selas",7,-1),new C("les",-1,-1),new C("los",-1,-1),new C("selos",10,-1),new C("nos",-1,-1)],o=[new C("ando",-1,6),new C("iendo",-1,6),new C("yendo",-1,7),new C("ándo",-1,2),new C("iéndo",-1,1),new C("ar",-1,6),new C("er",-1,6),new C("ir",-1,6),new C("ár",-1,3),new C("ér",-1,4),new C("ír",-1,5)],s=[new C("ic",-1,-1),new C("ad",-1,-1),new C("os",-1,-1),new C("iv",-1,1)],u=[new C("able",-1,1),new C("ible",-1,1),new C("ante",-1,1)],w=[new C("ic",-1,1),new C("abil",-1,1),new C("iv",-1,1)],c=[new C("ica",-1,1),new C("ancia",-1,2),new C("encia",-1,5),new C("adora",-1,2),new C("osa",-1,1),new C("ista",-1,1),new C("iva",-1,9),new C("anza",-1,1),new C("logía",-1,3),new C("idad",-1,8),new C("able",-1,1),new C("ible",-1,1),new C("ante",-1,2),new C("mente",-1,7),new C("amente",13,6),new C("ación",-1,2),new C("ución",-1,4),new C("ico",-1,1),new C("ismo",-1,1),new C("oso",-1,1),new C("amiento",-1,1),new C("imiento",-1,1),new C("ivo",-1,9),new C("ador",-1,2),new C("icas",-1,1),new C("ancias",-1,2),new C("encias",-1,5),new C("adoras",-1,2),new C("osas",-1,1),new C("istas",-1,1),new C("ivas",-1,9),new C("anzas",-1,1),new C("logías",-1,3),new C("idades",-1,8),new C("ables",-1,1),new C("ibles",-1,1),new C("aciones",-1,2),new C("uciones",-1,4),new C("adores",-1,2),new C("antes",-1,2),new C("icos",-1,1),new C("ismos",-1,1),new C("osos",-1,1),new C("amientos",-1,1),new C("imientos",-1,1),new C("ivos",-1,9)],m=[new C("ya",-1,1),new C("ye",-1,1),new C("yan",-1,1),new C("yen",-1,1),new C("yeron",-1,1),new C("yendo",-1,1),new C("yo",-1,1),new C("yas",-1,1),new C("yes",-1,1),new C("yais",-1,1),new C("yamos",-1,1),new C("yó",-1,1)],l=[new C("aba",-1,2),new C("ada",-1,2),new C("ida",-1,2),new C("ara",-1,2),new C("iera",-1,2),new C("ía",-1,2),new C("aría",5,2),new C("ería",5,2),new C("iría",5,2),new C("ad",-1,2),new C("ed",-1,2),new C("id",-1,2),new C("ase",-1,2),new C("iese",-1,2),new C("aste",-1,2),new C("iste",-1,2),new C("an",-1,2),new C("aban",16,2),new C("aran",16,2),new C("ieran",16,2),new C("ían",16,2),new C("arían",20,2),new C("erían",20,2),new C("irían",20,2),new C("en",-1,1),new C("asen",24,2),new C("iesen",24,2),new C("aron",-1,2),new C("ieron",-1,2),new C("arán",-1,2),new C("erán",-1,2),new C("irán",-1,2),new C("ado",-1,2),new C("ido",-1,2),new C("ando",-1,2),new C("iendo",-1,2),new C("ar",-1,2),new C("er",-1,2),new C("ir",-1,2),new C("as",-1,2),new C("abas",39,2),new C("adas",39,2),new C("idas",39,2),new C("aras",39,2),new C("ieras",39,2),new C("ías",39,2),new C("arías",45,2),new C("erías",45,2),new C("irías",45,2),new C("es",-1,1),new C("ases",49,2),new C("ieses",49,2),new C("abais",-1,2),new C("arais",-1,2),new C("ierais",-1,2),new C("íais",-1,2),new C("aríais",55,2),new C("eríais",55,2),new C("iríais",55,2),new C("aseis",-1,2),new C("ieseis",-1,2),new C("asteis",-1,2),new C("isteis",-1,2),new C("áis",-1,2),new C("éis",-1,1),new C("aréis",64,2),new C("eréis",64,2),new C("iréis",64,2),new C("ados",-1,2),new C("idos",-1,2),new C("amos",-1,2),new C("ábamos",70,2),new C("áramos",70,2),new C("iéramos",70,2),new C("íamos",70,2),new C("aríamos",74,2),new C("eríamos",74,2),new C("iríamos",74,2),new C("emos",-1,1),new C("aremos",78,2),new C("eremos",78,2),new C("iremos",78,2),new C("ásemos",78,2),new C("iésemos",78,2),new C("imos",-1,2),new C("arás",-1,2),new C("erás",-1,2),new C("irás",-1,2),new C("ís",-1,2),new C("ará",-1,2),new C("erá",-1,2),new C("irá",-1,2),new C("aré",-1,2),new C("eré",-1,2),new C("iré",-1,2),new C("ió",-1,2)],d=[new C("a",-1,1),new C("e",-1,2),new C("o",-1,1),new C("os",-1,1),new C("á",-1,1),new C("é",-1,2),new C("í",-1,1),new C("ó",-1,1)],b=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],f=new P;function _(){if(f.out_grouping(b,97,252)){for(;!f.in_grouping(b,97,252);){if(f.cursor>=f.limit)return!0;f.cursor++}return!1}return!0}function h(){var e,s=f.cursor;if(function(){if(f.in_grouping(b,97,252)){var e=f.cursor;if(_()){if(f.cursor=e,!f.in_grouping(b,97,252))return!0;for(;!f.out_grouping(b,97,252);){if(f.cursor>=f.limit)return!0;f.cursor++}}return!1}return!0}()){if(f.cursor=s,!f.out_grouping(b,97,252))return;if(e=f.cursor,_()){if(f.cursor=e,!f.in_grouping(b,97,252)||f.cursor>=f.limit)return;f.cursor++}}i=f.cursor}function v(){for(;!f.in_grouping(b,97,252);){if(f.cursor>=f.limit)return!1;f.cursor++}for(;!f.out_grouping(b,97,252);){if(f.cursor>=f.limit)return!1;f.cursor++}return!0}function p(){return i<=f.cursor}function g(){return r<=f.cursor}function k(e,s){if(!g())return!0;f.slice_del(),f.ket=f.cursor;var r=f.find_among_b(e,s);return r&&(f.bra=f.cursor,1==r&&g()&&f.slice_del()),!1}function y(e){return!g()||(f.slice_del(),f.ket=f.cursor,f.eq_s_b(2,e)&&(f.bra=f.cursor,g()&&f.slice_del()),!1)}function q(){var e;if(f.ket=f.cursor,e=f.find_among_b(c,46)){switch(f.bra=f.cursor,e){case 1:if(!g())return!1;f.slice_del();break;case 2:if(y("ic"))return!1;break;case 3:if(!g())return!1;f.slice_from("log");break;case 4:if(!g())return!1;f.slice_from("u");break;case 5:if(!g())return!1;f.slice_from("ente");break;case 6:if(!(n<=f.cursor))return!1;f.slice_del(),f.ket=f.cursor,(e=f.find_among_b(s,4))&&(f.bra=f.cursor,g()&&(f.slice_del(),1==e&&(f.ket=f.cursor,f.eq_s_b(2,"at")&&(f.bra=f.cursor,g()&&f.slice_del()))));break;case 7:if(k(u,3))return!1;break;case 8:if(k(w,3))return!1;break;case 9:if(y("at"))return!1}return!0}return!1}this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var e,s=f.cursor;return e=f.cursor,i=f.limit,r=n=i,h(),f.cursor=e,v()&&(n=f.cursor,v()&&(r=f.cursor)),f.limit_backward=s,f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,f.find_among_b(t,13)&&(f.bra=f.cursor,(e=f.find_among_b(o,11))&&p()))switch(e){case 1:f.bra=f.cursor,f.slice_from("iendo");break;case 2:f.bra=f.cursor,f.slice_from("ando");break;case 3:f.bra=f.cursor,f.slice_from("ar");break;case 4:f.bra=f.cursor,f.slice_from("er");break;case 5:f.bra=f.cursor,f.slice_from("ir");break;case 6:f.slice_del();break;case 7:f.eq_s_b(1,"u")&&f.slice_del()}}(),f.cursor=f.limit,q()||(f.cursor=f.limit,function(){var e,s;if(f.cursor>=i&&(s=f.limit_backward,f.limit_backward=i,f.ket=f.cursor,e=f.find_among_b(m,12),f.limit_backward=s,e)){if(f.bra=f.cursor,1==e){if(!f.eq_s_b(1,"u"))return!1;f.slice_del()}return!0}return!1}()||(f.cursor=f.limit,function(){var e,s,r,n;if(f.cursor>=i&&(s=f.limit_backward,f.limit_backward=i,f.ket=f.cursor,e=f.find_among_b(l,96),f.limit_backward=s,e))switch(f.bra=f.cursor,e){case 1:r=f.limit-f.cursor,f.eq_s_b(1,"u")?(n=f.limit-f.cursor,f.eq_s_b(1,"g")?f.cursor=f.limit-n:f.cursor=f.limit-r):f.cursor=f.limit-r,f.bra=f.cursor;case 2:f.slice_del()}}())),f.cursor=f.limit,function(){var e,s;if(f.ket=f.cursor,e=f.find_among_b(d,8))switch(f.bra=f.cursor,e){case 1:p()&&f.slice_del();break;case 2:p()&&(f.slice_del(),f.ket=f.cursor,f.eq_s_b(1,"u")&&(f.bra=f.cursor,s=f.limit-f.cursor,f.eq_s_b(1,"g")&&(f.cursor=f.limit-s,p()&&f.slice_del())))}}(),f.cursor=f.limit_backward,function(){for(var e;;){if(f.bra=f.cursor,e=f.find_among(a,6))switch(f.ket=f.cursor,e){case 1:f.slice_from("a");continue;case 2:f.slice_from("e");continue;case 3:f.slice_from("i");continue;case 4:f.slice_from("o");continue;case 5:f.slice_from("u");continue;case 6:if(f.cursor>=f.limit)break;f.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return s.setCurrent(e),s.stem(),s.getCurrent()}):(s.setCurrent(e),s.stem(),s.getCurrent())}),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.fi.js b/docs/assets/javascripts/lunr/lunr.fi.js index 2f9bf5ae..f34d10e0 100644 --- a/docs/assets/javascripts/lunr/lunr.fi.js +++ b/docs/assets/javascripts/lunr/lunr.fi.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Finnish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var v,C,e;i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=(v=i.stemmerSupport.Among,C=i.stemmerSupport.SnowballProgram,e=new function(){var n,t,l,o,r=[new v("pa",-1,1),new v("sti",-1,2),new v("kaan",-1,1),new v("han",-1,1),new v("kin",-1,1),new v("hän",-1,1),new v("kään",-1,1),new v("ko",-1,1),new v("pä",-1,1),new v("kö",-1,1)],s=[new v("lla",-1,-1),new v("na",-1,-1),new v("ssa",-1,-1),new v("ta",-1,-1),new v("lta",3,-1),new v("sta",3,-1)],a=[new v("llä",-1,-1),new v("nä",-1,-1),new v("ssä",-1,-1),new v("tä",-1,-1),new v("ltä",3,-1),new v("stä",3,-1)],u=[new v("lle",-1,-1),new v("ine",-1,-1)],c=[new v("nsa",-1,3),new v("mme",-1,3),new v("nne",-1,3),new v("ni",-1,2),new v("si",-1,1),new v("an",-1,4),new v("en",-1,6),new v("än",-1,5),new v("nsä",-1,3)],i=[new v("aa",-1,-1),new v("ee",-1,-1),new v("ii",-1,-1),new v("oo",-1,-1),new v("uu",-1,-1),new v("ää",-1,-1),new v("öö",-1,-1)],m=[new v("a",-1,8),new v("lla",0,-1),new v("na",0,-1),new v("ssa",0,-1),new v("ta",0,-1),new v("lta",4,-1),new v("sta",4,-1),new v("tta",4,9),new v("lle",-1,-1),new v("ine",-1,-1),new v("ksi",-1,-1),new v("n",-1,7),new v("han",11,1),new v("den",11,-1,q),new v("seen",11,-1,j),new v("hen",11,2),new v("tten",11,-1,q),new v("hin",11,3),new v("siin",11,-1,q),new v("hon",11,4),new v("hän",11,5),new v("hön",11,6),new v("ä",-1,8),new v("llä",22,-1),new v("nä",22,-1),new v("ssä",22,-1),new v("tä",22,-1),new v("ltä",26,-1),new v("stä",26,-1),new v("ttä",26,9)],w=[new v("eja",-1,-1),new v("mma",-1,1),new v("imma",1,-1),new v("mpa",-1,1),new v("impa",3,-1),new v("mmi",-1,1),new v("immi",5,-1),new v("mpi",-1,1),new v("impi",7,-1),new v("ejä",-1,-1),new v("mmä",-1,1),new v("immä",10,-1),new v("mpä",-1,1),new v("impä",12,-1)],_=[new v("i",-1,-1),new v("j",-1,-1)],k=[new v("mma",-1,1),new v("imma",0,-1)],b=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],e=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],f=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],h=new C;function p(){for(var i;i=h.cursor,!h.in_grouping(d,97,246);){if((h.cursor=i)>=h.limit)return!0;h.cursor++}for(h.cursor=i;!h.out_grouping(d,97,246);){if(h.cursor>=h.limit)return!0;h.cursor++}return!1}function g(){var i,e;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(r,10)){switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:if(!h.in_grouping_b(f,97,246))return;break;case 2:if(!(l<=h.cursor))return}h.slice_del()}else h.limit_backward=e}function j(){return h.find_among_b(i,7)}function q(){return h.eq_s_b(1,"i")&&h.in_grouping_b(e,97,246)}this.setCurrent=function(i){h.setCurrent(i)},this.getCurrent=function(){return h.getCurrent()},this.stem=function(){var i,e=h.cursor;return o=h.limit,l=o,p()||(o=h.cursor,p()||(l=h.cursor)),n=!1,h.limit_backward=e,h.cursor=h.limit,g(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(c,9))switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:r=h.limit-h.cursor,h.eq_s_b(1,"k")||(h.cursor=h.limit-r,h.slice_del());break;case 2:h.slice_del(),h.ket=h.cursor,h.eq_s_b(3,"kse")&&(h.bra=h.cursor,h.slice_from("ksi"));break;case 3:h.slice_del();break;case 4:h.find_among_b(s,6)&&h.slice_del();break;case 5:h.find_among_b(a,6)&&h.slice_del();break;case 6:h.find_among_b(u,2)&&h.slice_del()}else h.limit_backward=e}(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(m,30)){switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:if(!h.eq_s_b(1,"a"))return;break;case 2:case 9:if(!h.eq_s_b(1,"e"))return;break;case 3:if(!h.eq_s_b(1,"i"))return;break;case 4:if(!h.eq_s_b(1,"o"))return;break;case 5:if(!h.eq_s_b(1,"ä"))return;break;case 6:if(!h.eq_s_b(1,"ö"))return;break;case 7:if(r=h.limit-h.cursor,!j()&&(h.cursor=h.limit-r,!h.eq_s_b(2,"ie"))){h.cursor=h.limit-r;break}if(h.cursor=h.limit-r,h.cursor<=h.limit_backward){h.cursor=h.limit-r;break}h.cursor--,h.bra=h.cursor;break;case 8:if(!h.in_grouping_b(d,97,246)||!h.out_grouping_b(d,97,246))return}h.slice_del(),n=!0}else h.limit_backward=e}(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=l)if(e=h.limit_backward,h.limit_backward=l,h.ket=h.cursor,i=h.find_among_b(w,14)){if(h.bra=h.cursor,h.limit_backward=e,1==i){if(r=h.limit-h.cursor,h.eq_s_b(2,"po"))return;h.cursor=h.limit-r}h.slice_del()}else h.limit_backward=e}(),h.cursor=h.limit,h.cursor=(n?h.cursor>=o&&(i=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,h.find_among_b(_,2)?(h.bra=h.cursor,h.limit_backward=i,h.slice_del()):h.limit_backward=i):(h.cursor=h.limit,function(){var i,e,r,n,t,s;if(h.cursor>=o){if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,h.eq_s_b(1,"t")&&(h.bra=h.cursor,r=h.limit-h.cursor,h.in_grouping_b(d,97,246)&&(h.cursor=h.limit-r,h.slice_del(),h.limit_backward=e,n=h.limit-h.cursor,h.cursor>=l&&(h.cursor=l,t=h.limit_backward,h.limit_backward=h.cursor,h.cursor=h.limit-n,h.ket=h.cursor,i=h.find_among_b(k,2))))){if(h.bra=h.cursor,h.limit_backward=t,1==i){if(s=h.limit-h.cursor,h.eq_s_b(2,"po"))return;h.cursor=h.limit-s}return h.slice_del()}h.limit_backward=e}}()),h.limit),function(){var i,e,r,n;if(h.cursor>=o){for(i=h.limit_backward,h.limit_backward=o,e=h.limit-h.cursor,j()&&(h.cursor=h.limit-e,h.ket=h.cursor,h.cursor>h.limit_backward&&(h.cursor--,h.bra=h.cursor,h.slice_del())),h.cursor=h.limit-e,h.ket=h.cursor,h.in_grouping_b(b,97,228)&&(h.bra=h.cursor,h.out_grouping_b(d,97,246)&&h.slice_del()),h.cursor=h.limit-e,h.ket=h.cursor,h.eq_s_b(1,"j")&&(h.bra=h.cursor,r=h.limit-h.cursor,h.eq_s_b(1,"o")?h.slice_del():(h.cursor=h.limit-r,h.eq_s_b(1,"u")&&h.slice_del())),h.cursor=h.limit-e,h.ket=h.cursor,h.eq_s_b(1,"o")&&(h.bra=h.cursor,h.eq_s_b(1,"j")&&h.slice_del()),h.cursor=h.limit-e,h.limit_backward=i;;){if(n=h.limit-h.cursor,h.out_grouping_b(d,97,246)){h.cursor=h.limit-n;break}if(h.cursor=h.limit-n,h.cursor<=h.limit_backward)return;h.cursor--}h.ket=h.cursor,h.cursor>h.limit_backward&&(h.cursor--,h.bra=h.cursor,t=h.slice_to(),h.eq_v_b(t)&&h.slice_del())}}(),!0}},function(i){return"function"==typeof i.update?i.update(function(i){return e.setCurrent(i),e.stem(),e.getCurrent()}):(e.setCurrent(i),e.stem(),e.getCurrent())}),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.fr.js b/docs/assets/javascripts/lunr/lunr.fr.js index 078d0cab..d043ec65 100644 --- a/docs/assets/javascripts/lunr/lunr.fr.js +++ b/docs/assets/javascripts/lunr/lunr.fr.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `French` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,y,s;e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=(r=e.stemmerSupport.Among,y=e.stemmerSupport.SnowballProgram,s=new function(){var s,i,t,n=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],u=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],o=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],c=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],a=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],l=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],w=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],f=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],m=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],_=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],b=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],d=new y;function k(e,r,s){return!(!d.eq_s(1,e)||(d.ket=d.cursor,!d.in_grouping(_,97,251)))&&(d.slice_from(r),d.cursor=s,!0)}function p(e,r,s){return!!d.eq_s(1,e)&&(d.ket=d.cursor,d.slice_from(r),d.cursor=s,!0)}function g(){for(;!d.in_grouping(_,97,251);){if(d.cursor>=d.limit)return!0;d.cursor++}for(;!d.out_grouping(_,97,251);){if(d.cursor>=d.limit)return!0;d.cursor++}return!1}function q(){return t<=d.cursor}function v(){return i<=d.cursor}function h(){return s<=d.cursor}function z(){if(!function(){var e,r;if(d.ket=d.cursor,e=d.find_among_b(a,43)){switch(d.bra=d.cursor,e){case 1:if(!h())return!1;d.slice_del();break;case 2:if(!h())return!1;d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"ic")&&(d.bra=d.cursor,h()?d.slice_del():d.slice_from("iqU"));break;case 3:if(!h())return!1;d.slice_from("log");break;case 4:if(!h())return!1;d.slice_from("u");break;case 5:if(!h())return!1;d.slice_from("ent");break;case 6:if(!q())return!1;if(d.slice_del(),d.ket=d.cursor,e=d.find_among_b(o,6))switch(d.bra=d.cursor,e){case 1:h()&&(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,h()&&d.slice_del()));break;case 2:h()?d.slice_del():v()&&d.slice_from("eux");break;case 3:h()&&d.slice_del();break;case 4:q()&&d.slice_from("i")}break;case 7:if(!h())return!1;if(d.slice_del(),d.ket=d.cursor,e=d.find_among_b(c,3))switch(d.bra=d.cursor,e){case 1:h()?d.slice_del():d.slice_from("abl");break;case 2:h()?d.slice_del():d.slice_from("iqU");break;case 3:h()&&d.slice_del()}break;case 8:if(!h())return!1;if(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,h()&&(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"ic")))){d.bra=d.cursor,h()?d.slice_del():d.slice_from("iqU");break}break;case 9:d.slice_from("eau");break;case 10:if(!v())return!1;d.slice_from("al");break;case 11:if(h())d.slice_del();else{if(!v())return!1;d.slice_from("eux")}break;case 12:if(!v()||!d.out_grouping_b(_,97,251))return!1;d.slice_del();break;case 13:return q()&&d.slice_from("ant"),!1;case 14:return q()&&d.slice_from("ent"),!1;case 15:return r=d.limit-d.cursor,d.in_grouping_b(_,97,251)&&q()&&(d.cursor=d.limit-r,d.slice_del()),!1}return!0}return!1}()&&(d.cursor=d.limit,!function(){var e,r;if(d.cursor=t){if(s=d.limit_backward,d.limit_backward=t,d.ket=d.cursor,e=d.find_among_b(f,7))switch(d.bra=d.cursor,e){case 1:if(h()){if(i=d.limit-d.cursor,!d.eq_s_b(1,"s")&&(d.cursor=d.limit-i,!d.eq_s_b(1,"t")))break;d.slice_del()}break;case 2:d.slice_from("i");break;case 3:d.slice_del();break;case 4:d.eq_s_b(2,"gu")&&d.slice_del()}d.limit_backward=s}}();d.cursor=d.limit,d.ket=d.cursor,d.eq_s_b(1,"Y")?(d.bra=d.cursor,d.slice_from("i")):(d.cursor=d.limit,d.eq_s_b(1,"ç")&&(d.bra=d.cursor,d.slice_from("c")))}this.setCurrent=function(e){d.setCurrent(e)},this.getCurrent=function(){return d.getCurrent()},this.stem=function(){var e,r=d.cursor;return function(){for(var e,r;;){if(e=d.cursor,d.in_grouping(_,97,251)){if(d.bra=d.cursor,r=d.cursor,k("u","U",e))continue;if(d.cursor=r,k("i","I",e))continue;if(d.cursor=r,p("y","Y",e))continue}if(d.cursor=e,!k("y","Y",d.bra=e)){if(d.cursor=e,d.eq_s(1,"q")&&(d.bra=d.cursor,p("u","U",e)))continue;if((d.cursor=e)>=d.limit)return;d.cursor++}}}(),d.cursor=r,function(){var e=d.cursor;if(t=d.limit,s=i=t,d.in_grouping(_,97,251)&&d.in_grouping(_,97,251)&&d.cursor=d.limit){d.cursor=t;break}d.cursor++}while(!d.in_grouping(_,97,251))}t=d.cursor,d.cursor=e,g()||(i=d.cursor,g()||(s=d.cursor))}(),d.limit_backward=r,d.cursor=d.limit,z(),d.cursor=d.limit,e=d.limit-d.cursor,d.find_among_b(m,5)&&(d.cursor=d.limit-e,d.ket=d.cursor,d.cursor>d.limit_backward&&(d.cursor--,d.bra=d.cursor,d.slice_del())),d.cursor=d.limit,function(){for(var e,r=1;d.out_grouping_b(_,97,251);)r--;if(r<=0){if(d.ket=d.cursor,e=d.limit-d.cursor,!d.eq_s_b(1,"é")&&(d.cursor=d.limit-e,!d.eq_s_b(1,"è")))return;d.bra=d.cursor,d.slice_from("e")}}(),d.cursor=d.limit_backward,function(){for(var e,r;r=d.cursor,d.bra=r,e=d.find_among(u,4);)switch(d.ket=d.cursor,e){case 1:d.slice_from("i");break;case 2:d.slice_from("u");break;case 3:d.slice_from("y");break;case 4:if(d.cursor>=d.limit)return;d.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return s.setCurrent(e),s.stem(),s.getCurrent()}):(s.setCurrent(e),s.stem(),s.getCurrent())}),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.hu.js b/docs/assets/javascripts/lunr/lunr.hu.js index 56a4b0dc..bfc68db8 100644 --- a/docs/assets/javascripts/lunr/lunr.hu.js +++ b/docs/assets/javascripts/lunr/lunr.hu.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Hungarian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var p,_,n;e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=(p=e.stemmerSupport.Among,_=e.stemmerSupport.SnowballProgram,n=new function(){var r,i=[new p("cs",-1,-1),new p("dzs",-1,-1),new p("gy",-1,-1),new p("ly",-1,-1),new p("ny",-1,-1),new p("sz",-1,-1),new p("ty",-1,-1),new p("zs",-1,-1)],n=[new p("á",-1,1),new p("é",-1,2)],a=[new p("bb",-1,-1),new p("cc",-1,-1),new p("dd",-1,-1),new p("ff",-1,-1),new p("gg",-1,-1),new p("jj",-1,-1),new p("kk",-1,-1),new p("ll",-1,-1),new p("mm",-1,-1),new p("nn",-1,-1),new p("pp",-1,-1),new p("rr",-1,-1),new p("ccs",-1,-1),new p("ss",-1,-1),new p("zzs",-1,-1),new p("tt",-1,-1),new p("vv",-1,-1),new p("ggy",-1,-1),new p("lly",-1,-1),new p("nny",-1,-1),new p("tty",-1,-1),new p("ssz",-1,-1),new p("zz",-1,-1)],t=[new p("al",-1,1),new p("el",-1,2)],e=[new p("ba",-1,-1),new p("ra",-1,-1),new p("be",-1,-1),new p("re",-1,-1),new p("ig",-1,-1),new p("nak",-1,-1),new p("nek",-1,-1),new p("val",-1,-1),new p("vel",-1,-1),new p("ul",-1,-1),new p("nál",-1,-1),new p("nél",-1,-1),new p("ból",-1,-1),new p("ról",-1,-1),new p("tól",-1,-1),new p("bõl",-1,-1),new p("rõl",-1,-1),new p("tõl",-1,-1),new p("ül",-1,-1),new p("n",-1,-1),new p("an",19,-1),new p("ban",20,-1),new p("en",19,-1),new p("ben",22,-1),new p("képpen",22,-1),new p("on",19,-1),new p("ön",19,-1),new p("képp",-1,-1),new p("kor",-1,-1),new p("t",-1,-1),new p("at",29,-1),new p("et",29,-1),new p("ként",29,-1),new p("anként",32,-1),new p("enként",32,-1),new p("onként",32,-1),new p("ot",29,-1),new p("ért",29,-1),new p("öt",29,-1),new p("hez",-1,-1),new p("hoz",-1,-1),new p("höz",-1,-1),new p("vá",-1,-1),new p("vé",-1,-1)],s=[new p("án",-1,2),new p("én",-1,1),new p("ánként",-1,3)],c=[new p("stul",-1,2),new p("astul",0,1),new p("ástul",0,3),new p("stül",-1,2),new p("estül",3,1),new p("éstül",3,4)],w=[new p("á",-1,1),new p("é",-1,2)],o=[new p("k",-1,7),new p("ak",0,4),new p("ek",0,6),new p("ok",0,5),new p("ák",0,1),new p("ék",0,2),new p("ök",0,3)],l=[new p("éi",-1,7),new p("áéi",0,6),new p("ééi",0,5),new p("é",-1,9),new p("ké",3,4),new p("aké",4,1),new p("eké",4,1),new p("oké",4,1),new p("áké",4,3),new p("éké",4,2),new p("öké",4,1),new p("éé",3,8)],u=[new p("a",-1,18),new p("ja",0,17),new p("d",-1,16),new p("ad",2,13),new p("ed",2,13),new p("od",2,13),new p("ád",2,14),new p("éd",2,15),new p("öd",2,13),new p("e",-1,18),new p("je",9,17),new p("nk",-1,4),new p("unk",11,1),new p("ánk",11,2),new p("énk",11,3),new p("ünk",11,1),new p("uk",-1,8),new p("juk",16,7),new p("ájuk",17,5),new p("ük",-1,8),new p("jük",19,7),new p("éjük",20,6),new p("m",-1,12),new p("am",22,9),new p("em",22,9),new p("om",22,9),new p("ám",22,10),new p("ém",22,11),new p("o",-1,18),new p("á",-1,19),new p("é",-1,20)],m=[new p("id",-1,10),new p("aid",0,9),new p("jaid",1,6),new p("eid",0,9),new p("jeid",3,6),new p("áid",0,7),new p("éid",0,8),new p("i",-1,15),new p("ai",7,14),new p("jai",8,11),new p("ei",7,14),new p("jei",10,11),new p("ái",7,12),new p("éi",7,13),new p("itek",-1,24),new p("eitek",14,21),new p("jeitek",15,20),new p("éitek",14,23),new p("ik",-1,29),new p("aik",18,26),new p("jaik",19,25),new p("eik",18,26),new p("jeik",21,25),new p("áik",18,27),new p("éik",18,28),new p("ink",-1,20),new p("aink",25,17),new p("jaink",26,16),new p("eink",25,17),new p("jeink",28,16),new p("áink",25,18),new p("éink",25,19),new p("aitok",-1,21),new p("jaitok",32,20),new p("áitok",-1,22),new p("im",-1,5),new p("aim",35,4),new p("jaim",36,1),new p("eim",35,4),new p("jeim",38,1),new p("áim",35,2),new p("éim",35,3)],k=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],f=new _;function b(){return r<=f.cursor}function d(){var e=f.limit-f.cursor;return!!f.find_among_b(a,23)&&(f.cursor=f.limit-e,!0)}function g(){if(f.cursor>f.limit_backward){f.cursor--,f.ket=f.cursor;var e=f.cursor-1;f.limit_backward<=e&&e<=f.limit&&(f.cursor=e,f.bra=e,f.slice_del())}}function h(){f.ket=f.cursor,f.find_among_b(e,44)&&(f.bra=f.cursor,b()&&(f.slice_del(),function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(n,2))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("a");break;case 2:f.slice_from("e")}}()))}this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var e=f.cursor;return function(){var e,n=f.cursor;if(r=f.limit,f.in_grouping(k,97,252))for(;;){if(e=f.cursor,f.out_grouping(k,97,252))return f.cursor=e,f.find_among(i,8)||(f.cursor=e)=f.limit)return r=e;f.cursor++}if(f.cursor=n,f.out_grouping(k,97,252)){for(;!f.in_grouping(k,97,252);){if(f.cursor>=f.limit)return;f.cursor++}r=f.cursor}}(),f.limit_backward=e,f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(t,2))&&(f.bra=f.cursor,b())){if((1==e||2==e)&&!d())return;f.slice_del(),g()}}(),f.cursor=f.limit,h(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(s,3))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("e");break;case 2:case 3:f.slice_from("a")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(c,6))&&(f.bra=f.cursor,b()))switch(e){case 1:case 2:f.slice_del();break;case 3:f.slice_from("a");break;case 4:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(w,2))&&(f.bra=f.cursor,b())){if((1==e||2==e)&&!d())return;f.slice_del(),g()}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(l,12))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 7:case 9:f.slice_del();break;case 2:case 5:case 8:f.slice_from("e");break;case 3:case 6:f.slice_from("a")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(u,31))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:f.slice_del();break;case 2:case 5:case 10:case 14:case 19:f.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(m,42))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:f.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:f.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(o,7))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("a");break;case 2:f.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:f.slice_del()}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.it.js b/docs/assets/javascripts/lunr/lunr.it.js index 50dddaa0..58a46fb6 100644 --- a/docs/assets/javascripts/lunr/lunr.it.js +++ b/docs/assets/javascripts/lunr/lunr.it.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Italian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var z,P,r;e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=(z=e.stemmerSupport.Among,P=e.stemmerSupport.SnowballProgram,r=new function(){var o,t,s,a=[new z("",-1,7),new z("qu",0,6),new z("á",0,1),new z("é",0,2),new z("í",0,3),new z("ó",0,4),new z("ú",0,5)],u=[new z("",-1,3),new z("I",0,1),new z("U",0,2)],c=[new z("la",-1,-1),new z("cela",0,-1),new z("gliela",0,-1),new z("mela",0,-1),new z("tela",0,-1),new z("vela",0,-1),new z("le",-1,-1),new z("cele",6,-1),new z("gliele",6,-1),new z("mele",6,-1),new z("tele",6,-1),new z("vele",6,-1),new z("ne",-1,-1),new z("cene",12,-1),new z("gliene",12,-1),new z("mene",12,-1),new z("sene",12,-1),new z("tene",12,-1),new z("vene",12,-1),new z("ci",-1,-1),new z("li",-1,-1),new z("celi",20,-1),new z("glieli",20,-1),new z("meli",20,-1),new z("teli",20,-1),new z("veli",20,-1),new z("gli",20,-1),new z("mi",-1,-1),new z("si",-1,-1),new z("ti",-1,-1),new z("vi",-1,-1),new z("lo",-1,-1),new z("celo",31,-1),new z("glielo",31,-1),new z("melo",31,-1),new z("telo",31,-1),new z("velo",31,-1)],w=[new z("ando",-1,1),new z("endo",-1,1),new z("ar",-1,2),new z("er",-1,2),new z("ir",-1,2)],r=[new z("ic",-1,-1),new z("abil",-1,-1),new z("os",-1,-1),new z("iv",-1,1)],n=[new z("ic",-1,1),new z("abil",-1,1),new z("iv",-1,1)],i=[new z("ica",-1,1),new z("logia",-1,3),new z("osa",-1,1),new z("ista",-1,1),new z("iva",-1,9),new z("anza",-1,1),new z("enza",-1,5),new z("ice",-1,1),new z("atrice",7,1),new z("iche",-1,1),new z("logie",-1,3),new z("abile",-1,1),new z("ibile",-1,1),new z("usione",-1,4),new z("azione",-1,2),new z("uzione",-1,4),new z("atore",-1,2),new z("ose",-1,1),new z("ante",-1,1),new z("mente",-1,1),new z("amente",19,7),new z("iste",-1,1),new z("ive",-1,9),new z("anze",-1,1),new z("enze",-1,5),new z("ici",-1,1),new z("atrici",25,1),new z("ichi",-1,1),new z("abili",-1,1),new z("ibili",-1,1),new z("ismi",-1,1),new z("usioni",-1,4),new z("azioni",-1,2),new z("uzioni",-1,4),new z("atori",-1,2),new z("osi",-1,1),new z("anti",-1,1),new z("amenti",-1,6),new z("imenti",-1,6),new z("isti",-1,1),new z("ivi",-1,9),new z("ico",-1,1),new z("ismo",-1,1),new z("oso",-1,1),new z("amento",-1,6),new z("imento",-1,6),new z("ivo",-1,9),new z("ità",-1,8),new z("istà",-1,1),new z("istè",-1,1),new z("istì",-1,1)],l=[new z("isca",-1,1),new z("enda",-1,1),new z("ata",-1,1),new z("ita",-1,1),new z("uta",-1,1),new z("ava",-1,1),new z("eva",-1,1),new z("iva",-1,1),new z("erebbe",-1,1),new z("irebbe",-1,1),new z("isce",-1,1),new z("ende",-1,1),new z("are",-1,1),new z("ere",-1,1),new z("ire",-1,1),new z("asse",-1,1),new z("ate",-1,1),new z("avate",16,1),new z("evate",16,1),new z("ivate",16,1),new z("ete",-1,1),new z("erete",20,1),new z("irete",20,1),new z("ite",-1,1),new z("ereste",-1,1),new z("ireste",-1,1),new z("ute",-1,1),new z("erai",-1,1),new z("irai",-1,1),new z("isci",-1,1),new z("endi",-1,1),new z("erei",-1,1),new z("irei",-1,1),new z("assi",-1,1),new z("ati",-1,1),new z("iti",-1,1),new z("eresti",-1,1),new z("iresti",-1,1),new z("uti",-1,1),new z("avi",-1,1),new z("evi",-1,1),new z("ivi",-1,1),new z("isco",-1,1),new z("ando",-1,1),new z("endo",-1,1),new z("Yamo",-1,1),new z("iamo",-1,1),new z("avamo",-1,1),new z("evamo",-1,1),new z("ivamo",-1,1),new z("eremo",-1,1),new z("iremo",-1,1),new z("assimo",-1,1),new z("ammo",-1,1),new z("emmo",-1,1),new z("eremmo",54,1),new z("iremmo",54,1),new z("immo",-1,1),new z("ano",-1,1),new z("iscano",58,1),new z("avano",58,1),new z("evano",58,1),new z("ivano",58,1),new z("eranno",-1,1),new z("iranno",-1,1),new z("ono",-1,1),new z("iscono",65,1),new z("arono",65,1),new z("erono",65,1),new z("irono",65,1),new z("erebbero",-1,1),new z("irebbero",-1,1),new z("assero",-1,1),new z("essero",-1,1),new z("issero",-1,1),new z("ato",-1,1),new z("ito",-1,1),new z("uto",-1,1),new z("avo",-1,1),new z("evo",-1,1),new z("ivo",-1,1),new z("ar",-1,1),new z("ir",-1,1),new z("erà",-1,1),new z("irà",-1,1),new z("erò",-1,1),new z("irò",-1,1)],m=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],f=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],v=[17],b=new P;function d(e,r,n){return!(!b.eq_s(1,e)||(b.ket=b.cursor,!b.in_grouping(m,97,249)))&&(b.slice_from(r),b.cursor=n,!0)}function _(e){if(b.cursor=e,!b.in_grouping(m,97,249))return!1;for(;!b.out_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}return!0}function g(){var e,r=b.cursor;if(!function(){if(b.in_grouping(m,97,249)){var e=b.cursor;if(b.out_grouping(m,97,249)){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return _(e);b.cursor++}return!0}return _(e)}return!1}()){if(b.cursor=r,!b.out_grouping(m,97,249))return;if(e=b.cursor,b.out_grouping(m,97,249)){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return b.cursor=e,void(b.in_grouping(m,97,249)&&b.cursor=b.limit)return;b.cursor++}s=b.cursor}function p(){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}for(;!b.out_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}return!0}function k(){return s<=b.cursor}function h(){return o<=b.cursor}function q(){var e;if(b.ket=b.cursor,!(e=b.find_among_b(i,51)))return!1;switch(b.bra=b.cursor,e){case 1:if(!h())return!1;b.slice_del();break;case 2:if(!h())return!1;b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"ic")&&(b.bra=b.cursor,h()&&b.slice_del());break;case 3:if(!h())return!1;b.slice_from("log");break;case 4:if(!h())return!1;b.slice_from("u");break;case 5:if(!h())return!1;b.slice_from("ente");break;case 6:if(!k())return!1;b.slice_del();break;case 7:if(!(t<=b.cursor))return!1;b.slice_del(),b.ket=b.cursor,(e=b.find_among_b(r,4))&&(b.bra=b.cursor,h()&&(b.slice_del(),1==e&&(b.ket=b.cursor,b.eq_s_b(2,"at")&&(b.bra=b.cursor,h()&&b.slice_del()))));break;case 8:if(!h())return!1;b.slice_del(),b.ket=b.cursor,(e=b.find_among_b(n,3))&&(b.bra=b.cursor,1==e&&h()&&b.slice_del());break;case 9:if(!h())return!1;b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"at")&&(b.bra=b.cursor,h()&&(b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"ic")&&(b.bra=b.cursor,h()&&b.slice_del())))}return!0}function C(){var e;e=b.limit-b.cursor,b.ket=b.cursor,b.in_grouping_b(f,97,242)&&(b.bra=b.cursor,k()&&(b.slice_del(),b.ket=b.cursor,b.eq_s_b(1,"i")&&(b.bra=b.cursor,k())))?b.slice_del():b.cursor=b.limit-e,b.ket=b.cursor,b.eq_s_b(1,"h")&&(b.bra=b.cursor,b.in_grouping_b(v,99,103)&&k()&&b.slice_del())}this.setCurrent=function(e){b.setCurrent(e)},this.getCurrent=function(){return b.getCurrent()},this.stem=function(){var e,r,n,i=b.cursor;return function(){for(var e,r,n,i,o=b.cursor;;){if(b.bra=b.cursor,e=b.find_among(a,7))switch(b.ket=b.cursor,e){case 1:b.slice_from("à");continue;case 2:b.slice_from("è");continue;case 3:b.slice_from("ì");continue;case 4:b.slice_from("ò");continue;case 5:b.slice_from("ù");continue;case 6:b.slice_from("qU");continue;case 7:if(b.cursor>=b.limit)break;b.cursor++;continue}break}for(b.cursor=o;;)for(r=b.cursor;;){if(n=b.cursor,b.in_grouping(m,97,249)){if(b.bra=b.cursor,i=b.cursor,d("u","U",n))break;if(b.cursor=i,d("i","I",n))break}if(b.cursor=n,b.cursor>=b.limit)return b.cursor=r;b.cursor++}}(),b.cursor=i,e=b.cursor,s=b.limit,o=t=s,g(),b.cursor=e,p()&&(t=b.cursor,p()&&(o=b.cursor)),b.limit_backward=i,b.cursor=b.limit,function(){var e;if(b.ket=b.cursor,b.find_among_b(c,37)&&(b.bra=b.cursor,(e=b.find_among_b(w,5))&&k()))switch(e){case 1:b.slice_del();break;case 2:b.slice_from("e")}}(),b.cursor=b.limit,q()||(b.cursor=b.limit,b.cursor>=s&&(n=b.limit_backward,b.limit_backward=s,b.ket=b.cursor,(r=b.find_among_b(l,87))&&(b.bra=b.cursor,1==r&&b.slice_del()),b.limit_backward=n)),b.cursor=b.limit,C(),b.cursor=b.limit_backward,function(){for(var e;b.bra=b.cursor,e=b.find_among(u,3);)switch(b.ket=b.cursor,e){case 1:b.slice_from("i");break;case 2:b.slice_from("u");break;case 3:if(b.cursor>=b.limit)return;b.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.ja.js b/docs/assets/javascripts/lunr/lunr.ja.js index 69f62025..715b834a 100644 --- a/docs/assets/javascripts/lunr/lunr.ja.js +++ b/docs/assets/javascripts/lunr/lunr.ja.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Japanese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Chad Liu + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(m){if(void 0===m)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===m.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var l="2"==m.version[0];m.ja=function(){this.pipeline.reset(),this.pipeline.add(m.ja.trimmer,m.ja.stopWordFilter,m.ja.stemmer),l?this.tokenizer=m.ja.tokenizer:(m.tokenizer&&(m.tokenizer=m.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=m.ja.tokenizer))};var j=new m.TinySegmenter;m.ja.tokenizer=function(e){var r,t,i,n,o,s,p,a,u;if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return l?new m.Token(e.toLowerCase()):e.toLowerCase()});for(r=(t=e.toString().toLowerCase().replace(/^\s+/,"")).length-1;0<=r;r--)if(/\S/.test(t.charAt(r))){t=t.substring(0,r+1);break}for(o=[],i=t.length,p=a=0;a<=i;a++)if(s=a-p,t.charAt(a).match(/\s/)||a==i){if(0=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return e<=_.cursor}function g(){var r=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-r,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var r;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.slice_del(),u=!0,g())))}function k(){var r;b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.eq_s_b(3,"gem")||(_.cursor=_.limit-r,_.slice_del(),g())))}this.setCurrent=function(r){_.setCurrent(r)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var r=_.cursor;return function(){for(var r,e,i,n=_.cursor;;){if(_.bra=_.cursor,r=_.find_among(o,11))switch(_.ket=_.cursor,r){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(e=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=e);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=e;else if(s(e))break}else if(s(e))break}(),_.cursor=r,i=_.limit,e=i,w()||((i=_.cursor)<3&&(i=3),w()||(e=_.cursor)),_.limit_backward=r,_.cursor=_.limit,function(){var r,e,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,r=_.find_among_b(c,5))switch(_.bra=_.cursor,r){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(e=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-e,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,r=_.find_among_b(a,6))switch(_.bra=_.cursor,r){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var r;;)if(_.bra=_.cursor,r=_.find_among(n,3))switch(_.ket=_.cursor,r){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(r){return"function"==typeof r.update?r.update(function(r){return e.setCurrent(r),e.stem(),e.getCurrent()}):(e.setCurrent(r),e.stem(),e.getCurrent())}),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.no.js b/docs/assets/javascripts/lunr/lunr.no.js index 3d156b9c..031e4b20 100644 --- a/docs/assets/javascripts/lunr/lunr.no.js +++ b/docs/assets/javascripts/lunr/lunr.no.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Norwegian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,i;e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){var o,s,a=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],m=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],u=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],d=[119,125,149,1],c=new n;this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var e,r,n,i,t=c.cursor;return function(){var e,r=c.cursor+3;if(s=c.limit,0<=r||r<=c.limit){for(o=r;;){if(e=c.cursor,c.in_grouping(u,97,248)){c.cursor=e;break}if(e>=c.limit)return;c.cursor=e+1}for(;!c.out_grouping(u,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(s=c.cursor)=s&&(r=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,e=c.find_among_b(a,29),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:n=c.limit-c.cursor,c.in_grouping_b(d,98,122)?c.slice_del():(c.cursor=c.limit-n,c.eq_s_b(1,"k")&&c.out_grouping_b(u,97,248)&&c.slice_del());break;case 3:c.slice_from("er")}}(),c.cursor=c.limit,r=c.limit-c.cursor,c.cursor>=s&&(e=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,c.find_among_b(m,2)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e),c.cursor=c.limit,c.cursor>=s&&(i=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,(n=c.find_among_b(l,11))?(c.bra=c.cursor,c.limit_backward=i,1==n&&c.slice_del()):c.limit_backward=i),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.pt.js b/docs/assets/javascripts/lunr/lunr.pt.js index f50fc9fa..59e766fe 100644 --- a/docs/assets/javascripts/lunr/lunr.pt.js +++ b/docs/assets/javascripts/lunr/lunr.pt.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Portuguese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var j,C,r;e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=(j=e.stemmerSupport.Among,C=e.stemmerSupport.SnowballProgram,r=new function(){var s,n,i,o=[new j("",-1,3),new j("ã",0,1),new j("õ",0,2)],a=[new j("",-1,3),new j("a~",0,1),new j("o~",0,2)],r=[new j("ic",-1,-1),new j("ad",-1,-1),new j("os",-1,-1),new j("iv",-1,1)],t=[new j("ante",-1,1),new j("avel",-1,1),new j("ível",-1,1)],u=[new j("ic",-1,1),new j("abil",-1,1),new j("iv",-1,1)],w=[new j("ica",-1,1),new j("ância",-1,1),new j("ência",-1,4),new j("ira",-1,9),new j("adora",-1,1),new j("osa",-1,1),new j("ista",-1,1),new j("iva",-1,8),new j("eza",-1,1),new j("logía",-1,2),new j("idade",-1,7),new j("ante",-1,1),new j("mente",-1,6),new j("amente",12,5),new j("ável",-1,1),new j("ível",-1,1),new j("ución",-1,3),new j("ico",-1,1),new j("ismo",-1,1),new j("oso",-1,1),new j("amento",-1,1),new j("imento",-1,1),new j("ivo",-1,8),new j("aça~o",-1,1),new j("ador",-1,1),new j("icas",-1,1),new j("ências",-1,4),new j("iras",-1,9),new j("adoras",-1,1),new j("osas",-1,1),new j("istas",-1,1),new j("ivas",-1,8),new j("ezas",-1,1),new j("logías",-1,2),new j("idades",-1,7),new j("uciones",-1,3),new j("adores",-1,1),new j("antes",-1,1),new j("aço~es",-1,1),new j("icos",-1,1),new j("ismos",-1,1),new j("osos",-1,1),new j("amentos",-1,1),new j("imentos",-1,1),new j("ivos",-1,8)],m=[new j("ada",-1,1),new j("ida",-1,1),new j("ia",-1,1),new j("aria",2,1),new j("eria",2,1),new j("iria",2,1),new j("ara",-1,1),new j("era",-1,1),new j("ira",-1,1),new j("ava",-1,1),new j("asse",-1,1),new j("esse",-1,1),new j("isse",-1,1),new j("aste",-1,1),new j("este",-1,1),new j("iste",-1,1),new j("ei",-1,1),new j("arei",16,1),new j("erei",16,1),new j("irei",16,1),new j("am",-1,1),new j("iam",20,1),new j("ariam",21,1),new j("eriam",21,1),new j("iriam",21,1),new j("aram",20,1),new j("eram",20,1),new j("iram",20,1),new j("avam",20,1),new j("em",-1,1),new j("arem",29,1),new j("erem",29,1),new j("irem",29,1),new j("assem",29,1),new j("essem",29,1),new j("issem",29,1),new j("ado",-1,1),new j("ido",-1,1),new j("ando",-1,1),new j("endo",-1,1),new j("indo",-1,1),new j("ara~o",-1,1),new j("era~o",-1,1),new j("ira~o",-1,1),new j("ar",-1,1),new j("er",-1,1),new j("ir",-1,1),new j("as",-1,1),new j("adas",47,1),new j("idas",47,1),new j("ias",47,1),new j("arias",50,1),new j("erias",50,1),new j("irias",50,1),new j("aras",47,1),new j("eras",47,1),new j("iras",47,1),new j("avas",47,1),new j("es",-1,1),new j("ardes",58,1),new j("erdes",58,1),new j("irdes",58,1),new j("ares",58,1),new j("eres",58,1),new j("ires",58,1),new j("asses",58,1),new j("esses",58,1),new j("isses",58,1),new j("astes",58,1),new j("estes",58,1),new j("istes",58,1),new j("is",-1,1),new j("ais",71,1),new j("eis",71,1),new j("areis",73,1),new j("ereis",73,1),new j("ireis",73,1),new j("áreis",73,1),new j("éreis",73,1),new j("íreis",73,1),new j("ásseis",73,1),new j("ésseis",73,1),new j("ísseis",73,1),new j("áveis",73,1),new j("íeis",73,1),new j("aríeis",84,1),new j("eríeis",84,1),new j("iríeis",84,1),new j("ados",-1,1),new j("idos",-1,1),new j("amos",-1,1),new j("áramos",90,1),new j("éramos",90,1),new j("íramos",90,1),new j("ávamos",90,1),new j("íamos",90,1),new j("aríamos",95,1),new j("eríamos",95,1),new j("iríamos",95,1),new j("emos",-1,1),new j("aremos",99,1),new j("eremos",99,1),new j("iremos",99,1),new j("ássemos",99,1),new j("êssemos",99,1),new j("íssemos",99,1),new j("imos",-1,1),new j("armos",-1,1),new j("ermos",-1,1),new j("irmos",-1,1),new j("ámos",-1,1),new j("arás",-1,1),new j("erás",-1,1),new j("irás",-1,1),new j("eu",-1,1),new j("iu",-1,1),new j("ou",-1,1),new j("ará",-1,1),new j("erá",-1,1),new j("irá",-1,1)],c=[new j("a",-1,1),new j("i",-1,1),new j("o",-1,1),new j("os",-1,1),new j("á",-1,1),new j("í",-1,1),new j("ó",-1,1)],l=[new j("e",-1,1),new j("ç",-1,2),new j("é",-1,1),new j("ê",-1,1)],f=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],d=new C;function v(){if(d.out_grouping(f,97,250)){for(;!d.in_grouping(f,97,250);){if(d.cursor>=d.limit)return!0;d.cursor++}return!1}return!0}function p(){var e,r,s=d.cursor;if(d.in_grouping(f,97,250))if(e=d.cursor,v()){if(d.cursor=e,function(){if(d.in_grouping(f,97,250))for(;!d.out_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}return i=d.cursor,!0}())return}else i=d.cursor;if(d.cursor=s,d.out_grouping(f,97,250)){if(r=d.cursor,v()){if(d.cursor=r,!d.in_grouping(f,97,250)||d.cursor>=d.limit)return;d.cursor++}i=d.cursor}}function _(){for(;!d.in_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}for(;!d.out_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}return!0}function h(){return i<=d.cursor}function b(){return s<=d.cursor}function g(){var e;if(d.ket=d.cursor,!(e=d.find_among_b(w,45)))return!1;switch(d.bra=d.cursor,e){case 1:if(!b())return!1;d.slice_del();break;case 2:if(!b())return!1;d.slice_from("log");break;case 3:if(!b())return!1;d.slice_from("u");break;case 4:if(!b())return!1;d.slice_from("ente");break;case 5:if(!(n<=d.cursor))return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(r,4))&&(d.bra=d.cursor,b()&&(d.slice_del(),1==e&&(d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,b()&&d.slice_del()))));break;case 6:if(!b())return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(t,3))&&(d.bra=d.cursor,1==e&&b()&&d.slice_del());break;case 7:if(!b())return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(u,3))&&(d.bra=d.cursor,1==e&&b()&&d.slice_del());break;case 8:if(!b())return!1;d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,b()&&d.slice_del());break;case 9:if(!h()||!d.eq_s_b(1,"e"))return!1;d.slice_from("ir")}return!0}function k(e,r){if(d.eq_s_b(1,e)){d.bra=d.cursor;var s=d.limit-d.cursor;if(d.eq_s_b(1,r))return d.cursor=d.limit-s,h()&&d.slice_del(),!1}return!0}function q(){if(!g()&&(d.cursor=d.limit,!function(){var e,r;if(d.cursor>=i){if(r=d.limit_backward,d.limit_backward=i,d.ket=d.cursor,e=d.find_among_b(m,120))return d.bra=d.cursor,1==e&&d.slice_del(),d.limit_backward=r,!0;d.limit_backward=r}return!1}()))return d.cursor=d.limit,d.ket=d.cursor,void((e=d.find_among_b(c,7))&&(d.bra=d.cursor,1==e&&h()&&d.slice_del()));var e;d.cursor=d.limit,d.ket=d.cursor,d.eq_s_b(1,"i")&&(d.bra=d.cursor,d.eq_s_b(1,"c")&&(d.cursor=d.limit,h()&&d.slice_del()))}this.setCurrent=function(e){d.setCurrent(e)},this.getCurrent=function(){return d.getCurrent()},this.stem=function(){var e,r=d.cursor;return function(){for(var e;;){if(d.bra=d.cursor,e=d.find_among(o,3))switch(d.ket=d.cursor,e){case 1:d.slice_from("a~");continue;case 2:d.slice_from("o~");continue;case 3:if(d.cursor>=d.limit)break;d.cursor++;continue}break}}(),d.cursor=r,e=d.cursor,i=d.limit,s=n=i,p(),d.cursor=e,_()&&(n=d.cursor,_()&&(s=d.cursor)),d.limit_backward=r,d.cursor=d.limit,q(),d.cursor=d.limit,function(){var e;if(d.ket=d.cursor,e=d.find_among_b(l,4))switch(d.bra=d.cursor,e){case 1:h()&&(d.slice_del(),d.ket=d.cursor,d.limit,d.cursor,k("u","g")&&k("i","c"));break;case 2:d.slice_from("c")}}(),d.cursor=d.limit_backward,function(){for(var e;;){if(d.bra=d.cursor,e=d.find_among(a,3))switch(d.ket=d.cursor,e){case 1:d.slice_from("ã");continue;case 2:d.slice_from("õ");continue;case 3:if(d.cursor>=d.limit)break;d.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.ro.js b/docs/assets/javascripts/lunr/lunr.ro.js index b19627e1..c5ecc96c 100644 --- a/docs/assets/javascripts/lunr/lunr.ro.js +++ b/docs/assets/javascripts/lunr/lunr.ro.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Romanian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var h,z,i;e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=(h=e.stemmerSupport.Among,z=e.stemmerSupport.SnowballProgram,i=new function(){var r,n,t,a,o=[new h("",-1,3),new h("I",0,1),new h("U",0,2)],s=[new h("ea",-1,3),new h("aţia",-1,7),new h("aua",-1,2),new h("iua",-1,4),new h("aţie",-1,7),new h("ele",-1,3),new h("ile",-1,5),new h("iile",6,4),new h("iei",-1,4),new h("atei",-1,6),new h("ii",-1,4),new h("ului",-1,1),new h("ul",-1,1),new h("elor",-1,3),new h("ilor",-1,4),new h("iilor",14,4)],c=[new h("icala",-1,4),new h("iciva",-1,4),new h("ativa",-1,5),new h("itiva",-1,6),new h("icale",-1,4),new h("aţiune",-1,5),new h("iţiune",-1,6),new h("atoare",-1,5),new h("itoare",-1,6),new h("ătoare",-1,5),new h("icitate",-1,4),new h("abilitate",-1,1),new h("ibilitate",-1,2),new h("ivitate",-1,3),new h("icive",-1,4),new h("ative",-1,5),new h("itive",-1,6),new h("icali",-1,4),new h("atori",-1,5),new h("icatori",18,4),new h("itori",-1,6),new h("ători",-1,5),new h("icitati",-1,4),new h("abilitati",-1,1),new h("ivitati",-1,3),new h("icivi",-1,4),new h("ativi",-1,5),new h("itivi",-1,6),new h("icităi",-1,4),new h("abilităi",-1,1),new h("ivităi",-1,3),new h("icităţi",-1,4),new h("abilităţi",-1,1),new h("ivităţi",-1,3),new h("ical",-1,4),new h("ator",-1,5),new h("icator",35,4),new h("itor",-1,6),new h("ător",-1,5),new h("iciv",-1,4),new h("ativ",-1,5),new h("itiv",-1,6),new h("icală",-1,4),new h("icivă",-1,4),new h("ativă",-1,5),new h("itivă",-1,6)],u=[new h("ica",-1,1),new h("abila",-1,1),new h("ibila",-1,1),new h("oasa",-1,1),new h("ata",-1,1),new h("ita",-1,1),new h("anta",-1,1),new h("ista",-1,3),new h("uta",-1,1),new h("iva",-1,1),new h("ic",-1,1),new h("ice",-1,1),new h("abile",-1,1),new h("ibile",-1,1),new h("isme",-1,3),new h("iune",-1,2),new h("oase",-1,1),new h("ate",-1,1),new h("itate",17,1),new h("ite",-1,1),new h("ante",-1,1),new h("iste",-1,3),new h("ute",-1,1),new h("ive",-1,1),new h("ici",-1,1),new h("abili",-1,1),new h("ibili",-1,1),new h("iuni",-1,2),new h("atori",-1,1),new h("osi",-1,1),new h("ati",-1,1),new h("itati",30,1),new h("iti",-1,1),new h("anti",-1,1),new h("isti",-1,3),new h("uti",-1,1),new h("işti",-1,3),new h("ivi",-1,1),new h("ităi",-1,1),new h("oşi",-1,1),new h("ităţi",-1,1),new h("abil",-1,1),new h("ibil",-1,1),new h("ism",-1,3),new h("ator",-1,1),new h("os",-1,1),new h("at",-1,1),new h("it",-1,1),new h("ant",-1,1),new h("ist",-1,3),new h("ut",-1,1),new h("iv",-1,1),new h("ică",-1,1),new h("abilă",-1,1),new h("ibilă",-1,1),new h("oasă",-1,1),new h("ată",-1,1),new h("ită",-1,1),new h("antă",-1,1),new h("istă",-1,3),new h("ută",-1,1),new h("ivă",-1,1)],w=[new h("ea",-1,1),new h("ia",-1,1),new h("esc",-1,1),new h("ăsc",-1,1),new h("ind",-1,1),new h("ând",-1,1),new h("are",-1,1),new h("ere",-1,1),new h("ire",-1,1),new h("âre",-1,1),new h("se",-1,2),new h("ase",10,1),new h("sese",10,2),new h("ise",10,1),new h("use",10,1),new h("âse",10,1),new h("eşte",-1,1),new h("ăşte",-1,1),new h("eze",-1,1),new h("ai",-1,1),new h("eai",19,1),new h("iai",19,1),new h("sei",-1,2),new h("eşti",-1,1),new h("ăşti",-1,1),new h("ui",-1,1),new h("ezi",-1,1),new h("âi",-1,1),new h("aşi",-1,1),new h("seşi",-1,2),new h("aseşi",29,1),new h("seseşi",29,2),new h("iseşi",29,1),new h("useşi",29,1),new h("âseşi",29,1),new h("işi",-1,1),new h("uşi",-1,1),new h("âşi",-1,1),new h("aţi",-1,2),new h("eaţi",38,1),new h("iaţi",38,1),new h("eţi",-1,2),new h("iţi",-1,2),new h("âţi",-1,2),new h("arăţi",-1,1),new h("serăţi",-1,2),new h("aserăţi",45,1),new h("seserăţi",45,2),new h("iserăţi",45,1),new h("userăţi",45,1),new h("âserăţi",45,1),new h("irăţi",-1,1),new h("urăţi",-1,1),new h("ârăţi",-1,1),new h("am",-1,1),new h("eam",54,1),new h("iam",54,1),new h("em",-1,2),new h("asem",57,1),new h("sesem",57,2),new h("isem",57,1),new h("usem",57,1),new h("âsem",57,1),new h("im",-1,2),new h("âm",-1,2),new h("ăm",-1,2),new h("arăm",65,1),new h("serăm",65,2),new h("aserăm",67,1),new h("seserăm",67,2),new h("iserăm",67,1),new h("userăm",67,1),new h("âserăm",67,1),new h("irăm",65,1),new h("urăm",65,1),new h("ârăm",65,1),new h("au",-1,1),new h("eau",76,1),new h("iau",76,1),new h("indu",-1,1),new h("ându",-1,1),new h("ez",-1,1),new h("ească",-1,1),new h("ară",-1,1),new h("seră",-1,2),new h("aseră",84,1),new h("seseră",84,2),new h("iseră",84,1),new h("useră",84,1),new h("âseră",84,1),new h("iră",-1,1),new h("ură",-1,1),new h("âră",-1,1),new h("ează",-1,1)],i=[new h("a",-1,1),new h("e",-1,1),new h("ie",1,1),new h("i",-1,1),new h("ă",-1,1)],m=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],l=new z;function f(e,i){l.eq_s(1,e)&&(l.ket=l.cursor,l.in_grouping(m,97,259)&&l.slice_from(i))}function p(){if(l.out_grouping(m,97,259)){for(;!l.in_grouping(m,97,259);){if(l.cursor>=l.limit)return!0;l.cursor++}return!1}return!0}function d(){var e,i,r=l.cursor;if(l.in_grouping(m,97,259)){if(e=l.cursor,!p())return void(a=l.cursor);if(l.cursor=e,!function(){if(l.in_grouping(m,97,259))for(;!l.out_grouping(m,97,259);){if(l.cursor>=l.limit)return!0;l.cursor++}return!1}())return void(a=l.cursor)}l.cursor=r,l.out_grouping(m,97,259)&&(i=l.cursor,p()&&(l.cursor=i,l.in_grouping(m,97,259)&&l.cursor=l.limit)return!1;l.cursor++}for(;!l.out_grouping(m,97,259);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function v(){return t<=l.cursor}function _(){var e,i=l.limit-l.cursor;if(l.ket=l.cursor,(e=l.find_among_b(c,46))&&(l.bra=l.cursor,v())){switch(e){case 1:l.slice_from("abil");break;case 2:l.slice_from("ibil");break;case 3:l.slice_from("iv");break;case 4:l.slice_from("ic");break;case 5:l.slice_from("at");break;case 6:l.slice_from("it")}return r=!0,l.cursor=l.limit-i,!0}return!1}function g(){var e,i;for(r=!1;;)if(i=l.limit-l.cursor,!_()){l.cursor=l.limit-i;break}if(l.ket=l.cursor,(e=l.find_among_b(u,62))&&(l.bra=l.cursor,n<=l.cursor)){switch(e){case 1:l.slice_del();break;case 2:l.eq_s_b(1,"ţ")&&(l.bra=l.cursor,l.slice_from("t"));break;case 3:l.slice_from("ist")}r=!0}}function k(){var e;l.ket=l.cursor,(e=l.find_among_b(i,5))&&(l.bra=l.cursor,a<=l.cursor&&1==e&&l.slice_del())}this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){var e,i=l.cursor;return function(){for(var e,i;e=l.cursor,l.in_grouping(m,97,259)&&(i=l.cursor,l.bra=i,f("u","U"),l.cursor=i,f("i","I")),l.cursor=e,!(l.cursor>=l.limit);)l.cursor++}(),l.cursor=i,e=l.cursor,a=l.limit,n=t=a,d(),l.cursor=e,b()&&(t=l.cursor,b()&&(n=l.cursor)),l.limit_backward=i,l.cursor=l.limit,function(){var e,i;if(l.ket=l.cursor,(e=l.find_among_b(s,16))&&(l.bra=l.cursor,v()))switch(e){case 1:l.slice_del();break;case 2:l.slice_from("a");break;case 3:l.slice_from("e");break;case 4:l.slice_from("i");break;case 5:i=l.limit-l.cursor,l.eq_s_b(2,"ab")||(l.cursor=l.limit-i,l.slice_from("i"));break;case 6:l.slice_from("at");break;case 7:l.slice_from("aţi")}}(),l.cursor=l.limit,g(),l.cursor=l.limit,r||(l.cursor=l.limit,function(){var e,i,r;if(l.cursor>=a){if(i=l.limit_backward,l.limit_backward=a,l.ket=l.cursor,e=l.find_among_b(w,94))switch(l.bra=l.cursor,e){case 1:if(r=l.limit-l.cursor,!l.out_grouping_b(m,97,259)&&(l.cursor=l.limit-r,!l.eq_s_b(1,"u")))break;case 2:l.slice_del()}l.limit_backward=i}}(),l.cursor=l.limit),k(),l.cursor=l.limit_backward,function(){for(var e;;){if(l.bra=l.cursor,e=l.find_among(o,3))switch(l.ket=l.cursor,e){case 1:l.slice_from("i");continue;case 2:l.slice_from("u");continue;case 3:if(l.cursor>=l.limit)break;l.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.ru.js b/docs/assets/javascripts/lunr/lunr.ru.js index ac992480..104bc6e8 100644 --- a/docs/assets/javascripts/lunr/lunr.ru.js +++ b/docs/assets/javascripts/lunr/lunr.ru.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Russian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var h,g,n;e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=(h=e.stemmerSupport.Among,g=e.stemmerSupport.SnowballProgram,n=new function(){var n,e,r=[new h("в",-1,1),new h("ив",0,2),new h("ыв",0,2),new h("вши",-1,1),new h("ивши",3,2),new h("ывши",3,2),new h("вшись",-1,1),new h("ившись",6,2),new h("ывшись",6,2)],t=[new h("ее",-1,1),new h("ие",-1,1),new h("ое",-1,1),new h("ые",-1,1),new h("ими",-1,1),new h("ыми",-1,1),new h("ей",-1,1),new h("ий",-1,1),new h("ой",-1,1),new h("ый",-1,1),new h("ем",-1,1),new h("им",-1,1),new h("ом",-1,1),new h("ым",-1,1),new h("его",-1,1),new h("ого",-1,1),new h("ему",-1,1),new h("ому",-1,1),new h("их",-1,1),new h("ых",-1,1),new h("ею",-1,1),new h("ою",-1,1),new h("ую",-1,1),new h("юю",-1,1),new h("ая",-1,1),new h("яя",-1,1)],w=[new h("ем",-1,1),new h("нн",-1,1),new h("вш",-1,1),new h("ивш",2,2),new h("ывш",2,2),new h("щ",-1,1),new h("ющ",5,1),new h("ующ",6,2)],i=[new h("сь",-1,1),new h("ся",-1,1)],u=[new h("ла",-1,1),new h("ила",0,2),new h("ыла",0,2),new h("на",-1,1),new h("ена",3,2),new h("ете",-1,1),new h("ите",-1,2),new h("йте",-1,1),new h("ейте",7,2),new h("уйте",7,2),new h("ли",-1,1),new h("или",10,2),new h("ыли",10,2),new h("й",-1,1),new h("ей",13,2),new h("уй",13,2),new h("л",-1,1),new h("ил",16,2),new h("ыл",16,2),new h("ем",-1,1),new h("им",-1,2),new h("ым",-1,2),new h("н",-1,1),new h("ен",22,2),new h("ло",-1,1),new h("ило",24,2),new h("ыло",24,2),new h("но",-1,1),new h("ено",27,2),new h("нно",27,1),new h("ет",-1,1),new h("ует",30,2),new h("ит",-1,2),new h("ыт",-1,2),new h("ют",-1,1),new h("уют",34,2),new h("ят",-1,2),new h("ны",-1,1),new h("ены",37,2),new h("ть",-1,1),new h("ить",39,2),new h("ыть",39,2),new h("ешь",-1,1),new h("ишь",-1,2),new h("ю",-1,2),new h("ую",44,2)],s=[new h("а",-1,1),new h("ев",-1,1),new h("ов",-1,1),new h("е",-1,1),new h("ие",3,1),new h("ье",3,1),new h("и",-1,1),new h("еи",6,1),new h("ии",6,1),new h("ами",6,1),new h("ями",6,1),new h("иями",10,1),new h("й",-1,1),new h("ей",12,1),new h("ией",13,1),new h("ий",12,1),new h("ой",12,1),new h("ам",-1,1),new h("ем",-1,1),new h("ием",18,1),new h("ом",-1,1),new h("ям",-1,1),new h("иям",21,1),new h("о",-1,1),new h("у",-1,1),new h("ах",-1,1),new h("ях",-1,1),new h("иях",26,1),new h("ы",-1,1),new h("ь",-1,1),new h("ю",-1,1),new h("ию",30,1),new h("ью",30,1),new h("я",-1,1),new h("ия",33,1),new h("ья",33,1)],o=[new h("ост",-1,1),new h("ость",-1,1)],c=[new h("ейше",-1,1),new h("н",-1,2),new h("ейш",-1,1),new h("ь",-1,3)],m=[33,65,8,232],l=new g;function f(){for(;!l.in_grouping(m,1072,1103);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function a(){for(;!l.out_grouping(m,1072,1103);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function p(e,n){var r,t;if(l.ket=l.cursor,r=l.find_among_b(e,n)){switch(l.bra=l.cursor,r){case 1:if(t=l.limit-l.cursor,!l.eq_s_b(1,"а")&&(l.cursor=l.limit-t,!l.eq_s_b(1,"я")))return!1;case 2:l.slice_del()}return!0}return!1}function d(e,n){var r;return l.ket=l.cursor,!!(r=l.find_among_b(e,n))&&(l.bra=l.cursor,1==r&&l.slice_del(),!0)}function _(){return!!d(t,26)&&(p(w,8),!0)}function b(){var e;l.ket=l.cursor,(e=l.find_among_b(o,2))&&(l.bra=l.cursor,n<=l.cursor&&1==e&&l.slice_del())}this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){return e=l.limit,n=e,f()&&(e=l.cursor,a()&&f()&&a()&&(n=l.cursor)),l.cursor=l.limit,!(l.cursor>3]&1<<(7&s))return this.cursor++,!0}return!1},in_grouping_b:function(r,t,i){if(this.cursor>this.limit_backward){var s=b.charCodeAt(this.cursor-1);if(s<=i&&t<=s&&r[(s-=t)>>3]&1<<(7&s))return this.cursor--,!0}return!1},out_grouping:function(r,t,i){if(this.cursor>3]&1<<(7&s)))return this.cursor++,!0}return!1},out_grouping_b:function(r,t,i){if(this.cursor>this.limit_backward){var s=b.charCodeAt(this.cursor-1);if(i>3]&1<<(7&s)))return this.cursor--,!0}return!1},eq_s:function(r,t){if(this.limit-this.cursor>1),a=0,f=u=(l=r[i]).s_size){if(this.cursor=e+l.s_size,!l.method)return l.result;var m=l.method();if(this.cursor=e+l.s_size,m)return l.result}if((i=l.substring_i)<0)return 0}},find_among_b:function(r,t){for(var i=0,s=t,e=this.cursor,n=this.limit_backward,u=0,o=0,h=!1;;){for(var c=i+(s-i>>1),a=0,f=u=(_=r[i]).s_size){if(this.cursor=e-_.s_size,!_.method)return _.result;var m=_.method();if(this.cursor=e-_.s_size,m)return _.result}if((i=_.substring_i)<0)return 0}},replace_s:function(r,t,i){var s=i.length-(t-r);return b=b.substring(0,r)+i+b.substring(t),this.limit+=s,this.cursor>=t?this.cursor+=s:this.cursor>r&&(this.cursor=r),s},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>b.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),b.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.sv.js b/docs/assets/javascripts/lunr/lunr.sv.js index 6daf5f9d..a46a4e70 100644 --- a/docs/assets/javascripts/lunr/lunr.sv.js +++ b/docs/assets/javascripts/lunr/lunr.sv.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Swedish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,l,n;e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=(r=e.stemmerSupport.Among,l=e.stemmerSupport.SnowballProgram,n=new function(){var n,t,i=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],s=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],o=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],u=[119,127,149],m=new l;this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var e,r=m.cursor;return function(){var e,r=m.cursor+3;if(t=m.limit,0<=r||r<=m.limit){for(n=r;;){if(e=m.cursor,m.in_grouping(o,97,246)){m.cursor=e;break}if(m.cursor=e,m.cursor>=m.limit)return;m.cursor++}for(;!m.out_grouping(o,97,246);){if(m.cursor>=m.limit)return;m.cursor++}(t=m.cursor)=t&&(m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(i,37),m.limit_backward=r,e))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.in_grouping_b(u,98,121)&&m.slice_del()}}(),m.cursor=m.limit,e=m.limit_backward,m.cursor>=t&&(m.limit_backward=t,m.cursor=m.limit,m.find_among_b(s,7)&&(m.cursor=m.limit,m.ket=m.cursor,m.cursor>m.limit_backward&&(m.bra=--m.cursor,m.slice_del())),m.limit_backward=e),m.cursor=m.limit,function(){var e,r;if(m.cursor>=t){if(r=m.limit_backward,m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(a,5))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.slice_from("lös");break;case 3:m.slice_from("full")}m.limit_backward=r}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.th.js b/docs/assets/javascripts/lunr/lunr.th.js index ee8ef373..7f9887f7 100644 --- a/docs/assets/javascripts/lunr/lunr.th.js +++ b/docs/assets/javascripts/lunr/lunr.th.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Thai` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2017, Keerati Thiwanruk + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(t){if(void 0===t)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===t.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==t.version[0];t.th=function(){this.pipeline.reset(),this.pipeline.add(t.th.trimmer),i?this.tokenizer=t.th.tokenizer:(t.tokenizer&&(t.tokenizer=t.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=t.th.tokenizer))},t.th.wordCharacters="[฀-๿]",t.th.trimmer=t.trimmerSupport.generateTrimmer(t.th.wordCharacters),t.Pipeline.registerFunction(t.th.trimmer,"trimmer-th");var n=t.wordcut;n.init(),t.th.tokenizer=function(e){if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return i?new t.Token(e):e});var r=e.toString().replace(/^\s+/,"");return n.cut(r).split("|")}}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.tr.js b/docs/assets/javascripts/lunr/lunr.tr.js index e8fb5a7d..64ba95cb 100644 --- a/docs/assets/javascripts/lunr/lunr.tr.js +++ b/docs/assets/javascripts/lunr/lunr.tr.js @@ -1 +1,17 @@ +/*! + * Lunr languages, `Turkish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ !function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var mr,dr,i;r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=(mr=r.stemmerSupport.Among,dr=r.stemmerSupport.SnowballProgram,i=new function(){var t,r=[new mr("m",-1,-1),new mr("n",-1,-1),new mr("miz",-1,-1),new mr("niz",-1,-1),new mr("muz",-1,-1),new mr("nuz",-1,-1),new mr("müz",-1,-1),new mr("nüz",-1,-1),new mr("mız",-1,-1),new mr("nız",-1,-1)],i=[new mr("leri",-1,-1),new mr("ları",-1,-1)],e=[new mr("ni",-1,-1),new mr("nu",-1,-1),new mr("nü",-1,-1),new mr("nı",-1,-1)],n=[new mr("in",-1,-1),new mr("un",-1,-1),new mr("ün",-1,-1),new mr("ın",-1,-1)],u=[new mr("a",-1,-1),new mr("e",-1,-1)],o=[new mr("na",-1,-1),new mr("ne",-1,-1)],s=[new mr("da",-1,-1),new mr("ta",-1,-1),new mr("de",-1,-1),new mr("te",-1,-1)],c=[new mr("nda",-1,-1),new mr("nde",-1,-1)],l=[new mr("dan",-1,-1),new mr("tan",-1,-1),new mr("den",-1,-1),new mr("ten",-1,-1)],a=[new mr("ndan",-1,-1),new mr("nden",-1,-1)],m=[new mr("la",-1,-1),new mr("le",-1,-1)],d=[new mr("ca",-1,-1),new mr("ce",-1,-1)],f=[new mr("im",-1,-1),new mr("um",-1,-1),new mr("üm",-1,-1),new mr("ım",-1,-1)],b=[new mr("sin",-1,-1),new mr("sun",-1,-1),new mr("sün",-1,-1),new mr("sın",-1,-1)],w=[new mr("iz",-1,-1),new mr("uz",-1,-1),new mr("üz",-1,-1),new mr("ız",-1,-1)],_=[new mr("siniz",-1,-1),new mr("sunuz",-1,-1),new mr("sünüz",-1,-1),new mr("sınız",-1,-1)],k=[new mr("lar",-1,-1),new mr("ler",-1,-1)],p=[new mr("niz",-1,-1),new mr("nuz",-1,-1),new mr("nüz",-1,-1),new mr("nız",-1,-1)],g=[new mr("dir",-1,-1),new mr("tir",-1,-1),new mr("dur",-1,-1),new mr("tur",-1,-1),new mr("dür",-1,-1),new mr("tür",-1,-1),new mr("dır",-1,-1),new mr("tır",-1,-1)],y=[new mr("casına",-1,-1),new mr("cesine",-1,-1)],z=[new mr("di",-1,-1),new mr("ti",-1,-1),new mr("dik",-1,-1),new mr("tik",-1,-1),new mr("duk",-1,-1),new mr("tuk",-1,-1),new mr("dük",-1,-1),new mr("tük",-1,-1),new mr("dık",-1,-1),new mr("tık",-1,-1),new mr("dim",-1,-1),new mr("tim",-1,-1),new mr("dum",-1,-1),new mr("tum",-1,-1),new mr("düm",-1,-1),new mr("tüm",-1,-1),new mr("dım",-1,-1),new mr("tım",-1,-1),new mr("din",-1,-1),new mr("tin",-1,-1),new mr("dun",-1,-1),new mr("tun",-1,-1),new mr("dün",-1,-1),new mr("tün",-1,-1),new mr("dın",-1,-1),new mr("tın",-1,-1),new mr("du",-1,-1),new mr("tu",-1,-1),new mr("dü",-1,-1),new mr("tü",-1,-1),new mr("dı",-1,-1),new mr("tı",-1,-1)],h=[new mr("sa",-1,-1),new mr("se",-1,-1),new mr("sak",-1,-1),new mr("sek",-1,-1),new mr("sam",-1,-1),new mr("sem",-1,-1),new mr("san",-1,-1),new mr("sen",-1,-1)],v=[new mr("miş",-1,-1),new mr("muş",-1,-1),new mr("müş",-1,-1),new mr("mış",-1,-1)],q=[new mr("b",-1,1),new mr("c",-1,2),new mr("d",-1,3),new mr("ğ",-1,4)],C=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],P=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],F=[65],S=[65],W=[["a",[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],97,305],["e",[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],101,252],["ı",[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],97,305],["i",[17],101,105],["o",F,111,117],["ö",S,246,252],["u",F,111,117]],L=new dr;function x(r,i,e){for(;;){var n=L.limit-L.cursor;if(L.in_grouping_b(r,i,e)){L.cursor=L.limit-n;break}if(L.cursor=L.limit-n,L.cursor<=L.limit_backward)return!1;L.cursor--}return!0}function A(){var r,i;r=L.limit-L.cursor,x(C,97,305);for(var e=0;eL.limit_backward&&(L.cursor--,e=L.limit-L.cursor,i()))?(L.cursor=L.limit-e,!0):(L.cursor=L.limit-n,r()?(L.cursor=L.limit-n,!1):(L.cursor=L.limit-n,!(L.cursor<=L.limit_backward)&&(L.cursor--,!!i()&&(L.cursor=L.limit-n,!0))))}function j(r){return E(r,function(){return L.in_grouping_b(C,97,305)})}function T(){return j(function(){return L.eq_s_b(1,"n")})}function Z(){return j(function(){return L.eq_s_b(1,"y")})}function B(){return L.find_among_b(r,10)&&E(function(){return L.in_grouping_b(P,105,305)},function(){return L.out_grouping_b(C,97,305)})}function D(){return A()&&L.in_grouping_b(P,105,305)&&j(function(){return L.eq_s_b(1,"s")})}function G(){return L.find_among_b(i,2)}function H(){return A()&&L.find_among_b(n,4)&&T()}function I(){return A()&&L.find_among_b(s,4)}function J(){return A()&&L.find_among_b(c,2)}function K(){return A()&&L.find_among_b(f,4)&&Z()}function M(){return A()&&L.find_among_b(b,4)}function N(){return A()&&L.find_among_b(w,4)&&Z()}function O(){return L.find_among_b(_,4)}function Q(){return A()&&L.find_among_b(k,2)}function R(){return A()&&L.find_among_b(g,8)}function U(){return A()&&L.find_among_b(z,32)&&Z()}function V(){return L.find_among_b(h,8)&&Z()}function X(){return A()&&L.find_among_b(v,4)&&Z()}function Y(){var r=L.limit-L.cursor;return!(X()||(L.cursor=L.limit-r,U()||(L.cursor=L.limit-r,V()||(L.cursor=L.limit-r,L.eq_s_b(3,"ken")&&Z()))))}function $(){if(L.find_among_b(y,2)){var r=L.limit-L.cursor;if(O()||(L.cursor=L.limit-r,Q()||(L.cursor=L.limit-r,K()||(L.cursor=L.limit-r,M()||(L.cursor=L.limit-r,N()||(L.cursor=L.limit-r))))),X())return!1}return!0}function rr(){if(!A()||!L.find_among_b(p,4))return!0;var r=L.limit-L.cursor;return!U()&&(L.cursor=L.limit-r,!V())}function ir(){var r,i,e,n=L.limit-L.cursor;if(L.ket=L.cursor,t=!0,Y()&&(L.cursor=L.limit-n,$()&&(L.cursor=L.limit-n,function(){if(Q()){L.bra=L.cursor,L.slice_del();var r=L.limit-L.cursor;return L.ket=L.cursor,R()||(L.cursor=L.limit-r,U()||(L.cursor=L.limit-r,V()||(L.cursor=L.limit-r,X()||(L.cursor=L.limit-r)))),t=!1}return!0}()&&(L.cursor=L.limit-n,rr()&&(L.cursor=L.limit-n,e=L.limit-L.cursor,!(O()||(L.cursor=L.limit-e,N()||(L.cursor=L.limit-e,M()||(L.cursor=L.limit-e,K()))))||(L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,X()||(L.cursor=L.limit-i),0)))))){if(L.cursor=L.limit-n,!R())return;L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,r=L.limit-L.cursor,O()||(L.cursor=L.limit-r,Q()||(L.cursor=L.limit-r,K()||(L.cursor=L.limit-r,M()||(L.cursor=L.limit-r,N()||(L.cursor=L.limit-r))))),X()||(L.cursor=L.limit-r)}L.bra=L.cursor,L.slice_del()}function er(){var r,i,e,n;if(L.ket=L.cursor,L.eq_s_b(2,"ki")){if(r=L.limit-L.cursor,I())return L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,Q()?(L.bra=L.cursor,L.slice_del(),er()):(L.cursor=L.limit-i,B()&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()))),!0;if(L.cursor=L.limit-r,H()){if(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,e=L.limit-L.cursor,G())L.bra=L.cursor,L.slice_del();else{if(L.cursor=L.limit-e,L.ket=L.cursor,!B()&&(L.cursor=L.limit-e,!D()&&(L.cursor=L.limit-e,!er())))return!0;L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())}return!0}if(L.cursor=L.limit-r,J()){if(n=L.limit-L.cursor,G())L.bra=L.cursor,L.slice_del();else if(L.cursor=L.limit-n,D())L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er());else if(L.cursor=L.limit-n,!er())return!1;return!0}}return!1}function nr(r){if(L.ket=L.cursor,!J()&&(L.cursor=L.limit-r,!A()||!L.find_among_b(o,2)))return!1;var i=L.limit-L.cursor;if(G())L.bra=L.cursor,L.slice_del();else if(L.cursor=L.limit-i,D())L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er());else if(L.cursor=L.limit-i,!er())return!1;return!0}function tr(r){if(L.ket=L.cursor,!(A()&&L.find_among_b(a,2)||(L.cursor=L.limit-r,A()&&L.find_among_b(e,4))))return!1;var i=L.limit-L.cursor;return!(!D()&&(L.cursor=L.limit-i,!G()))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()),!0)}function ur(){var r,i=L.limit-L.cursor;return L.ket=L.cursor,!!(H()||(L.cursor=L.limit-i,A()&&L.find_among_b(m,2)&&Z()))&&(L.bra=L.cursor,L.slice_del(),r=L.limit-L.cursor,L.ket=L.cursor,!(!Q()||(L.bra=L.cursor,L.slice_del(),!er()))||(L.cursor=L.limit-r,L.ket=L.cursor,(B()||(L.cursor=L.limit-r,D()||(L.cursor=L.limit-r,er())))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())),!0))}function or(){var r,i,e=L.limit-L.cursor;if(L.ket=L.cursor,!(I()||(L.cursor=L.limit-e,A()&&L.in_grouping_b(P,105,305)&&Z()||(L.cursor=L.limit-e,A()&&L.find_among_b(u,2)&&Z()))))return!1;if(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,r=L.limit-L.cursor,B())L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,Q()||(L.cursor=L.limit-i);else if(L.cursor=L.limit-r,!Q())return!0;return L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,er(),!0}function sr(){var r,i,e=L.limit-L.cursor;if(L.ket=L.cursor,Q())return L.bra=L.cursor,L.slice_del(),void er();if(L.cursor=L.limit-e,L.ket=L.cursor,A()&&L.find_among_b(d,2)&&T())if(L.bra=L.cursor,L.slice_del(),r=L.limit-L.cursor,L.ket=L.cursor,G())L.bra=L.cursor,L.slice_del();else{if(L.cursor=L.limit-r,L.ket=L.cursor,!B()&&(L.cursor=L.limit-r,!D())){if(L.cursor=L.limit-r,L.ket=L.cursor,!Q())return;if(L.bra=L.cursor,L.slice_del(),!er())return}L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())}else if(L.cursor=L.limit-e,!nr(e)&&(L.cursor=L.limit-e,!tr(e))){if(L.cursor=L.limit-e,L.ket=L.cursor,A()&&L.find_among_b(l,4))return L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,i=L.limit-L.cursor,void(B()?(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())):(L.cursor=L.limit-i,Q()?(L.bra=L.cursor,L.slice_del()):L.cursor=L.limit-i,er()));if(L.cursor=L.limit-e,!ur()){if(L.cursor=L.limit-e,G())return L.bra=L.cursor,void L.slice_del();L.cursor=L.limit-e,er()||(L.cursor=L.limit-e,or()||(L.cursor=L.limit-e,L.ket=L.cursor,(B()||(L.cursor=L.limit-e,D()))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()))))}}}function cr(r,i,e){if(L.cursor=L.limit-r,function(){for(;;){var r=L.limit-L.cursor;if(L.in_grouping_b(C,97,305)){L.cursor=L.limit-r;break}if(L.cursor=L.limit-r,L.cursor<=L.limit_backward)return!1;L.cursor--}return!0}()){var n=L.limit-L.cursor;if(!L.eq_s_b(1,i)&&(L.cursor=L.limit-n,!L.eq_s_b(1,e)))return!0;L.cursor=L.limit-r;var t=L.cursor;return L.insert(L.cursor,L.cursor,e),L.cursor=t,!1}return!0}function lr(r,i,e){for(;!L.eq_s(i,e);){if(L.cursor>=L.limit)return!0;L.cursor++}return i!=L.limit||(L.cursor=r,!1)}function ar(){var r,i,e=L.cursor;return!(!lr(r=L.cursor,2,"ad")||!lr(L.cursor=r,5,"soyad"))&&(L.limit_backward=e,L.cursor=L.limit,i=L.limit-L.cursor,(L.eq_s_b(1,"d")||(L.cursor=L.limit-i,L.eq_s_b(1,"g")))&&cr(i,"a","ı")&&cr(i,"e","i")&&cr(i,"o","u")&&cr(i,"ö","ü"),L.cursor=L.limit,function(){var r;if(L.ket=L.cursor,r=L.find_among_b(q,4))switch(L.bra=L.cursor,r){case 1:L.slice_from("p");break;case 2:L.slice_from("ç");break;case 3:L.slice_from("t");break;case 4:L.slice_from("k")}}(),!0)}this.setCurrent=function(r){L.setCurrent(r)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){return!!(function(){for(var r,i=L.cursor,e=2;;){for(r=L.cursor;!L.in_grouping(C,97,305);){if(L.cursor>=L.limit)return L.cursor=r,!(0.md-nav__link{color:inherit}button[data-md-color-primary=pink]{background-color:#e91e63}[data-md-color-primary=pink] .md-typeset a{color:#e91e63}[data-md-color-primary=pink] .md-header,[data-md-color-primary=pink] .md-hero{background-color:#e91e63}[data-md-color-primary=pink] .md-nav__link--active,[data-md-color-primary=pink] .md-nav__link:active{color:#e91e63}[data-md-color-primary=pink] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=purple]{background-color:#ab47bc}[data-md-color-primary=purple] .md-typeset a{color:#ab47bc}[data-md-color-primary=purple] .md-header,[data-md-color-primary=purple] .md-hero{background-color:#ab47bc}[data-md-color-primary=purple] .md-nav__link--active,[data-md-color-primary=purple] .md-nav__link:active{color:#ab47bc}[data-md-color-primary=purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-purple]{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-typeset a{color:#7e57c2}[data-md-color-primary=deep-purple] .md-header,[data-md-color-primary=deep-purple] .md-hero{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__link--active,[data-md-color-primary=deep-purple] .md-nav__link:active{color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=indigo]{background-color:#3f51b5}[data-md-color-primary=indigo] .md-typeset a{color:#3f51b5}[data-md-color-primary=indigo] .md-header,[data-md-color-primary=indigo] .md-hero{background-color:#3f51b5}[data-md-color-primary=indigo] .md-nav__link--active,[data-md-color-primary=indigo] .md-nav__link:active{color:#3f51b5}[data-md-color-primary=indigo] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue]{background-color:#2196f3}[data-md-color-primary=blue] .md-typeset a{color:#2196f3}[data-md-color-primary=blue] .md-header,[data-md-color-primary=blue] .md-hero{background-color:#2196f3}[data-md-color-primary=blue] .md-nav__link--active,[data-md-color-primary=blue] .md-nav__link:active{color:#2196f3}[data-md-color-primary=blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-blue]{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-typeset a{color:#03a9f4}[data-md-color-primary=light-blue] .md-header,[data-md-color-primary=light-blue] .md-hero{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__link--active,[data-md-color-primary=light-blue] .md-nav__link:active{color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=cyan]{background-color:#00bcd4}[data-md-color-primary=cyan] .md-typeset a{color:#00bcd4}[data-md-color-primary=cyan] .md-header,[data-md-color-primary=cyan] .md-hero{background-color:#00bcd4}[data-md-color-primary=cyan] .md-nav__link--active,[data-md-color-primary=cyan] .md-nav__link:active{color:#00bcd4}[data-md-color-primary=cyan] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=teal]{background-color:#009688}[data-md-color-primary=teal] .md-typeset a{color:#009688}[data-md-color-primary=teal] .md-header,[data-md-color-primary=teal] .md-hero{background-color:#009688}[data-md-color-primary=teal] .md-nav__link--active,[data-md-color-primary=teal] .md-nav__link:active{color:#009688}[data-md-color-primary=teal] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=green]{background-color:#4caf50}[data-md-color-primary=green] .md-typeset a{color:#4caf50}[data-md-color-primary=green] .md-header,[data-md-color-primary=green] .md-hero{background-color:#4caf50}[data-md-color-primary=green] .md-nav__link--active,[data-md-color-primary=green] .md-nav__link:active{color:#4caf50}[data-md-color-primary=green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-green]{background-color:#7cb342}[data-md-color-primary=light-green] .md-typeset a{color:#7cb342}[data-md-color-primary=light-green] .md-header,[data-md-color-primary=light-green] .md-hero{background-color:#7cb342}[data-md-color-primary=light-green] .md-nav__link--active,[data-md-color-primary=light-green] .md-nav__link:active{color:#7cb342}[data-md-color-primary=light-green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=lime]{background-color:#c0ca33}[data-md-color-primary=lime] .md-typeset a{color:#c0ca33}[data-md-color-primary=lime] .md-header,[data-md-color-primary=lime] .md-hero{background-color:#c0ca33}[data-md-color-primary=lime] .md-nav__link--active,[data-md-color-primary=lime] .md-nav__link:active{color:#c0ca33}[data-md-color-primary=lime] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=yellow]{background-color:#f9a825}[data-md-color-primary=yellow] .md-typeset a{color:#f9a825}[data-md-color-primary=yellow] .md-header,[data-md-color-primary=yellow] .md-hero{background-color:#f9a825}[data-md-color-primary=yellow] .md-nav__link--active,[data-md-color-primary=yellow] .md-nav__link:active{color:#f9a825}[data-md-color-primary=yellow] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=amber]{background-color:#ffa000}[data-md-color-primary=amber] .md-typeset a{color:#ffa000}[data-md-color-primary=amber] .md-header,[data-md-color-primary=amber] .md-hero{background-color:#ffa000}[data-md-color-primary=amber] .md-nav__link--active,[data-md-color-primary=amber] .md-nav__link:active{color:#ffa000}[data-md-color-primary=amber] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=orange]{background-color:#fb8c00}[data-md-color-primary=orange] .md-typeset a{color:#fb8c00}[data-md-color-primary=orange] .md-header,[data-md-color-primary=orange] .md-hero{background-color:#fb8c00}[data-md-color-primary=orange] .md-nav__link--active,[data-md-color-primary=orange] .md-nav__link:active{color:#fb8c00}[data-md-color-primary=orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-orange]{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-typeset a{color:#ff7043}[data-md-color-primary=deep-orange] .md-header,[data-md-color-primary=deep-orange] .md-hero{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__link--active,[data-md-color-primary=deep-orange] .md-nav__link:active{color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=brown]{background-color:#795548}[data-md-color-primary=brown] .md-typeset a{color:#795548}[data-md-color-primary=brown] .md-header,[data-md-color-primary=brown] .md-hero{background-color:#795548}[data-md-color-primary=brown] .md-nav__link--active,[data-md-color-primary=brown] .md-nav__link:active{color:#795548}[data-md-color-primary=brown] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=grey]{background-color:#757575}[data-md-color-primary=grey] .md-typeset a{color:#757575}[data-md-color-primary=grey] .md-header,[data-md-color-primary=grey] .md-hero{background-color:#757575}[data-md-color-primary=grey] .md-nav__link--active,[data-md-color-primary=grey] .md-nav__link:active{color:#757575}[data-md-color-primary=grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue-grey]{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-typeset a{color:#546e7a}[data-md-color-primary=blue-grey] .md-header,[data-md-color-primary=blue-grey] .md-hero{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__link--active,[data-md-color-primary=blue-grey] .md-nav__link:active{color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=white]{box-shadow:inset 0 0 .05rem rgba(0,0,0,.54)}[data-md-color-primary=white] .md-header,[data-md-color-primary=white] .md-hero,button[data-md-color-primary=white]{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero--expand{border-bottom:.05rem solid rgba(0,0,0,.07)}button[data-md-color-accent=red]{background-color:#ff1744}[data-md-color-accent=red] .md-typeset a:active,[data-md-color-accent=red] .md-typeset a:hover{color:#ff1744}[data-md-color-accent=red] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=red] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-nav__link:focus,[data-md-color-accent=red] .md-nav__link:hover,[data-md-color-accent=red] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=red] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=red] .md-typeset .md-clipboard:active:before,[data-md-color-accent=red] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=red] .md-typeset [id] .headerlink:focus,[data-md-color-accent=red] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=red] .md-typeset [id]:target .headerlink{color:#ff1744}[data-md-color-accent=red] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-search-result__link:hover,[data-md-color-accent=red] .md-search-result__link[data-md-state=active]{background-color:rgba(255,23,68,.1)}[data-md-color-accent=red] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-source-file:hover:before{background-color:#ff1744}button[data-md-color-accent=pink]{background-color:#f50057}[data-md-color-accent=pink] .md-typeset a:active,[data-md-color-accent=pink] .md-typeset a:hover{color:#f50057}[data-md-color-accent=pink] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=pink] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-nav__link:focus,[data-md-color-accent=pink] .md-nav__link:hover,[data-md-color-accent=pink] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=pink] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=pink] .md-typeset .md-clipboard:active:before,[data-md-color-accent=pink] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=pink] .md-typeset [id] .headerlink:focus,[data-md-color-accent=pink] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=pink] .md-typeset [id]:target .headerlink{color:#f50057}[data-md-color-accent=pink] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-search-result__link:hover,[data-md-color-accent=pink] .md-search-result__link[data-md-state=active]{background-color:rgba(245,0,87,.1)}[data-md-color-accent=pink] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-source-file:hover:before{background-color:#f50057}button[data-md-color-accent=purple]{background-color:#e040fb}[data-md-color-accent=purple] .md-typeset a:active,[data-md-color-accent=purple] .md-typeset a:hover{color:#e040fb}[data-md-color-accent=purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-nav__link:focus,[data-md-color-accent=purple] .md-nav__link:hover,[data-md-color-accent=purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=purple] .md-typeset [id]:target .headerlink{color:#e040fb}[data-md-color-accent=purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-search-result__link:hover,[data-md-color-accent=purple] .md-search-result__link[data-md-state=active]{background-color:rgba(224,64,251,.1)}[data-md-color-accent=purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-source-file:hover:before{background-color:#e040fb}button[data-md-color-accent=deep-purple]{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset a:active,[data-md-color-accent=deep-purple] .md-typeset a:hover{color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-nav__link:focus,[data-md-color-accent=deep-purple] .md-nav__link:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-purple] .md-typeset [id]:target .headerlink{color:#7c4dff}[data-md-color-accent=deep-purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-search-result__link:hover,[data-md-color-accent=deep-purple] .md-search-result__link[data-md-state=active]{background-color:rgba(124,77,255,.1)}[data-md-color-accent=deep-purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-source-file:hover:before{background-color:#7c4dff}button[data-md-color-accent=indigo]{background-color:#536dfe}[data-md-color-accent=indigo] .md-typeset a:active,[data-md-color-accent=indigo] .md-typeset a:hover{color:#536dfe}[data-md-color-accent=indigo] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=indigo] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-nav__link:focus,[data-md-color-accent=indigo] .md-nav__link:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=indigo] .md-typeset .md-clipboard:active:before,[data-md-color-accent=indigo] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=indigo] .md-typeset [id] .headerlink:focus,[data-md-color-accent=indigo] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=indigo] .md-typeset [id]:target .headerlink{color:#536dfe}[data-md-color-accent=indigo] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-search-result__link:hover,[data-md-color-accent=indigo] .md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}[data-md-color-accent=indigo] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-source-file:hover:before{background-color:#536dfe}button[data-md-color-accent=blue]{background-color:#448aff}[data-md-color-accent=blue] .md-typeset a:active,[data-md-color-accent=blue] .md-typeset a:hover{color:#448aff}[data-md-color-accent=blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-nav__link:focus,[data-md-color-accent=blue] .md-nav__link:hover,[data-md-color-accent=blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=blue] .md-typeset [id]:target .headerlink{color:#448aff}[data-md-color-accent=blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-search-result__link:hover,[data-md-color-accent=blue] .md-search-result__link[data-md-state=active]{background-color:rgba(68,138,255,.1)}[data-md-color-accent=blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-source-file:hover:before{background-color:#448aff}button[data-md-color-accent=light-blue]{background-color:#0091ea}[data-md-color-accent=light-blue] .md-typeset a:active,[data-md-color-accent=light-blue] .md-typeset a:hover{color:#0091ea}[data-md-color-accent=light-blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-nav__link:focus,[data-md-color-accent=light-blue] .md-nav__link:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-blue] .md-typeset [id]:target .headerlink{color:#0091ea}[data-md-color-accent=light-blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-search-result__link:hover,[data-md-color-accent=light-blue] .md-search-result__link[data-md-state=active]{background-color:rgba(0,145,234,.1)}[data-md-color-accent=light-blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-source-file:hover:before{background-color:#0091ea}button[data-md-color-accent=cyan]{background-color:#00b8d4}[data-md-color-accent=cyan] .md-typeset a:active,[data-md-color-accent=cyan] .md-typeset a:hover{color:#00b8d4}[data-md-color-accent=cyan] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=cyan] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-nav__link:focus,[data-md-color-accent=cyan] .md-nav__link:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=cyan] .md-typeset .md-clipboard:active:before,[data-md-color-accent=cyan] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=cyan] .md-typeset [id] .headerlink:focus,[data-md-color-accent=cyan] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=cyan] .md-typeset [id]:target .headerlink{color:#00b8d4}[data-md-color-accent=cyan] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-search-result__link:hover,[data-md-color-accent=cyan] .md-search-result__link[data-md-state=active]{background-color:rgba(0,184,212,.1)}[data-md-color-accent=cyan] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-source-file:hover:before{background-color:#00b8d4}button[data-md-color-accent=teal]{background-color:#00bfa5}[data-md-color-accent=teal] .md-typeset a:active,[data-md-color-accent=teal] .md-typeset a:hover{color:#00bfa5}[data-md-color-accent=teal] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=teal] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-nav__link:focus,[data-md-color-accent=teal] .md-nav__link:hover,[data-md-color-accent=teal] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=teal] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=teal] .md-typeset .md-clipboard:active:before,[data-md-color-accent=teal] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=teal] .md-typeset [id] .headerlink:focus,[data-md-color-accent=teal] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=teal] .md-typeset [id]:target .headerlink{color:#00bfa5}[data-md-color-accent=teal] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-search-result__link:hover,[data-md-color-accent=teal] .md-search-result__link[data-md-state=active]{background-color:rgba(0,191,165,.1)}[data-md-color-accent=teal] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-source-file:hover:before{background-color:#00bfa5}button[data-md-color-accent=green]{background-color:#00c853}[data-md-color-accent=green] .md-typeset a:active,[data-md-color-accent=green] .md-typeset a:hover{color:#00c853}[data-md-color-accent=green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-nav__link:focus,[data-md-color-accent=green] .md-nav__link:hover,[data-md-color-accent=green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=green] .md-typeset [id]:target .headerlink{color:#00c853}[data-md-color-accent=green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-search-result__link:hover,[data-md-color-accent=green] .md-search-result__link[data-md-state=active]{background-color:rgba(0,200,83,.1)}[data-md-color-accent=green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-source-file:hover:before{background-color:#00c853}button[data-md-color-accent=light-green]{background-color:#64dd17}[data-md-color-accent=light-green] .md-typeset a:active,[data-md-color-accent=light-green] .md-typeset a:hover{color:#64dd17}[data-md-color-accent=light-green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-nav__link:focus,[data-md-color-accent=light-green] .md-nav__link:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-green] .md-typeset [id]:target .headerlink{color:#64dd17}[data-md-color-accent=light-green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-search-result__link:hover,[data-md-color-accent=light-green] .md-search-result__link[data-md-state=active]{background-color:rgba(100,221,23,.1)}[data-md-color-accent=light-green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-source-file:hover:before{background-color:#64dd17}button[data-md-color-accent=lime]{background-color:#aeea00}[data-md-color-accent=lime] .md-typeset a:active,[data-md-color-accent=lime] .md-typeset a:hover{color:#aeea00}[data-md-color-accent=lime] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=lime] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-nav__link:focus,[data-md-color-accent=lime] .md-nav__link:hover,[data-md-color-accent=lime] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=lime] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=lime] .md-typeset .md-clipboard:active:before,[data-md-color-accent=lime] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=lime] .md-typeset [id] .headerlink:focus,[data-md-color-accent=lime] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=lime] .md-typeset [id]:target .headerlink{color:#aeea00}[data-md-color-accent=lime] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-search-result__link:hover,[data-md-color-accent=lime] .md-search-result__link[data-md-state=active]{background-color:rgba(174,234,0,.1)}[data-md-color-accent=lime] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-source-file:hover:before{background-color:#aeea00}button[data-md-color-accent=yellow]{background-color:#ffd600}[data-md-color-accent=yellow] .md-typeset a:active,[data-md-color-accent=yellow] .md-typeset a:hover{color:#ffd600}[data-md-color-accent=yellow] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=yellow] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-nav__link:focus,[data-md-color-accent=yellow] .md-nav__link:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=yellow] .md-typeset .md-clipboard:active:before,[data-md-color-accent=yellow] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=yellow] .md-typeset [id] .headerlink:focus,[data-md-color-accent=yellow] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=yellow] .md-typeset [id]:target .headerlink{color:#ffd600}[data-md-color-accent=yellow] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-search-result__link:hover,[data-md-color-accent=yellow] .md-search-result__link[data-md-state=active]{background-color:rgba(255,214,0,.1)}[data-md-color-accent=yellow] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-source-file:hover:before{background-color:#ffd600}button[data-md-color-accent=amber]{background-color:#ffab00}[data-md-color-accent=amber] .md-typeset a:active,[data-md-color-accent=amber] .md-typeset a:hover{color:#ffab00}[data-md-color-accent=amber] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=amber] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-nav__link:focus,[data-md-color-accent=amber] .md-nav__link:hover,[data-md-color-accent=amber] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=amber] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=amber] .md-typeset .md-clipboard:active:before,[data-md-color-accent=amber] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=amber] .md-typeset [id] .headerlink:focus,[data-md-color-accent=amber] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=amber] .md-typeset [id]:target .headerlink{color:#ffab00}[data-md-color-accent=amber] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-search-result__link:hover,[data-md-color-accent=amber] .md-search-result__link[data-md-state=active]{background-color:rgba(255,171,0,.1)}[data-md-color-accent=amber] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-source-file:hover:before{background-color:#ffab00}button[data-md-color-accent=orange]{background-color:#ff9100}[data-md-color-accent=orange] .md-typeset a:active,[data-md-color-accent=orange] .md-typeset a:hover{color:#ff9100}[data-md-color-accent=orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-nav__link:focus,[data-md-color-accent=orange] .md-nav__link:hover,[data-md-color-accent=orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=orange] .md-typeset [id]:target .headerlink{color:#ff9100}[data-md-color-accent=orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-search-result__link:hover,[data-md-color-accent=orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,145,0,.1)}[data-md-color-accent=orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-source-file:hover:before{background-color:#ff9100}button[data-md-color-accent=deep-orange]{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset a:active,[data-md-color-accent=deep-orange] .md-typeset a:hover{color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-nav__link:focus,[data-md-color-accent=deep-orange] .md-nav__link:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-orange] .md-typeset [id]:target .headerlink{color:#ff6e40}[data-md-color-accent=deep-orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-search-result__link:hover,[data-md-color-accent=deep-orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,110,64,.1)}[data-md-color-accent=deep-orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-source-file:hover:before{background-color:#ff6e40}@media only screen and (max-width:59.9375em){[data-md-color-primary=red] .md-nav__source{background-color:rgba(190,66,64,.9675)}[data-md-color-primary=pink] .md-nav__source{background-color:rgba(185,24,79,.9675)}[data-md-color-primary=purple] .md-nav__source{background-color:rgba(136,57,150,.9675)}[data-md-color-primary=deep-purple] .md-nav__source{background-color:rgba(100,69,154,.9675)}[data-md-color-primary=indigo] .md-nav__source{background-color:rgba(50,64,144,.9675)}[data-md-color-primary=blue] .md-nav__source{background-color:rgba(26,119,193,.9675)}[data-md-color-primary=light-blue] .md-nav__source{background-color:rgba(2,134,194,.9675)}[data-md-color-primary=cyan] .md-nav__source{background-color:rgba(0,150,169,.9675)}[data-md-color-primary=teal] .md-nav__source{background-color:rgba(0,119,108,.9675)}[data-md-color-primary=green] .md-nav__source{background-color:rgba(60,139,64,.9675)}[data-md-color-primary=light-green] .md-nav__source{background-color:rgba(99,142,53,.9675)}[data-md-color-primary=lime] .md-nav__source{background-color:rgba(153,161,41,.9675)}[data-md-color-primary=yellow] .md-nav__source{background-color:rgba(198,134,29,.9675)}[data-md-color-primary=amber] .md-nav__source{background-color:rgba(203,127,0,.9675)}[data-md-color-primary=orange] .md-nav__source{background-color:rgba(200,111,0,.9675)}[data-md-color-primary=deep-orange] .md-nav__source{background-color:rgba(203,89,53,.9675)}[data-md-color-primary=brown] .md-nav__source{background-color:rgba(96,68,57,.9675)}[data-md-color-primary=grey] .md-nav__source{background-color:rgba(93,93,93,.9675)}[data-md-color-primary=blue-grey] .md-nav__source{background-color:rgba(67,88,97,.9675)}[data-md-color-primary=white] .md-nav__source{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.87)}}@media only screen and (max-width:76.1875em){html [data-md-color-primary=red] .md-nav--primary .md-nav__title--site{background-color:#ef5350}html [data-md-color-primary=pink] .md-nav--primary .md-nav__title--site{background-color:#e91e63}html [data-md-color-primary=purple] .md-nav--primary .md-nav__title--site{background-color:#ab47bc}html [data-md-color-primary=deep-purple] .md-nav--primary .md-nav__title--site{background-color:#7e57c2}html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site{background-color:#3f51b5}html [data-md-color-primary=blue] .md-nav--primary .md-nav__title--site{background-color:#2196f3}html [data-md-color-primary=light-blue] .md-nav--primary .md-nav__title--site{background-color:#03a9f4}html [data-md-color-primary=cyan] .md-nav--primary .md-nav__title--site{background-color:#00bcd4}html [data-md-color-primary=teal] .md-nav--primary .md-nav__title--site{background-color:#009688}html [data-md-color-primary=green] .md-nav--primary .md-nav__title--site{background-color:#4caf50}html [data-md-color-primary=light-green] .md-nav--primary .md-nav__title--site{background-color:#7cb342}html [data-md-color-primary=lime] .md-nav--primary .md-nav__title--site{background-color:#c0ca33}html [data-md-color-primary=yellow] .md-nav--primary .md-nav__title--site{background-color:#f9a825}html [data-md-color-primary=amber] .md-nav--primary .md-nav__title--site{background-color:#ffa000}html [data-md-color-primary=orange] .md-nav--primary .md-nav__title--site{background-color:#fb8c00}html [data-md-color-primary=deep-orange] .md-nav--primary .md-nav__title--site{background-color:#ff7043}html [data-md-color-primary=brown] .md-nav--primary .md-nav__title--site{background-color:#795548}html [data-md-color-primary=grey] .md-nav--primary .md-nav__title--site{background-color:#757575}html [data-md-color-primary=blue-grey] .md-nav--primary .md-nav__title--site{background-color:#546e7a}html [data-md-color-primary=white] .md-nav--primary .md-nav__title--site{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero{border-bottom:.05rem solid rgba(0,0,0,.07)}}@media only screen and (min-width:76.25em){[data-md-color-primary=red] .md-tabs{background-color:#ef5350}[data-md-color-primary=pink] .md-tabs{background-color:#e91e63}[data-md-color-primary=purple] .md-tabs{background-color:#ab47bc}[data-md-color-primary=deep-purple] .md-tabs{background-color:#7e57c2}[data-md-color-primary=indigo] .md-tabs{background-color:#3f51b5}[data-md-color-primary=blue] .md-tabs{background-color:#2196f3}[data-md-color-primary=light-blue] .md-tabs{background-color:#03a9f4}[data-md-color-primary=cyan] .md-tabs{background-color:#00bcd4}[data-md-color-primary=teal] .md-tabs{background-color:#009688}[data-md-color-primary=green] .md-tabs{background-color:#4caf50}[data-md-color-primary=light-green] .md-tabs{background-color:#7cb342}[data-md-color-primary=lime] .md-tabs{background-color:#c0ca33}[data-md-color-primary=yellow] .md-tabs{background-color:#f9a825}[data-md-color-primary=amber] .md-tabs{background-color:#ffa000}[data-md-color-primary=orange] .md-tabs{background-color:#fb8c00}[data-md-color-primary=deep-orange] .md-tabs{background-color:#ff7043}[data-md-color-primary=brown] .md-tabs{background-color:#795548}[data-md-color-primary=grey] .md-tabs{background-color:#757575}[data-md-color-primary=blue-grey] .md-tabs{background-color:#546e7a}[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07);background-color:#fff;color:rgba(0,0,0,.87)}}@media only screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}} \ No newline at end of file +button[data-md-color-accent],button[data-md-color-primary]{width:6.5rem;margin-bottom:.2rem;padding:1.2rem .4rem .2rem;-webkit-transition:background-color .25s,opacity .25s;transition:background-color .25s,opacity .25s;border-radius:.1rem;color:#fff;font-size:.64rem;text-align:left;cursor:pointer}button[data-md-color-accent]:hover,button[data-md-color-primary]:hover{opacity:.75}button[data-md-color-primary=red]{background-color:#ef5350}[data-md-color-primary=red] .md-typeset a{color:#ef5350}[data-md-color-primary=red] .md-header,[data-md-color-primary=red] .md-hero{background-color:#ef5350}[data-md-color-primary=red] .md-nav__link--active,[data-md-color-primary=red] .md-nav__link:active{color:#ef5350}[data-md-color-primary=red] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=pink]{background-color:#e91e63}[data-md-color-primary=pink] .md-typeset a{color:#e91e63}[data-md-color-primary=pink] .md-header,[data-md-color-primary=pink] .md-hero{background-color:#e91e63}[data-md-color-primary=pink] .md-nav__link--active,[data-md-color-primary=pink] .md-nav__link:active{color:#e91e63}[data-md-color-primary=pink] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=purple]{background-color:#ab47bc}[data-md-color-primary=purple] .md-typeset a{color:#ab47bc}[data-md-color-primary=purple] .md-header,[data-md-color-primary=purple] .md-hero{background-color:#ab47bc}[data-md-color-primary=purple] .md-nav__link--active,[data-md-color-primary=purple] .md-nav__link:active{color:#ab47bc}[data-md-color-primary=purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-purple]{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-typeset a{color:#7e57c2}[data-md-color-primary=deep-purple] .md-header,[data-md-color-primary=deep-purple] .md-hero{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__link--active,[data-md-color-primary=deep-purple] .md-nav__link:active{color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=indigo]{background-color:#3f51b5}[data-md-color-primary=indigo] .md-typeset a{color:#3f51b5}[data-md-color-primary=indigo] .md-header,[data-md-color-primary=indigo] .md-hero{background-color:#3f51b5}[data-md-color-primary=indigo] .md-nav__link--active,[data-md-color-primary=indigo] .md-nav__link:active{color:#3f51b5}[data-md-color-primary=indigo] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue]{background-color:#2196f3}[data-md-color-primary=blue] .md-typeset a{color:#2196f3}[data-md-color-primary=blue] .md-header,[data-md-color-primary=blue] .md-hero{background-color:#2196f3}[data-md-color-primary=blue] .md-nav__link--active,[data-md-color-primary=blue] .md-nav__link:active{color:#2196f3}[data-md-color-primary=blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-blue]{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-typeset a{color:#03a9f4}[data-md-color-primary=light-blue] .md-header,[data-md-color-primary=light-blue] .md-hero{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__link--active,[data-md-color-primary=light-blue] .md-nav__link:active{color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=cyan]{background-color:#00bcd4}[data-md-color-primary=cyan] .md-typeset a{color:#00bcd4}[data-md-color-primary=cyan] .md-header,[data-md-color-primary=cyan] .md-hero{background-color:#00bcd4}[data-md-color-primary=cyan] .md-nav__link--active,[data-md-color-primary=cyan] .md-nav__link:active{color:#00bcd4}[data-md-color-primary=cyan] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=teal]{background-color:#009688}[data-md-color-primary=teal] .md-typeset a{color:#009688}[data-md-color-primary=teal] .md-header,[data-md-color-primary=teal] .md-hero{background-color:#009688}[data-md-color-primary=teal] .md-nav__link--active,[data-md-color-primary=teal] .md-nav__link:active{color:#009688}[data-md-color-primary=teal] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=green]{background-color:#4caf50}[data-md-color-primary=green] .md-typeset a{color:#4caf50}[data-md-color-primary=green] .md-header,[data-md-color-primary=green] .md-hero{background-color:#4caf50}[data-md-color-primary=green] .md-nav__link--active,[data-md-color-primary=green] .md-nav__link:active{color:#4caf50}[data-md-color-primary=green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-green]{background-color:#7cb342}[data-md-color-primary=light-green] .md-typeset a{color:#7cb342}[data-md-color-primary=light-green] .md-header,[data-md-color-primary=light-green] .md-hero{background-color:#7cb342}[data-md-color-primary=light-green] .md-nav__link--active,[data-md-color-primary=light-green] .md-nav__link:active{color:#7cb342}[data-md-color-primary=light-green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=lime]{background-color:#c0ca33}[data-md-color-primary=lime] .md-typeset a{color:#c0ca33}[data-md-color-primary=lime] .md-header,[data-md-color-primary=lime] .md-hero{background-color:#c0ca33}[data-md-color-primary=lime] .md-nav__link--active,[data-md-color-primary=lime] .md-nav__link:active{color:#c0ca33}[data-md-color-primary=lime] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=yellow]{background-color:#f9a825}[data-md-color-primary=yellow] .md-typeset a{color:#f9a825}[data-md-color-primary=yellow] .md-header,[data-md-color-primary=yellow] .md-hero{background-color:#f9a825}[data-md-color-primary=yellow] .md-nav__link--active,[data-md-color-primary=yellow] .md-nav__link:active{color:#f9a825}[data-md-color-primary=yellow] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=amber]{background-color:#ffa000}[data-md-color-primary=amber] .md-typeset a{color:#ffa000}[data-md-color-primary=amber] .md-header,[data-md-color-primary=amber] .md-hero{background-color:#ffa000}[data-md-color-primary=amber] .md-nav__link--active,[data-md-color-primary=amber] .md-nav__link:active{color:#ffa000}[data-md-color-primary=amber] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=orange]{background-color:#fb8c00}[data-md-color-primary=orange] .md-typeset a{color:#fb8c00}[data-md-color-primary=orange] .md-header,[data-md-color-primary=orange] .md-hero{background-color:#fb8c00}[data-md-color-primary=orange] .md-nav__link--active,[data-md-color-primary=orange] .md-nav__link:active{color:#fb8c00}[data-md-color-primary=orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-orange]{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-typeset a{color:#ff7043}[data-md-color-primary=deep-orange] .md-header,[data-md-color-primary=deep-orange] .md-hero{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__link--active,[data-md-color-primary=deep-orange] .md-nav__link:active{color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=brown]{background-color:#795548}[data-md-color-primary=brown] .md-typeset a{color:#795548}[data-md-color-primary=brown] .md-header,[data-md-color-primary=brown] .md-hero{background-color:#795548}[data-md-color-primary=brown] .md-nav__link--active,[data-md-color-primary=brown] .md-nav__link:active{color:#795548}[data-md-color-primary=brown] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=grey]{background-color:#757575}[data-md-color-primary=grey] .md-typeset a{color:#757575}[data-md-color-primary=grey] .md-header,[data-md-color-primary=grey] .md-hero{background-color:#757575}[data-md-color-primary=grey] .md-nav__link--active,[data-md-color-primary=grey] .md-nav__link:active{color:#757575}[data-md-color-primary=grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue-grey]{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-typeset a{color:#546e7a}[data-md-color-primary=blue-grey] .md-header,[data-md-color-primary=blue-grey] .md-hero{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__link--active,[data-md-color-primary=blue-grey] .md-nav__link:active{color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=white]{box-shadow:inset 0 0 .05rem rgba(0,0,0,.54)}[data-md-color-primary=white] .md-header,[data-md-color-primary=white] .md-hero,button[data-md-color-primary=white]{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero--expand{border-bottom:.05rem solid rgba(0,0,0,.07)}[data-md-color-primary=black] .md-header,[data-md-color-primary=black] .md-hero,button[data-md-color-primary=black]{background-color:#000}button[data-md-color-accent=red]{background-color:#ff1744}[data-md-color-accent=red] .md-typeset a:active,[data-md-color-accent=red] .md-typeset a:hover{color:#ff1744}[data-md-color-accent=red] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=red] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-nav__link:focus,[data-md-color-accent=red] .md-nav__link:hover,[data-md-color-accent=red] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=red] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=red] .md-typeset .md-clipboard:active:before,[data-md-color-accent=red] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=red] .md-typeset [id] .headerlink:focus,[data-md-color-accent=red] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=red] .md-typeset [id]:target .headerlink{color:#ff1744}[data-md-color-accent=red] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-search-result__link:hover,[data-md-color-accent=red] .md-search-result__link[data-md-state=active]{background-color:rgba(255,23,68,.1)}[data-md-color-accent=red] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-source-file:hover:before{background-color:#ff1744}button[data-md-color-accent=pink]{background-color:#f50057}[data-md-color-accent=pink] .md-typeset a:active,[data-md-color-accent=pink] .md-typeset a:hover{color:#f50057}[data-md-color-accent=pink] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=pink] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-nav__link:focus,[data-md-color-accent=pink] .md-nav__link:hover,[data-md-color-accent=pink] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=pink] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=pink] .md-typeset .md-clipboard:active:before,[data-md-color-accent=pink] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=pink] .md-typeset [id] .headerlink:focus,[data-md-color-accent=pink] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=pink] .md-typeset [id]:target .headerlink{color:#f50057}[data-md-color-accent=pink] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-search-result__link:hover,[data-md-color-accent=pink] .md-search-result__link[data-md-state=active]{background-color:rgba(245,0,87,.1)}[data-md-color-accent=pink] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-source-file:hover:before{background-color:#f50057}button[data-md-color-accent=purple]{background-color:#e040fb}[data-md-color-accent=purple] .md-typeset a:active,[data-md-color-accent=purple] .md-typeset a:hover{color:#e040fb}[data-md-color-accent=purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-nav__link:focus,[data-md-color-accent=purple] .md-nav__link:hover,[data-md-color-accent=purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=purple] .md-typeset [id]:target .headerlink{color:#e040fb}[data-md-color-accent=purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-search-result__link:hover,[data-md-color-accent=purple] .md-search-result__link[data-md-state=active]{background-color:rgba(224,64,251,.1)}[data-md-color-accent=purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-source-file:hover:before{background-color:#e040fb}button[data-md-color-accent=deep-purple]{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset a:active,[data-md-color-accent=deep-purple] .md-typeset a:hover{color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-nav__link:focus,[data-md-color-accent=deep-purple] .md-nav__link:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-purple] .md-typeset [id]:target .headerlink{color:#7c4dff}[data-md-color-accent=deep-purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-search-result__link:hover,[data-md-color-accent=deep-purple] .md-search-result__link[data-md-state=active]{background-color:rgba(124,77,255,.1)}[data-md-color-accent=deep-purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-source-file:hover:before{background-color:#7c4dff}button[data-md-color-accent=indigo]{background-color:#536dfe}[data-md-color-accent=indigo] .md-typeset a:active,[data-md-color-accent=indigo] .md-typeset a:hover{color:#536dfe}[data-md-color-accent=indigo] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=indigo] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-nav__link:focus,[data-md-color-accent=indigo] .md-nav__link:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=indigo] .md-typeset .md-clipboard:active:before,[data-md-color-accent=indigo] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=indigo] .md-typeset [id] .headerlink:focus,[data-md-color-accent=indigo] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=indigo] .md-typeset [id]:target .headerlink{color:#536dfe}[data-md-color-accent=indigo] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-search-result__link:hover,[data-md-color-accent=indigo] .md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}[data-md-color-accent=indigo] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-source-file:hover:before{background-color:#536dfe}button[data-md-color-accent=blue]{background-color:#448aff}[data-md-color-accent=blue] .md-typeset a:active,[data-md-color-accent=blue] .md-typeset a:hover{color:#448aff}[data-md-color-accent=blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-nav__link:focus,[data-md-color-accent=blue] .md-nav__link:hover,[data-md-color-accent=blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=blue] .md-typeset [id]:target .headerlink{color:#448aff}[data-md-color-accent=blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-search-result__link:hover,[data-md-color-accent=blue] .md-search-result__link[data-md-state=active]{background-color:rgba(68,138,255,.1)}[data-md-color-accent=blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-source-file:hover:before{background-color:#448aff}button[data-md-color-accent=light-blue]{background-color:#0091ea}[data-md-color-accent=light-blue] .md-typeset a:active,[data-md-color-accent=light-blue] .md-typeset a:hover{color:#0091ea}[data-md-color-accent=light-blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-nav__link:focus,[data-md-color-accent=light-blue] .md-nav__link:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-blue] .md-typeset [id]:target .headerlink{color:#0091ea}[data-md-color-accent=light-blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-search-result__link:hover,[data-md-color-accent=light-blue] .md-search-result__link[data-md-state=active]{background-color:rgba(0,145,234,.1)}[data-md-color-accent=light-blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-source-file:hover:before{background-color:#0091ea}button[data-md-color-accent=cyan]{background-color:#00b8d4}[data-md-color-accent=cyan] .md-typeset a:active,[data-md-color-accent=cyan] .md-typeset a:hover{color:#00b8d4}[data-md-color-accent=cyan] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=cyan] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-nav__link:focus,[data-md-color-accent=cyan] .md-nav__link:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=cyan] .md-typeset .md-clipboard:active:before,[data-md-color-accent=cyan] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=cyan] .md-typeset [id] .headerlink:focus,[data-md-color-accent=cyan] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=cyan] .md-typeset [id]:target .headerlink{color:#00b8d4}[data-md-color-accent=cyan] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-search-result__link:hover,[data-md-color-accent=cyan] .md-search-result__link[data-md-state=active]{background-color:rgba(0,184,212,.1)}[data-md-color-accent=cyan] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-source-file:hover:before{background-color:#00b8d4}button[data-md-color-accent=teal]{background-color:#00bfa5}[data-md-color-accent=teal] .md-typeset a:active,[data-md-color-accent=teal] .md-typeset a:hover{color:#00bfa5}[data-md-color-accent=teal] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=teal] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-nav__link:focus,[data-md-color-accent=teal] .md-nav__link:hover,[data-md-color-accent=teal] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=teal] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=teal] .md-typeset .md-clipboard:active:before,[data-md-color-accent=teal] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=teal] .md-typeset [id] .headerlink:focus,[data-md-color-accent=teal] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=teal] .md-typeset [id]:target .headerlink{color:#00bfa5}[data-md-color-accent=teal] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-search-result__link:hover,[data-md-color-accent=teal] .md-search-result__link[data-md-state=active]{background-color:rgba(0,191,165,.1)}[data-md-color-accent=teal] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-source-file:hover:before{background-color:#00bfa5}button[data-md-color-accent=green]{background-color:#00c853}[data-md-color-accent=green] .md-typeset a:active,[data-md-color-accent=green] .md-typeset a:hover{color:#00c853}[data-md-color-accent=green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-nav__link:focus,[data-md-color-accent=green] .md-nav__link:hover,[data-md-color-accent=green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=green] .md-typeset [id]:target .headerlink{color:#00c853}[data-md-color-accent=green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-search-result__link:hover,[data-md-color-accent=green] .md-search-result__link[data-md-state=active]{background-color:rgba(0,200,83,.1)}[data-md-color-accent=green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-source-file:hover:before{background-color:#00c853}button[data-md-color-accent=light-green]{background-color:#64dd17}[data-md-color-accent=light-green] .md-typeset a:active,[data-md-color-accent=light-green] .md-typeset a:hover{color:#64dd17}[data-md-color-accent=light-green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-nav__link:focus,[data-md-color-accent=light-green] .md-nav__link:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-green] .md-typeset [id]:target .headerlink{color:#64dd17}[data-md-color-accent=light-green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-search-result__link:hover,[data-md-color-accent=light-green] .md-search-result__link[data-md-state=active]{background-color:rgba(100,221,23,.1)}[data-md-color-accent=light-green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-source-file:hover:before{background-color:#64dd17}button[data-md-color-accent=lime]{background-color:#aeea00}[data-md-color-accent=lime] .md-typeset a:active,[data-md-color-accent=lime] .md-typeset a:hover{color:#aeea00}[data-md-color-accent=lime] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=lime] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-nav__link:focus,[data-md-color-accent=lime] .md-nav__link:hover,[data-md-color-accent=lime] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=lime] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=lime] .md-typeset .md-clipboard:active:before,[data-md-color-accent=lime] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=lime] .md-typeset [id] .headerlink:focus,[data-md-color-accent=lime] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=lime] .md-typeset [id]:target .headerlink{color:#aeea00}[data-md-color-accent=lime] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-search-result__link:hover,[data-md-color-accent=lime] .md-search-result__link[data-md-state=active]{background-color:rgba(174,234,0,.1)}[data-md-color-accent=lime] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-source-file:hover:before{background-color:#aeea00}button[data-md-color-accent=yellow]{background-color:#ffd600}[data-md-color-accent=yellow] .md-typeset a:active,[data-md-color-accent=yellow] .md-typeset a:hover{color:#ffd600}[data-md-color-accent=yellow] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=yellow] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-nav__link:focus,[data-md-color-accent=yellow] .md-nav__link:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=yellow] .md-typeset .md-clipboard:active:before,[data-md-color-accent=yellow] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=yellow] .md-typeset [id] .headerlink:focus,[data-md-color-accent=yellow] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=yellow] .md-typeset [id]:target .headerlink{color:#ffd600}[data-md-color-accent=yellow] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-search-result__link:hover,[data-md-color-accent=yellow] .md-search-result__link[data-md-state=active]{background-color:rgba(255,214,0,.1)}[data-md-color-accent=yellow] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-source-file:hover:before{background-color:#ffd600}button[data-md-color-accent=amber]{background-color:#ffab00}[data-md-color-accent=amber] .md-typeset a:active,[data-md-color-accent=amber] .md-typeset a:hover{color:#ffab00}[data-md-color-accent=amber] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=amber] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-nav__link:focus,[data-md-color-accent=amber] .md-nav__link:hover,[data-md-color-accent=amber] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=amber] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=amber] .md-typeset .md-clipboard:active:before,[data-md-color-accent=amber] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=amber] .md-typeset [id] .headerlink:focus,[data-md-color-accent=amber] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=amber] .md-typeset [id]:target .headerlink{color:#ffab00}[data-md-color-accent=amber] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-search-result__link:hover,[data-md-color-accent=amber] .md-search-result__link[data-md-state=active]{background-color:rgba(255,171,0,.1)}[data-md-color-accent=amber] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-source-file:hover:before{background-color:#ffab00}button[data-md-color-accent=orange]{background-color:#ff9100}[data-md-color-accent=orange] .md-typeset a:active,[data-md-color-accent=orange] .md-typeset a:hover{color:#ff9100}[data-md-color-accent=orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-nav__link:focus,[data-md-color-accent=orange] .md-nav__link:hover,[data-md-color-accent=orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=orange] .md-typeset [id]:target .headerlink{color:#ff9100}[data-md-color-accent=orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-search-result__link:hover,[data-md-color-accent=orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,145,0,.1)}[data-md-color-accent=orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-source-file:hover:before{background-color:#ff9100}button[data-md-color-accent=deep-orange]{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset a:active,[data-md-color-accent=deep-orange] .md-typeset a:hover{color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-nav__link:focus,[data-md-color-accent=deep-orange] .md-nav__link:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-orange] .md-typeset [id]:target .headerlink{color:#ff6e40}[data-md-color-accent=deep-orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-search-result__link:hover,[data-md-color-accent=deep-orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,110,64,.1)}[data-md-color-accent=deep-orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-source-file:hover:before{background-color:#ff6e40}@media only screen and (max-width:59.9375em){[data-md-color-primary=red] .md-nav__source{background-color:rgba(190,66,64,.9675)}[data-md-color-primary=pink] .md-nav__source{background-color:rgba(185,24,79,.9675)}[data-md-color-primary=purple] .md-nav__source{background-color:rgba(136,57,150,.9675)}[data-md-color-primary=deep-purple] .md-nav__source{background-color:rgba(100,69,154,.9675)}[data-md-color-primary=indigo] .md-nav__source{background-color:rgba(50,64,144,.9675)}[data-md-color-primary=blue] .md-nav__source{background-color:rgba(26,119,193,.9675)}[data-md-color-primary=light-blue] .md-nav__source{background-color:rgba(2,134,194,.9675)}[data-md-color-primary=cyan] .md-nav__source{background-color:rgba(0,150,169,.9675)}[data-md-color-primary=teal] .md-nav__source{background-color:rgba(0,119,108,.9675)}[data-md-color-primary=green] .md-nav__source{background-color:rgba(60,139,64,.9675)}[data-md-color-primary=light-green] .md-nav__source{background-color:rgba(99,142,53,.9675)}[data-md-color-primary=lime] .md-nav__source{background-color:rgba(153,161,41,.9675)}[data-md-color-primary=yellow] .md-nav__source{background-color:rgba(198,134,29,.9675)}[data-md-color-primary=amber] .md-nav__source{background-color:rgba(203,127,0,.9675)}[data-md-color-primary=orange] .md-nav__source{background-color:rgba(200,111,0,.9675)}[data-md-color-primary=deep-orange] .md-nav__source{background-color:rgba(203,89,53,.9675)}[data-md-color-primary=brown] .md-nav__source{background-color:rgba(96,68,57,.9675)}[data-md-color-primary=grey] .md-nav__source{background-color:rgba(93,93,93,.9675)}[data-md-color-primary=blue-grey] .md-nav__source{background-color:rgba(67,88,97,.9675)}[data-md-color-primary=white] .md-nav__source{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.87)}[data-md-color-primary=black] .md-nav__source{background-color:#404040}}@media only screen and (max-width:76.1875em){html [data-md-color-primary=red] .md-nav--primary .md-nav__title--site{background-color:#ef5350}html [data-md-color-primary=pink] .md-nav--primary .md-nav__title--site{background-color:#e91e63}html [data-md-color-primary=purple] .md-nav--primary .md-nav__title--site{background-color:#ab47bc}html [data-md-color-primary=deep-purple] .md-nav--primary .md-nav__title--site{background-color:#7e57c2}html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site{background-color:#3f51b5}html [data-md-color-primary=blue] .md-nav--primary .md-nav__title--site{background-color:#2196f3}html [data-md-color-primary=light-blue] .md-nav--primary .md-nav__title--site{background-color:#03a9f4}html [data-md-color-primary=cyan] .md-nav--primary .md-nav__title--site{background-color:#00bcd4}html [data-md-color-primary=teal] .md-nav--primary .md-nav__title--site{background-color:#009688}html [data-md-color-primary=green] .md-nav--primary .md-nav__title--site{background-color:#4caf50}html [data-md-color-primary=light-green] .md-nav--primary .md-nav__title--site{background-color:#7cb342}html [data-md-color-primary=lime] .md-nav--primary .md-nav__title--site{background-color:#c0ca33}html [data-md-color-primary=yellow] .md-nav--primary .md-nav__title--site{background-color:#f9a825}html [data-md-color-primary=amber] .md-nav--primary .md-nav__title--site{background-color:#ffa000}html [data-md-color-primary=orange] .md-nav--primary .md-nav__title--site{background-color:#fb8c00}html [data-md-color-primary=deep-orange] .md-nav--primary .md-nav__title--site{background-color:#ff7043}html [data-md-color-primary=brown] .md-nav--primary .md-nav__title--site{background-color:#795548}html [data-md-color-primary=grey] .md-nav--primary .md-nav__title--site{background-color:#757575}html [data-md-color-primary=blue-grey] .md-nav--primary .md-nav__title--site{background-color:#546e7a}html [data-md-color-primary=white] .md-nav--primary .md-nav__title--site{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero{border-bottom:.05rem solid rgba(0,0,0,.07)}html [data-md-color-primary=black] .md-nav--primary .md-nav__title--site{background-color:#000}}@media only screen and (min-width:76.25em){[data-md-color-primary=red] .md-tabs{background-color:#ef5350}[data-md-color-primary=pink] .md-tabs{background-color:#e91e63}[data-md-color-primary=purple] .md-tabs{background-color:#ab47bc}[data-md-color-primary=deep-purple] .md-tabs{background-color:#7e57c2}[data-md-color-primary=indigo] .md-tabs{background-color:#3f51b5}[data-md-color-primary=blue] .md-tabs{background-color:#2196f3}[data-md-color-primary=light-blue] .md-tabs{background-color:#03a9f4}[data-md-color-primary=cyan] .md-tabs{background-color:#00bcd4}[data-md-color-primary=teal] .md-tabs{background-color:#009688}[data-md-color-primary=green] .md-tabs{background-color:#4caf50}[data-md-color-primary=light-green] .md-tabs{background-color:#7cb342}[data-md-color-primary=lime] .md-tabs{background-color:#c0ca33}[data-md-color-primary=yellow] .md-tabs{background-color:#f9a825}[data-md-color-primary=amber] .md-tabs{background-color:#ffa000}[data-md-color-primary=orange] .md-tabs{background-color:#fb8c00}[data-md-color-primary=deep-orange] .md-tabs{background-color:#ff7043}[data-md-color-primary=brown] .md-tabs{background-color:#795548}[data-md-color-primary=grey] .md-tabs{background-color:#757575}[data-md-color-primary=blue-grey] .md-tabs{background-color:#546e7a}[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07);background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=black] .md-tabs{background-color:#000}}@media only screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.3)}} \ No newline at end of file diff --git a/docs/assets/stylesheets/application.1b62728e.css b/docs/assets/stylesheets/application.1b62728e.css new file mode 100644 index 00000000..3840dd13 --- /dev/null +++ b/docs/assets/stylesheets/application.1b62728e.css @@ -0,0 +1 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}body{margin:0}hr{overflow:visible;box-sizing:content-box}a{-webkit-text-decoration-skip:objects}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}small,sub,sup{font-size:80%}sub,sup{position:relative;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}table{border-collapse:separate;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;border:0;outline-style:none;background:transparent;font-size:inherit}input{border:0;outline:0}.md-clipboard:before,.md-icon,.md-nav__button,.md-nav__link:after,.md-nav__title:before,.md-search-result__article--document:before,.md-source-file:before,.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset .critic.comment:before,.md-typeset .footnote-backref,.md-typeset .task-list-control .task-list-indicator:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before,.md-typeset summary:after{font-family:Material Icons;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-content__icon,.md-footer-nav__button,.md-header-nav__button,.md-nav__button,.md-nav__title:before,.md-search-result__article--document:before{display:inline-block;margin:.2rem;padding:.4rem;font-size:1.2rem;cursor:pointer}.md-icon--arrow-back:before{content:""}.md-icon--arrow-forward:before{content:""}.md-icon--menu:before{content:""}.md-icon--search:before{content:""}[dir=rtl] .md-icon--arrow-back:before{content:""}[dir=rtl] .md-icon--arrow-forward:before{content:""}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{color:rgba(0,0,0,.87);font-feature-settings:"kern","liga";font-family:Helvetica Neue,Helvetica,Arial,sans-serif}code,kbd,pre{color:rgba(0,0,0,.87);font-feature-settings:"kern";font-family:Courier New,Courier,monospace}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact}.md-typeset blockquote,.md-typeset ol,.md-typeset p,.md-typeset ul{margin:1em 0}.md-typeset h1{margin:0 0 2rem;color:rgba(0,0,0,.54);font-size:1.5625rem;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:2rem 0 .8rem;font-size:1.25rem;line-height:1.4}.md-typeset h3{margin:1.6rem 0 .8rem;font-size:1rem;font-weight:400;letter-spacing:-.01em;line-height:1.5}.md-typeset h2+h3{margin-top:.8rem}.md-typeset h4{font-size:.8rem}.md-typeset h4,.md-typeset h5,.md-typeset h6{margin:.8rem 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset h5{text-transform:uppercase}.md-typeset hr{margin:1.5em 0;border-bottom:.05rem dotted rgba(0,0,0,.26)}.md-typeset a{color:#3f51b5;word-break:break-word}.md-typeset a,.md-typeset a:before{-webkit-transition:color .125s;transition:color .125s}.md-typeset a:active,.md-typeset a:hover{color:#536dfe}.md-typeset code,.md-typeset pre{background-color:hsla(0,0%,92.5%,.5);color:#37474f;font-size:85%;direction:ltr}.md-typeset code{margin:0 .29412em;padding:.07353em 0;border-radius:.1rem;box-shadow:.29412em 0 0 hsla(0,0%,92.5%,.5),-.29412em 0 0 hsla(0,0%,92.5%,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:0;background-color:transparent;box-shadow:none}.md-typeset a>code{margin:inherit;padding:inherit;border-radius:initial;background-color:inherit;color:inherit;box-shadow:none}.md-typeset pre{position:relative;margin:1em 0;border-radius:.1rem;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset pre>code{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;font-size:inherit;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;overflow:auto}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset kbd{padding:0 .29412em;border-radius:.15rem;border:.05rem solid #c9c9c9;border-bottom-color:#bcbcbc;background-color:#fcfcfc;color:#555;font-size:85%;box-shadow:0 .05rem 0 #b0b0b0;word-break:break-word}.md-typeset mark{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;background-color:rgba(255,235,59,.5);box-shadow:.25em 0 0 rgba(255,235,59,.5),-.25em 0 0 rgba(255,235,59,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{border-bottom:.05rem dotted rgba(0,0,0,.54);text-decoration:none;cursor:help}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.07812em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.07812em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;border-left:.2rem solid rgba(0,0,0,.26);color:rgba(0,0,0,.54)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid rgba(0,0,0,.26);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset iframe,.md-typeset img,.md-typeset svg{max-width:100%}.md-typeset table:not([class]){box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);display:inline-block;max-width:100%;border-radius:.1rem;font-size:.64rem;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.6rem .8rem;background-color:rgba(0,0,0,.54);color:#fff;vertical-align:top}.md-typeset table:not([class]) td{padding:.6rem .8rem;border-top:.05rem solid rgba(0,0,0,.07);vertical-align:top}.md-typeset table:not([class]) tr{-webkit-transition:background-color .125s;transition:background-color .125s}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:inset 0 .05rem 0 #fff}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;-webkit-overflow-scrolling:touch}.md-typeset .md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}.md-typeset .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{font-size:125%;overflow-x:hidden}body,html{height:100%}body{position:relative;font-size:.5rem}hr{display:block;height:.05rem;padding:0;border:0}.md-svg{display:none}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container,.md-main{overflow:auto}.md-container{display:table;width:100%;height:100%;padding-top:2.4rem;table-layout:fixed}.md-main{display:table-row;height:100%}.md-main__inner{height:100%;padding-top:1.5rem;padding-bottom:.05rem}.md-toggle{display:none}.md-overlay{position:fixed;top:0;width:0;height:0;-webkit-transition:width 0s .25s,height 0s .25s,opacity .25s;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);opacity:0;z-index:3}.md-flex{display:table}.md-flex__cell{display:table-cell;position:relative;vertical-align:top}.md-flex__cell--shrink{width:0}.md-flex__cell--stretch{display:table;width:100%;table-layout:fixed}.md-flex__ellipsis{display:table-cell;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-skip{position:fixed;width:.05rem;height:.05rem;margin:.5rem;padding:.3rem .5rem;-webkit-transform:translateY(.4rem);transform:translateY(.4rem);border-radius:.1rem;background-color:rgba(0,0,0,.87);color:#fff;font-size:.64rem;opacity:0;overflow:hidden}.md-skip:focus{width:auto;height:auto;clip:auto;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;z-index:10}@page{margin:25mm}.md-clipboard{position:absolute;top:.3rem;right:.3rem;width:1.4rem;height:1.4rem;border-radius:.1rem;font-size:.8rem;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-clipboard:before{-webkit-transition:color .25s,opacity .25s;transition:color .25s,opacity .25s;color:rgba(0,0,0,.07);content:"\E14D"}.codehilite:hover .md-clipboard:before,.md-typeset .highlight:hover .md-clipboard:before,pre:hover .md-clipboard:before{color:rgba(0,0,0,.54)}.md-clipboard:focus:before,.md-clipboard:hover:before{color:#536dfe}.md-clipboard__message{display:block;position:absolute;top:0;right:1.7rem;padding:.3rem .5rem;-webkit-transform:translateX(.4rem);transform:translateX(.4rem);-webkit-transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s;transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);border-radius:.1rem;background-color:rgba(0,0,0,.54);color:#fff;font-size:.64rem;white-space:nowrap;opacity:0;pointer-events:none}.md-clipboard__message--active{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;pointer-events:auto}.md-clipboard__message:before{content:attr(aria-label)}.md-clipboard__message:after{display:block;position:absolute;top:50%;right:-.2rem;width:0;margin-top:-.2rem;border-color:transparent rgba(0,0,0,.54);border-style:solid;border-width:.2rem 0 .2rem .2rem;content:""}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__icon{position:relative;margin:.4rem 0;padding:0;float:right}.md-typeset .md-content__icon{color:rgba(0,0,0,.26)}.md-header{position:fixed;top:0;right:0;left:0;height:2.4rem;-webkit-transition:background-color .25s,color .25s;transition:background-color .25s,color .25s;background-color:#3f51b5;color:#fff;box-shadow:none;z-index:2;-webkit-backface-visibility:hidden;backface-visibility:hidden}.no-js .md-header{-webkit-transition:none;transition:none;box-shadow:none}.md-header[data-md-state=shadow]{-webkit-transition:background-color .25s,color .25s,box-shadow .25s;transition:background-color .25s,color .25s,box-shadow .25s;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.md-header-nav{padding:0 .2rem}.md-header-nav__button{position:relative;-webkit-transition:opacity .25s;transition:opacity .25s;z-index:1}.md-header-nav__button:hover{opacity:.7}.md-header-nav__button.md-logo *{display:block}.no-js .md-header-nav__button.md-icon--search{display:none}.md-header-nav__topic{display:block;position:absolute;-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem)}.no-js .md-header-nav__topic{position:static}.no-js .md-header-nav__topic+.md-header-nav__topic{display:none}.md-header-nav__title{padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.md-header-nav__title[data-md-state=active] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);opacity:1;z-index:0;pointer-events:auto}.md-header-nav__source{display:none}.md-hero{-webkit-transition:background .25s;transition:background .25s;background-color:#3f51b5;color:#fff;font-size:1rem;overflow:hidden}.md-hero__inner{margin-top:1rem;padding:.8rem .8rem .4rem;-webkit-transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);-webkit-transition-delay:.1s;transition-delay:.1s}[data-md-state=hidden] .md-hero__inner{pointer-events:none;-webkit-transform:translateY(.625rem);transform:translateY(.625rem);-webkit-transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:transform 0s .4s,opacity .1s 0s;transition:transform 0s .4s,opacity .1s 0s,-webkit-transform 0s .4s;opacity:0}.md-hero--expand .md-hero__inner{margin-bottom:1.2rem}.md-footer-nav{background-color:rgba(0,0,0,.87);color:#fff}.md-footer-nav__inner{padding:.2rem;overflow:auto}.md-footer-nav__link{padding-top:1.4rem;padding-bottom:.4rem;-webkit-transition:opacity .25s;transition:opacity .25s}.md-footer-nav__link:hover{opacity:.7}.md-footer-nav__link--prev{width:25%;float:left}[dir=rtl] .md-footer-nav__link--prev{float:right}.md-footer-nav__link--next{width:75%;float:right;text-align:right}[dir=rtl] .md-footer-nav__link--next{float:left;text-align:left}.md-footer-nav__button{-webkit-transition:background .25s;transition:background .25s}.md-footer-nav__title{position:relative;padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer-nav__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;color:hsla(0,0%,100%,.7);font-size:.75rem}.md-footer-meta{background-color:rgba(0,0,0,.895)}.md-footer-meta__inner{padding:.2rem;overflow:auto}html .md-footer-meta.md-typeset a{color:hsla(0,0%,100%,.7)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:#fff}.md-footer-copyright{margin:0 .6rem;padding:.4rem 0;color:hsla(0,0%,100%,.3);font-size:.64rem}.md-footer-copyright__highlight{color:hsla(0,0%,100%,.7)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;font-size:.8rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden}.md-nav__title:before{display:none;content:"\E5C4"}[dir=rtl] .md-nav__title:before{content:"\E5C8"}.md-nav__title .md-nav__button{display:none}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item:last-child{padding-bottom:.6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__item .md-nav__item:last-child{padding-bottom:0}.md-nav__button img{width:100%;height:auto}.md-nav__link{display:block;margin-top:.625em;-webkit-transition:color .125s;transition:color .125s;text-overflow:ellipsis;cursor:pointer;overflow:hidden}.md-nav__item--nested>.md-nav__link:after{content:"\E313"}html .md-nav__link[for=__toc],html .md-nav__link[for=__toc]+.md-nav__link:after,html .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link[data-md-state=blur]{color:rgba(0,0,0,.54)}.md-nav__link--active,.md-nav__link:active{color:#3f51b5}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:#536dfe}.md-nav__source,.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}.md-search__form{position:relative}.md-search__input{position:relative;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;z-index:2}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-moz-placeholder{-moz-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input:-ms-input-placeholder{-ms-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}.md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}.md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}.md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-clear{display:none}.md-search__icon{position:absolute;-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;font-size:1.2rem;cursor:pointer;z-index:2}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}.md-search__icon[for=__search]:before{content:"\E8B6"}.md-search__icon[type=reset]{top:.3rem;right:.5rem;-webkit-transform:scale(.125);transform:scale(.125);-webkit-transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);opacity:0}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{-webkit-transform:scale(1);transform:scale(1);opacity:1}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;width:100%;border-radius:0 0 .1rem .1rem;overflow:hidden;z-index:1}.md-search__scrollwrap{height:100%;background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07);overflow-y:auto;-webkit-overflow-scrolling:touch}.md-search-result{color:rgba(0,0,0,.87);word-break:break-word}.md-search-result__meta{padding:0 .8rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.8rem}.md-search-result__list{margin:0;padding:0;border-top:.05rem solid rgba(0,0,0,.07);list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 rgba(0,0,0,.07)}.md-search-result__link{display:block;-webkit-transition:background .25s;transition:background .25s;outline:0;overflow:hidden}.md-search-result__link:hover,.md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}.md-search-result__link:hover .md-search-result__article:before,.md-search-result__link[data-md-state=active] .md-search-result__article:before{opacity:.7}.md-search-result__link:last-child .md-search-result__teaser{margin-bottom:.6rem}.md-search-result__article{position:relative;padding:0 .8rem;overflow:auto}.md-search-result__article--document:before{position:absolute;left:0;margin:.1rem;-webkit-transition:opacity .25s;transition:opacity .25s;color:rgba(0,0,0,.54);content:"\E880"}[dir=rtl] .md-search-result__article--document:before{right:0;left:auto}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-size:.8rem;font-weight:400;line-height:1.4}.md-search-result__title{margin:.5em 0;font-size:.64rem;font-weight:700;line-height:1.4}.md-search-result__teaser{display:-webkit-box;max-height:1.65rem;margin:.5em 0;color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.4;text-overflow:ellipsis;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.md-search-result em{font-style:normal;font-weight:700;text-decoration:underline}.md-sidebar{position:absolute;width:12.1rem;padding:1.2rem 0;overflow:hidden}.md-sidebar[data-md-state=lock]{position:fixed;top:2.4rem}.md-sidebar--secondary{display:none}.md-sidebar__scrollwrap{max-height:100%;margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.md-source{display:block;padding-right:.6rem;-webkit-transition:opacity .25s;transition:opacity .25s;font-size:.65rem;line-height:1.2;white-space:nowrap}[dir=rtl] .md-source{padding-right:0;padding-left:.6rem}.md-source:hover{opacity:.7}.md-source:after,.md-source__icon{display:inline-block;height:2.4rem;content:"";vertical-align:middle}.md-source__icon{width:2.4rem}.md-source__icon svg{width:1.2rem;height:1.2rem;margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:100%;margin-left:.6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden;vertical-align:middle}.md-source__facts{margin:0;padding:0;font-size:.55rem;font-weight:700;list-style-type:none;opacity:.75;overflow:hidden}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"\00B7"}.md-source__fact:first-child:before{display:none}.md-source-file{display:inline-block;margin:1em .5em 1em 0;padding-right:.25rem;border-radius:.1rem;background-color:rgba(0,0,0,.07);font-size:.64rem;list-style-type:none;cursor:pointer;overflow:hidden}.md-source-file:before{display:inline-block;margin-right:.25rem;padding:.25rem;background-color:rgba(0,0,0,.26);color:#fff;font-size:.8rem;content:"\E86F";vertical-align:middle}html .md-source-file{-webkit-transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1);transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1)}html .md-source-file:before{-webkit-transition:inherit;transition:inherit}html body .md-typeset .md-source-file{color:rgba(0,0,0,.54)}.md-source-file:hover{box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36)}.md-source-file:hover:before{background-color:#536dfe}.md-tabs{width:100%;-webkit-transition:background .25s;transition:background .25s;background-color:#3f51b5;color:#fff;overflow:auto}.md-tabs__list{margin:0 0 0 .2rem;padding:0;list-style:none;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;-webkit-transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);font-size:.7rem;opacity:.7}.md-tabs__link--active,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{-webkit-transition-delay:.02s;transition-delay:.02s}.md-tabs__item:nth-child(3) .md-tabs__link{-webkit-transition-delay:.04s;transition-delay:.04s}.md-tabs__item:nth-child(4) .md-tabs__link{-webkit-transition-delay:.06s;transition-delay:.06s}.md-tabs__item:nth-child(5) .md-tabs__link{-webkit-transition-delay:.08s;transition-delay:.08s}.md-tabs__item:nth-child(6) .md-tabs__link{-webkit-transition-delay:.1s;transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{-webkit-transition-delay:.12s;transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{-webkit-transition-delay:.14s;transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{-webkit-transition-delay:.16s;transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{-webkit-transition-delay:.18s;transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{-webkit-transition-delay:.2s;transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{-webkit-transition-delay:.22s;transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{-webkit-transition-delay:.24s;transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{-webkit-transition-delay:.26s;transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{-webkit-transition-delay:.28s;transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{-webkit-transition-delay:.3s;transition-delay:.3s}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs[data-md-state=hidden] .md-tabs__link{-webkit-transform:translateY(50%);transform:translateY(50%);-webkit-transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,transform 0s .4s,opacity .1s;transition:color .25s,transform 0s .4s,opacity .1s,-webkit-transform 0s .4s;opacity:0}.md-typeset .admonition,.md-typeset details{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin:1.5625em 0;padding:0 .6rem;border-left:.2rem solid #448aff;border-radius:.1rem;font-size:.64rem;overflow:auto}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition>.admonition-title,.md-typeset .admonition>summary,.md-typeset details>.admonition-title,.md-typeset details>summary{margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;border-bottom:.05rem solid rgba(68,138,255,.1);background-color:rgba(68,138,255,.1);font-weight:700}[dir=rtl] .md-typeset .admonition>.admonition-title,[dir=rtl] .md-typeset .admonition>summary,[dir=rtl] .md-typeset details>.admonition-title,[dir=rtl] .md-typeset details>summary{padding:.4rem 2rem .4rem .6rem}.md-typeset .admonition>.admonition-title:last-child,.md-typeset .admonition>summary:last-child,.md-typeset details>.admonition-title:last-child,.md-typeset details>summary:last-child{margin-bottom:0}.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before{position:absolute;left:.6rem;color:#448aff;font-size:1rem;content:"\E3C9"}[dir=rtl] .md-typeset .admonition>.admonition-title:before,[dir=rtl] .md-typeset .admonition>summary:before,[dir=rtl] .md-typeset details>.admonition-title:before,[dir=rtl] .md-typeset details>summary:before{right:.6rem;left:auto}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-left-color:#00b0ff}[dir=rtl] .md-typeset .admonition.abstract,[dir=rtl] .md-typeset .admonition.summary,[dir=rtl] .md-typeset .admonition.tldr,[dir=rtl] .md-typeset details.abstract,[dir=rtl] .md-typeset details.summary,[dir=rtl] .md-typeset details.tldr{border-right-color:#00b0ff}.md-typeset .admonition.abstract>.admonition-title,.md-typeset .admonition.abstract>summary,.md-typeset .admonition.summary>.admonition-title,.md-typeset .admonition.summary>summary,.md-typeset .admonition.tldr>.admonition-title,.md-typeset .admonition.tldr>summary,.md-typeset details.abstract>.admonition-title,.md-typeset details.abstract>summary,.md-typeset details.summary>.admonition-title,.md-typeset details.summary>summary,.md-typeset details.tldr>.admonition-title,.md-typeset details.tldr>summary{border-bottom-color:rgba(0,176,255,.1);background-color:rgba(0,176,255,.1)}.md-typeset .admonition.abstract>.admonition-title:before,.md-typeset .admonition.abstract>summary:before,.md-typeset .admonition.summary>.admonition-title:before,.md-typeset .admonition.summary>summary:before,.md-typeset .admonition.tldr>.admonition-title:before,.md-typeset .admonition.tldr>summary:before,.md-typeset details.abstract>.admonition-title:before,.md-typeset details.abstract>summary:before,.md-typeset details.summary>.admonition-title:before,.md-typeset details.summary>summary:before,.md-typeset details.tldr>.admonition-title:before,.md-typeset details.tldr>summary:before{color:#00b0ff;content:""}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-left-color:#00b8d4}[dir=rtl] .md-typeset .admonition.info,[dir=rtl] .md-typeset .admonition.todo,[dir=rtl] .md-typeset details.info,[dir=rtl] .md-typeset details.todo{border-right-color:#00b8d4}.md-typeset .admonition.info>.admonition-title,.md-typeset .admonition.info>summary,.md-typeset .admonition.todo>.admonition-title,.md-typeset .admonition.todo>summary,.md-typeset details.info>.admonition-title,.md-typeset details.info>summary,.md-typeset details.todo>.admonition-title,.md-typeset details.todo>summary{border-bottom-color:rgba(0,184,212,.1);background-color:rgba(0,184,212,.1)}.md-typeset .admonition.info>.admonition-title:before,.md-typeset .admonition.info>summary:before,.md-typeset .admonition.todo>.admonition-title:before,.md-typeset .admonition.todo>summary:before,.md-typeset details.info>.admonition-title:before,.md-typeset details.info>summary:before,.md-typeset details.todo>.admonition-title:before,.md-typeset details.todo>summary:before{color:#00b8d4;content:""}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-left-color:#00bfa5}[dir=rtl] .md-typeset .admonition.hint,[dir=rtl] .md-typeset .admonition.important,[dir=rtl] .md-typeset .admonition.tip,[dir=rtl] .md-typeset details.hint,[dir=rtl] .md-typeset details.important,[dir=rtl] .md-typeset details.tip{border-right-color:#00bfa5}.md-typeset .admonition.hint>.admonition-title,.md-typeset .admonition.hint>summary,.md-typeset .admonition.important>.admonition-title,.md-typeset .admonition.important>summary,.md-typeset .admonition.tip>.admonition-title,.md-typeset .admonition.tip>summary,.md-typeset details.hint>.admonition-title,.md-typeset details.hint>summary,.md-typeset details.important>.admonition-title,.md-typeset details.important>summary,.md-typeset details.tip>.admonition-title,.md-typeset details.tip>summary{border-bottom-color:rgba(0,191,165,.1);background-color:rgba(0,191,165,.1)}.md-typeset .admonition.hint>.admonition-title:before,.md-typeset .admonition.hint>summary:before,.md-typeset .admonition.important>.admonition-title:before,.md-typeset .admonition.important>summary:before,.md-typeset .admonition.tip>.admonition-title:before,.md-typeset .admonition.tip>summary:before,.md-typeset details.hint>.admonition-title:before,.md-typeset details.hint>summary:before,.md-typeset details.important>.admonition-title:before,.md-typeset details.important>summary:before,.md-typeset details.tip>.admonition-title:before,.md-typeset details.tip>summary:before{color:#00bfa5;content:""}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-left-color:#00c853}[dir=rtl] .md-typeset .admonition.check,[dir=rtl] .md-typeset .admonition.done,[dir=rtl] .md-typeset .admonition.success,[dir=rtl] .md-typeset details.check,[dir=rtl] .md-typeset details.done,[dir=rtl] .md-typeset details.success{border-right-color:#00c853}.md-typeset .admonition.check>.admonition-title,.md-typeset .admonition.check>summary,.md-typeset .admonition.done>.admonition-title,.md-typeset .admonition.done>summary,.md-typeset .admonition.success>.admonition-title,.md-typeset .admonition.success>summary,.md-typeset details.check>.admonition-title,.md-typeset details.check>summary,.md-typeset details.done>.admonition-title,.md-typeset details.done>summary,.md-typeset details.success>.admonition-title,.md-typeset details.success>summary{border-bottom-color:rgba(0,200,83,.1);background-color:rgba(0,200,83,.1)}.md-typeset .admonition.check>.admonition-title:before,.md-typeset .admonition.check>summary:before,.md-typeset .admonition.done>.admonition-title:before,.md-typeset .admonition.done>summary:before,.md-typeset .admonition.success>.admonition-title:before,.md-typeset .admonition.success>summary:before,.md-typeset details.check>.admonition-title:before,.md-typeset details.check>summary:before,.md-typeset details.done>.admonition-title:before,.md-typeset details.done>summary:before,.md-typeset details.success>.admonition-title:before,.md-typeset details.success>summary:before{color:#00c853;content:""}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-left-color:#64dd17}[dir=rtl] .md-typeset .admonition.faq,[dir=rtl] .md-typeset .admonition.help,[dir=rtl] .md-typeset .admonition.question,[dir=rtl] .md-typeset details.faq,[dir=rtl] .md-typeset details.help,[dir=rtl] .md-typeset details.question{border-right-color:#64dd17}.md-typeset .admonition.faq>.admonition-title,.md-typeset .admonition.faq>summary,.md-typeset .admonition.help>.admonition-title,.md-typeset .admonition.help>summary,.md-typeset .admonition.question>.admonition-title,.md-typeset .admonition.question>summary,.md-typeset details.faq>.admonition-title,.md-typeset details.faq>summary,.md-typeset details.help>.admonition-title,.md-typeset details.help>summary,.md-typeset details.question>.admonition-title,.md-typeset details.question>summary{border-bottom-color:rgba(100,221,23,.1);background-color:rgba(100,221,23,.1)}.md-typeset .admonition.faq>.admonition-title:before,.md-typeset .admonition.faq>summary:before,.md-typeset .admonition.help>.admonition-title:before,.md-typeset .admonition.help>summary:before,.md-typeset .admonition.question>.admonition-title:before,.md-typeset .admonition.question>summary:before,.md-typeset details.faq>.admonition-title:before,.md-typeset details.faq>summary:before,.md-typeset details.help>.admonition-title:before,.md-typeset details.help>summary:before,.md-typeset details.question>.admonition-title:before,.md-typeset details.question>summary:before{color:#64dd17;content:""}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-left-color:#ff9100}[dir=rtl] .md-typeset .admonition.attention,[dir=rtl] .md-typeset .admonition.caution,[dir=rtl] .md-typeset .admonition.warning,[dir=rtl] .md-typeset details.attention,[dir=rtl] .md-typeset details.caution,[dir=rtl] .md-typeset details.warning{border-right-color:#ff9100}.md-typeset .admonition.attention>.admonition-title,.md-typeset .admonition.attention>summary,.md-typeset .admonition.caution>.admonition-title,.md-typeset .admonition.caution>summary,.md-typeset .admonition.warning>.admonition-title,.md-typeset .admonition.warning>summary,.md-typeset details.attention>.admonition-title,.md-typeset details.attention>summary,.md-typeset details.caution>.admonition-title,.md-typeset details.caution>summary,.md-typeset details.warning>.admonition-title,.md-typeset details.warning>summary{border-bottom-color:rgba(255,145,0,.1);background-color:rgba(255,145,0,.1)}.md-typeset .admonition.attention>.admonition-title:before,.md-typeset .admonition.attention>summary:before,.md-typeset .admonition.caution>.admonition-title:before,.md-typeset .admonition.caution>summary:before,.md-typeset .admonition.warning>.admonition-title:before,.md-typeset .admonition.warning>summary:before,.md-typeset details.attention>.admonition-title:before,.md-typeset details.attention>summary:before,.md-typeset details.caution>.admonition-title:before,.md-typeset details.caution>summary:before,.md-typeset details.warning>.admonition-title:before,.md-typeset details.warning>summary:before{color:#ff9100;content:""}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-left-color:#ff5252}[dir=rtl] .md-typeset .admonition.fail,[dir=rtl] .md-typeset .admonition.failure,[dir=rtl] .md-typeset .admonition.missing,[dir=rtl] .md-typeset details.fail,[dir=rtl] .md-typeset details.failure,[dir=rtl] .md-typeset details.missing{border-right-color:#ff5252}.md-typeset .admonition.fail>.admonition-title,.md-typeset .admonition.fail>summary,.md-typeset .admonition.failure>.admonition-title,.md-typeset .admonition.failure>summary,.md-typeset .admonition.missing>.admonition-title,.md-typeset .admonition.missing>summary,.md-typeset details.fail>.admonition-title,.md-typeset details.fail>summary,.md-typeset details.failure>.admonition-title,.md-typeset details.failure>summary,.md-typeset details.missing>.admonition-title,.md-typeset details.missing>summary{border-bottom-color:rgba(255,82,82,.1);background-color:rgba(255,82,82,.1)}.md-typeset .admonition.fail>.admonition-title:before,.md-typeset .admonition.fail>summary:before,.md-typeset .admonition.failure>.admonition-title:before,.md-typeset .admonition.failure>summary:before,.md-typeset .admonition.missing>.admonition-title:before,.md-typeset .admonition.missing>summary:before,.md-typeset details.fail>.admonition-title:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>.admonition-title:before,.md-typeset details.failure>summary:before,.md-typeset details.missing>.admonition-title:before,.md-typeset details.missing>summary:before{color:#ff5252;content:""}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-left-color:#ff1744}[dir=rtl] .md-typeset .admonition.danger,[dir=rtl] .md-typeset .admonition.error,[dir=rtl] .md-typeset details.danger,[dir=rtl] .md-typeset details.error{border-right-color:#ff1744}.md-typeset .admonition.danger>.admonition-title,.md-typeset .admonition.danger>summary,.md-typeset .admonition.error>.admonition-title,.md-typeset .admonition.error>summary,.md-typeset details.danger>.admonition-title,.md-typeset details.danger>summary,.md-typeset details.error>.admonition-title,.md-typeset details.error>summary{border-bottom-color:rgba(255,23,68,.1);background-color:rgba(255,23,68,.1)}.md-typeset .admonition.danger>.admonition-title:before,.md-typeset .admonition.danger>summary:before,.md-typeset .admonition.error>.admonition-title:before,.md-typeset .admonition.error>summary:before,.md-typeset details.danger>.admonition-title:before,.md-typeset details.danger>summary:before,.md-typeset details.error>.admonition-title:before,.md-typeset details.error>summary:before{color:#ff1744;content:""}.md-typeset .admonition.bug,.md-typeset details.bug{border-left-color:#f50057}[dir=rtl] .md-typeset .admonition.bug,[dir=rtl] .md-typeset details.bug{border-right-color:#f50057}.md-typeset .admonition.bug>.admonition-title,.md-typeset .admonition.bug>summary,.md-typeset details.bug>.admonition-title,.md-typeset details.bug>summary{border-bottom-color:rgba(245,0,87,.1);background-color:rgba(245,0,87,.1)}.md-typeset .admonition.bug>.admonition-title:before,.md-typeset .admonition.bug>summary:before,.md-typeset details.bug>.admonition-title:before,.md-typeset details.bug>summary:before{color:#f50057;content:""}.md-typeset .admonition.example,.md-typeset details.example{border-left-color:#651fff}[dir=rtl] .md-typeset .admonition.example,[dir=rtl] .md-typeset details.example{border-right-color:#651fff}.md-typeset .admonition.example>.admonition-title,.md-typeset .admonition.example>summary,.md-typeset details.example>.admonition-title,.md-typeset details.example>summary{border-bottom-color:rgba(101,31,255,.1);background-color:rgba(101,31,255,.1)}.md-typeset .admonition.example>.admonition-title:before,.md-typeset .admonition.example>summary:before,.md-typeset details.example>.admonition-title:before,.md-typeset details.example>summary:before{color:#651fff;content:""}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-left-color:#9e9e9e}[dir=rtl] .md-typeset .admonition.cite,[dir=rtl] .md-typeset .admonition.quote,[dir=rtl] .md-typeset details.cite,[dir=rtl] .md-typeset details.quote{border-right-color:#9e9e9e}.md-typeset .admonition.cite>.admonition-title,.md-typeset .admonition.cite>summary,.md-typeset .admonition.quote>.admonition-title,.md-typeset .admonition.quote>summary,.md-typeset details.cite>.admonition-title,.md-typeset details.cite>summary,.md-typeset details.quote>.admonition-title,.md-typeset details.quote>summary{border-bottom-color:hsla(0,0%,62%,.1);background-color:hsla(0,0%,62%,.1)}.md-typeset .admonition.cite>.admonition-title:before,.md-typeset .admonition.cite>summary:before,.md-typeset .admonition.quote>.admonition-title:before,.md-typeset .admonition.quote>summary:before,.md-typeset details.cite>.admonition-title:before,.md-typeset details.cite>summary:before,.md-typeset details.quote>.admonition-title:before,.md-typeset details.quote>summary:before{color:#9e9e9e;content:""}.codehilite .o,.codehilite .ow,.md-typeset .highlight .o,.md-typeset .highlight .ow{color:inherit}.codehilite .ge,.md-typeset .highlight .ge{color:#000}.codehilite .gr,.md-typeset .highlight .gr{color:#a00}.codehilite .gh,.md-typeset .highlight .gh{color:#999}.codehilite .go,.md-typeset .highlight .go{color:#888}.codehilite .gp,.md-typeset .highlight .gp{color:#555}.codehilite .gs,.md-typeset .highlight .gs{color:inherit}.codehilite .gu,.md-typeset .highlight .gu{color:#aaa}.codehilite .gt,.md-typeset .highlight .gt{color:#a00}.codehilite .gd,.md-typeset .highlight .gd{background-color:#fdd}.codehilite .gi,.md-typeset .highlight .gi{background-color:#dfd}.codehilite .k,.md-typeset .highlight .k{color:#3b78e7}.codehilite .kc,.md-typeset .highlight .kc{color:#a71d5d}.codehilite .kd,.codehilite .kn,.md-typeset .highlight .kd,.md-typeset .highlight .kn{color:#3b78e7}.codehilite .kp,.md-typeset .highlight .kp{color:#a71d5d}.codehilite .kr,.codehilite .kt,.md-typeset .highlight .kr,.md-typeset .highlight .kt{color:#3e61a2}.codehilite .c,.codehilite .cm,.md-typeset .highlight .c,.md-typeset .highlight .cm{color:#999}.codehilite .cp,.md-typeset .highlight .cp{color:#666}.codehilite .c1,.codehilite .ch,.codehilite .cs,.md-typeset .highlight .c1,.md-typeset .highlight .ch,.md-typeset .highlight .cs{color:#999}.codehilite .na,.codehilite .nb,.md-typeset .highlight .na,.md-typeset .highlight .nb{color:#c2185b}.codehilite .bp,.md-typeset .highlight .bp{color:#3e61a2}.codehilite .nc,.md-typeset .highlight .nc{color:#c2185b}.codehilite .no,.md-typeset .highlight .no{color:#3e61a2}.codehilite .nd,.codehilite .ni,.md-typeset .highlight .nd,.md-typeset .highlight .ni{color:#666}.codehilite .ne,.codehilite .nf,.md-typeset .highlight .ne,.md-typeset .highlight .nf{color:#c2185b}.codehilite .nl,.md-typeset .highlight .nl{color:#3b5179}.codehilite .nn,.md-typeset .highlight .nn{color:#ec407a}.codehilite .nt,.md-typeset .highlight .nt{color:#3b78e7}.codehilite .nv,.codehilite .vc,.codehilite .vg,.codehilite .vi,.md-typeset .highlight .nv,.md-typeset .highlight .vc,.md-typeset .highlight .vg,.md-typeset .highlight .vi{color:#3e61a2}.codehilite .nx,.md-typeset .highlight .nx{color:#ec407a}.codehilite .il,.codehilite .m,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.md-typeset .highlight .il,.md-typeset .highlight .m,.md-typeset .highlight .mf,.md-typeset .highlight .mh,.md-typeset .highlight .mi,.md-typeset .highlight .mo{color:#e74c3c}.codehilite .s,.codehilite .sb,.codehilite .sc,.md-typeset .highlight .s,.md-typeset .highlight .sb,.md-typeset .highlight .sc{color:#0d904f}.codehilite .sd,.md-typeset .highlight .sd{color:#999}.codehilite .s2,.md-typeset .highlight .s2{color:#0d904f}.codehilite .se,.codehilite .sh,.codehilite .si,.codehilite .sx,.md-typeset .highlight .se,.md-typeset .highlight .sh,.md-typeset .highlight .si,.md-typeset .highlight .sx{color:#183691}.codehilite .sr,.md-typeset .highlight .sr{color:#009926}.codehilite .s1,.codehilite .ss,.md-typeset .highlight .s1,.md-typeset .highlight .ss{color:#0d904f}.codehilite .err,.md-typeset .highlight .err{color:#a61717}.codehilite .w,.md-typeset .highlight .w{color:transparent}.codehilite .hll,.md-typeset .highlight .hll{display:block;margin:0 -.6rem;padding:0 .6rem;background-color:rgba(255,235,59,.5)}.md-typeset .codehilite,.md-typeset .highlight{position:relative;margin:1em 0;padding:0;border-radius:.1rem;background-color:hsla(0,0%,92.5%,.5);color:#37474f;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset .codehilite code,.md-typeset .codehilite pre,.md-typeset .highlight code,.md-typeset .highlight pre{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;overflow:auto;vertical-align:top}.md-typeset .codehilite code::-webkit-scrollbar,.md-typeset .codehilite pre::-webkit-scrollbar,.md-typeset .highlight code::-webkit-scrollbar,.md-typeset .highlight pre::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset .codehilite code::-webkit-scrollbar-thumb,.md-typeset .codehilite pre::-webkit-scrollbar-thumb,.md-typeset .highlight code::-webkit-scrollbar-thumb,.md-typeset .highlight pre::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset .codehilite code::-webkit-scrollbar-thumb:hover,.md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,.md-typeset .highlight code::-webkit-scrollbar-thumb:hover,.md-typeset .highlight pre::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset pre.codehilite,.md-typeset pre.highlight{overflow:visible}.md-typeset pre.codehilite code,.md-typeset pre.highlight code{display:block;padding:.525rem .6rem;overflow:auto}.md-typeset .codehilitetable,.md-typeset .highlighttable{display:block;margin:1em 0;border-radius:.2em;font-size:.8rem;overflow:hidden}.md-typeset .codehilitetable tbody,.md-typeset .codehilitetable td,.md-typeset .highlighttable tbody,.md-typeset .highlighttable td{display:block;padding:0}.md-typeset .codehilitetable tr,.md-typeset .highlighttable tr{display:-webkit-box;display:flex}.md-typeset .codehilitetable .codehilite,.md-typeset .codehilitetable .highlight,.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .codehilite,.md-typeset .highlighttable .highlight,.md-typeset .highlighttable .linenodiv{margin:0;border-radius:0}.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .linenodiv{padding:.525rem .6rem}.md-typeset .codehilitetable .linenos,.md-typeset .highlighttable .linenos{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.26);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset .codehilitetable .linenos pre,.md-typeset .highlighttable .linenos pre{margin:0;padding:0;background-color:transparent;color:inherit;text-align:right}.md-typeset .codehilitetable .code,.md-typeset .highlighttable .code{-webkit-box-flex:1;flex:1;overflow:hidden}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{box-shadow:none}.md-typeset [id^="fnref:"]{display:inline-block}.md-typeset [id^="fnref:"]:target{margin-top:-3.8rem;padding-top:3.8rem;pointer-events:none}.md-typeset [id^="fn:"]:before{display:none;height:0;content:""}.md-typeset [id^="fn:"]:target:before{display:block;margin-top:-3.5rem;padding-top:3.5rem;pointer-events:none}.md-typeset .footnote{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{-webkit-transition:color .25s;transition:color .25s}.md-typeset .footnote li:target{color:rgba(0,0,0,.87)}.md-typeset .footnote li :first-child{margin-top:0}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}.md-typeset .footnote li:hover .footnote-backref:hover,.md-typeset .footnote li:target .footnote-backref{color:#536dfe}.md-typeset .footnote-ref{display:inline-block;pointer-events:auto}.md-typeset .footnote-ref:before{display:inline;margin:0 .2em;border-left:.05rem solid rgba(0,0,0,.26);font-size:1.25em;content:"";vertical-align:-.25rem}.md-typeset .footnote-backref{display:inline-block;-webkit-transform:translateX(.25rem);transform:translateX(.25rem);-webkit-transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s,-webkit-transform .25s .125s;color:rgba(0,0,0,.26);font-size:0;opacity:0;vertical-align:text-bottom}[dir=rtl] .md-typeset .footnote-backref{-webkit-transform:translateX(-.25rem);transform:translateX(-.25rem)}.md-typeset .footnote-backref:before{display:inline-block;font-size:.8rem;content:"\E31B"}[dir=rtl] .md-typeset .footnote-backref:before{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;-webkit-transform:translateY(.25rem);transform:translateY(.25rem);-webkit-transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s,-webkit-transform .25s .25s;opacity:0}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}html body .md-typeset .headerlink{color:rgba(0,0,0,.26)}.md-typeset h1[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h1[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink,.md-typeset h1[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink:hover,.md-typeset h1[id]:target .headerlink{color:#536dfe}.md-typeset h2[id]:before{display:block;margin-top:-8px;padding-top:8px;content:""}.md-typeset h2[id]:target:before{margin-top:-3.4rem;padding-top:3.4rem}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink,.md-typeset h2[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink:hover,.md-typeset h2[id]:target .headerlink{color:#536dfe}.md-typeset h3[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h3[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink,.md-typeset h3[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink:hover,.md-typeset h3[id]:target .headerlink{color:#536dfe}.md-typeset h4[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h4[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink,.md-typeset h4[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink:hover,.md-typeset h4[id]:target .headerlink{color:#536dfe}.md-typeset h5[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h5[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink,.md-typeset h5[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink:hover,.md-typeset h5[id]:target .headerlink{color:#536dfe}.md-typeset h6[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h6[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink,.md-typeset h6[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink:hover,.md-typeset h6[id]:target .headerlink{color:#536dfe}.md-typeset .MJXc-display{margin:.75em 0;padding:.75em 0;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset .MathJax_CHTML{outline:0}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:#fdd;box-shadow:.25em 0 0 #fdd,-.25em 0 0 #fdd}.md-typeset ins.critic{background-color:#dfd;box-shadow:.25em 0 0 #dfd,-.25em 0 0 #dfd}.md-typeset .critic.comment{background-color:hsla(0,0%,92.5%,.5);color:#37474f;box-shadow:.25em 0 0 hsla(0,0%,92.5%,.5),-.25em 0 0 hsla(0,0%,92.5%,.5)}.md-typeset .critic.comment:before{padding-right:.125em;color:rgba(0,0,0,.26);content:"\E0B7";vertical-align:-.125em}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;box-shadow:none}.md-typeset .critic.block :first-child{margin-top:.5em}.md-typeset .critic.block :last-child{margin-bottom:.5em}.md-typeset details{display:block;padding-top:0}.md-typeset details[open]>summary:after{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.md-typeset details:not([open]){padding-bottom:0}.md-typeset details:not([open])>summary{border-bottom:none}.md-typeset details summary{padding-right:2rem}[dir=rtl] .md-typeset details summary{padding-left:2rem}.no-details .md-typeset details:not([open])>*{display:none}.no-details .md-typeset details:not([open]) summary{display:block}.md-typeset summary{display:block;outline:none;cursor:pointer}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset summary:after{position:absolute;top:.4rem;right:.6rem;color:rgba(0,0,0,.26);font-size:1rem;content:"\E313"}[dir=rtl] .md-typeset summary:after{right:auto;left:.6rem}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{width:1rem;vertical-align:text-top}.md-typeset code.codehilite,.md-typeset code.highlight{margin:0 .29412em;padding:.07353em 0}.md-typeset .superfences-content{display:none;-webkit-box-ordinal-group:100;order:99;width:100%;background-color:#fff}.md-typeset .superfences-content>*{margin:0;border-radius:0}.md-typeset .superfences-tabs{display:-webkit-box;display:flex;position:relative;flex-wrap:wrap;margin:1em 0;border:.05rem solid rgba(0,0,0,.07);border-radius:.2em}.md-typeset .superfences-tabs>input{display:none}.md-typeset .superfences-tabs>input:checked+label{font-weight:700}.md-typeset .superfences-tabs>input:checked+label+.superfences-content{display:block}.md-typeset .superfences-tabs>label{width:auto;padding:.6rem;-webkit-transition:color .125s;transition:color .125s;font-size:.64rem;cursor:pointer}html .md-typeset .superfences-tabs>label:hover{color:#536dfe}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control .task-list-indicator:before{position:absolute;top:.15em;left:-1.25em;color:rgba(0,0,0,.26);font-size:1.25em;content:"\E835";vertical-align:-.25em}[dir=rtl] .md-typeset .task-list-control .task-list-indicator:before{right:-1.25em;left:auto}.md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator:before{content:"\E834"}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}@media print{.md-typeset a:after{color:rgba(0,0,0,.54);content:" [" attr(href) "]"}.md-typeset code,.md-typeset pre{white-space:pre-wrap}.md-typeset code{box-shadow:none;-webkit-box-decoration-break:initial;box-decoration-break:slice}.md-clipboard,.md-content__icon,.md-footer,.md-header,.md-sidebar,.md-tabs,.md-typeset .headerlink{display:none}}@media only screen and (max-width:44.9375em){.md-typeset pre{margin:1em -.8rem;border-radius:0}.md-typeset pre>code{padding:.525rem .8rem}.md-footer-nav__link--prev .md-footer-nav__title{display:none}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}.codehilite .hll,.md-typeset .highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.codehilite,.md-typeset>.highlight{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilite code,.md-typeset>.codehilite pre,.md-typeset>.highlight code,.md-typeset>.highlight pre{padding:.525rem .8rem}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilitetable .codehilite>code,.md-typeset>.codehilitetable .codehilite>pre,.md-typeset>.codehilitetable .highlight>code,.md-typeset>.codehilitetable .highlight>pre,.md-typeset>.codehilitetable .linenodiv,.md-typeset>.highlighttable .codehilite>code,.md-typeset>.highlighttable .codehilite>pre,.md-typeset>.highlighttable .highlight>code,.md-typeset>.highlighttable .highlight>pre,.md-typeset>.highlighttable .linenodiv{padding:.5rem .8rem}.md-typeset>p>.MJXc-display{margin:.75em -.8rem;padding:.25em .8rem}.md-typeset>.superfences-tabs{margin:1em -.8rem;border:0;border-top:.05rem solid rgba(0,0,0,.07);border-radius:0}.md-typeset>.superfences-tabs code,.md-typeset>.superfences-tabs pre{padding:.525rem .8rem}}@media only screen and (min-width:100em){html{font-size:137.5%}}@media only screen and (min-width:125em){html{font-size:150%}}@media only screen and (max-width:59.9375em){body[data-md-state=lock]{overflow:hidden}.ios body[data-md-state=lock] .md-container{display:none}html .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}html .md-nav__link[for=__toc]:after{color:inherit;content:"\E8DE"}html .md-nav__link[for=__toc]+.md-nav__link{display:none}html .md-nav__link[for=__toc]~.md-nav{display:-webkit-box;display:flex}html [dir=rtl] .md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav__source{display:block;padding:0 .2rem;background-color:rgba(50,64,144,.9675);color:#fff}.md-search__overlay{position:absolute;top:.2rem;left:.2rem;width:1.8rem;height:1.8rem;-webkit-transform-origin:center;transform-origin:center;-webkit-transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:transform .3s .1s,opacity .2s .2s;transition:transform .3s .1s,opacity .2s .2s,-webkit-transform .3s .1s;border-radius:1rem;background-color:#fff;overflow:hidden;pointer-events:none}[dir=rtl] .md-search__overlay{right:.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transition:opacity .1s,-webkit-transform .4s;transition:opacity .1s,-webkit-transform .4s;transition:transform .4s,opacity .1s;transition:transform .4s,opacity .1s,-webkit-transform .4s;opacity:1}.md-search__inner{position:fixed;top:0;left:100%;width:100%;height:100%;-webkit-transform:translateX(5%);transform:translateX(5%);-webkit-transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;opacity:0;z-index:2}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;opacity:1}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;-webkit-transform:translateX(-5%);transform:translateX(-5%)}.md-search__input{width:100%;height:2.4rem;font-size:.9rem}.md-search__icon[for=__search]{top:.6rem;left:.8rem}.md-search__icon[for=__search][for=__search]:before{content:"\E5C4"}[dir=rtl] .md-search__icon[for=__search][for=__search]:before{content:"\E5C8"}.md-search__icon[type=reset]{top:.6rem;right:.8rem}.md-search__output{top:2.4rem;bottom:0}.md-search-result__article--document:before{display:none}}@media only screen and (max-width:76.1875em){[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;-webkit-transition:width 0s,height 0s,opacity .25s;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-header-nav__button.md-icon--home,.md-header-nav__button.md-logo{display:none}.md-hero__inner{margin-top:2.4rem;margin-bottom:1.2rem}.md-nav{background-color:#fff}.md-nav--primary,.md-nav--primary .md-nav{display:-webkit-box;display:flex;position:absolute;top:0;right:0;left:0;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;height:100%;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}html .md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-weight:400;line-height:2.4rem;white-space:nowrap;cursor:pointer}html .md-nav--primary .md-nav__title:before{display:block;position:absolute;top:.2rem;left:.2rem;width:2rem;height:2rem;color:rgba(0,0,0,.54)}html .md-nav--primary .md-nav__title~.md-nav__list{background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07)}html .md-nav--primary .md-nav__title~.md-nav__list>.md-nav__item:first-child{border-top:0}html .md-nav--primary .md-nav__title--site{position:relative;background-color:#3f51b5;color:#fff}html .md-nav--primary .md-nav__title--site .md-nav__button{display:block;position:absolute;top:.2rem;left:.2rem;width:3.2rem;height:3.2rem;font-size:2.4rem}html .md-nav--primary .md-nav__title--site:before{display:none}html [dir=rtl] .md-nav--primary .md-nav__title--site .md-nav__button,html [dir=rtl] .md-nav--primary .md-nav__title:before{right:.2rem;left:auto}.md-nav--primary .md-nav__list{-webkit-box-flex:1;flex:1;overflow-y:auto}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid rgba(0,0,0,.07)}[dir=rtl] .md-nav--primary .md-nav__item{padding:0}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E315"}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E314"}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link:after{position:absolute;top:50%;right:.6rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link:after{right:auto;left:.6rem}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:transparent}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav__toggle~.md-nav{display:-webkit-box;display:flex;-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);opacity:0}[dir=rtl] .md-nav__toggle~.md-nav{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.no-csstransforms3d .md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1}.no-csstransforms3d .md-nav__toggle:checked~.md-nav{display:-webkit-box;display:flex}.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;width:12.1rem;height:100%;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);background-color:#fff;z-index:3}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}.no-csstransforms3d .md-sidebar--primary{display:none}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);-webkit-transform:translateX(12.1rem);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{-webkit-transform:translateX(-12.1rem);transform:translateX(-12.1rem)}.no-csstransforms3d [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{display:block}.md-sidebar--primary .md-sidebar__scrollwrap{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;margin:0}.md-tabs{display:none}}@media only screen and (min-width:60em){.md-content{margin-right:12.1rem}[dir=rtl] .md-content{margin-right:0;margin-left:12.1rem}.md-header-nav__button.md-icon--search{display:none}.md-header-nav__source{display:block;width:11.7rem;max-width:11.7rem;padding-right:.6rem}[dir=rtl] .md-header-nav__source{padding-right:0;padding-left:.6rem}.md-search{padding:.2rem}.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;-webkit-transition:width 0s .25s,height 0s .25s,opacity .25s;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);cursor:pointer}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:100%;-webkit-transition:width 0s,height 0s,opacity .25s;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-search__inner{position:relative;width:11.5rem;margin-right:.8rem;padding:.1rem 0;float:right;-webkit-transition:width .25s cubic-bezier(.1,.7,.1,1);transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{margin-right:0;margin-left:.8rem;float:left}.md-search__form,.md-search__input{border-radius:.1rem}.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;-webkit-transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);background-color:rgba(0,0,0,.26);color:inherit;font-size:.8rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:inherit}.md-search__input::-webkit-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-moz-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{border-radius:.1rem .1rem 0 0;background-color:#fff;color:rgba(0,0,0,.87);text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:rgba(0,0,0,.54)}.md-search__output{top:1.9rem;-webkit-transition:opacity .4s;transition:opacity .4s;opacity:0}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}.md-search__scrollwrap{max-height:0}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}.md-sidebar--secondary{display:block;margin-left:100%;-webkit-transform:translate(-100%);transform:translate(-100%)}[dir=rtl] .md-sidebar--secondary{margin-right:100%;margin-left:0;-webkit-transform:translate(100%);transform:translate(100%)}}@media only screen and (min-width:76.25em){.md-content{margin-left:12.1rem}[dir=rtl] .md-content{margin-right:12.1rem}.md-content__inner{margin-right:1.2rem;margin-left:1.2rem}.md-header-nav__button.md-icon--menu{display:none}.md-nav[data-md-state=animate]{-webkit-transition:max-height .25s cubic-bezier(.86,0,.07,1);transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav__toggle~.md-nav{max-height:0;overflow:hidden}.no-js .md-nav__toggle~.md-nav{display:none}.md-nav[data-md-state=expand],.md-nav__toggle:checked~.md-nav{max-height:100%}.no-js .md-nav[data-md-state=expand],.no-js .md-nav__toggle:checked~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--nested>.md-nav__link:after{display:inline-block;-webkit-transform-origin:.45em .45em;transform-origin:.45em .45em;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;vertical-align:-.125em}.js .md-nav__item--nested>.md-nav__link:after{-webkit-transition:-webkit-transform .4s;transition:-webkit-transform .4s;transition:transform .4s;transition:transform .4s,-webkit-transform .4s}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link:after{-webkit-transform:rotateX(180deg);transform:rotateX(180deg)}.md-search__inner{margin-right:1.2rem}[dir=rtl] .md-search__inner{margin-left:1.2rem}.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}.md-sidebar--secondary{margin-left:61rem}[dir=rtl] .md-sidebar--secondary{margin-right:61rem;margin-left:0}.md-tabs~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary .md-nav__title{display:block;padding:0}.md-tabs--active~.md-main .md-nav--primary .md-nav__title--site{display:none}.no-js .md-tabs--active~.md-main .md-nav--primary .md-nav{display:block}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{display:none;font-size:.7rem;overflow:auto;visibility:visible}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested>.md-nav__link{display:none}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--active{display:block}.md-tabs--active~.md-main .md-nav[data-md-level="1"]{max-height:none;overflow:visible}.md-tabs--active~.md-main .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:0}.md-tabs--active~.md-main .md-nav[data-md-level="1"] .md-nav .md-nav__title{display:none}}@media only screen and (min-width:45em){.md-footer-nav__link{width:50%}.md-footer-copyright{max-width:75%;float:left}[dir=rtl] .md-footer-copyright{float:right}.md-footer-social{padding:.6rem 0;float:right}[dir=rtl] .md-footer-social{float:left}}@media only screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(45);transform:scale(45)}}@media only screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(60);transform:scale(60)}}@media only screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(75);transform:scale(75)}}@media only screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}} \ No newline at end of file diff --git a/docs/assets/stylesheets/application.750b69bd.css b/docs/assets/stylesheets/application.750b69bd.css deleted file mode 100644 index 7c7ab1cf..00000000 --- a/docs/assets/stylesheets/application.750b69bd.css +++ /dev/null @@ -1 +0,0 @@ -@charset "UTF-8";html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}body{margin:0}hr{overflow:visible;box-sizing:content-box}a{-webkit-text-decoration-skip:objects}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}small,sub,sup{font-size:80%}sub,sup{position:relative;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}table{border-collapse:separate;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;border:0;outline-style:none;background:transparent;font-size:inherit}input{border:0;outline:0}.md-clipboard:before,.md-icon,.md-nav__button,.md-nav__link:after,.md-nav__title:before,.md-search-result__article--document:before,.md-source-file:before,.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset .critic.comment:before,.md-typeset .footnote-backref,.md-typeset .task-list-control .task-list-indicator:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before,.md-typeset summary:after{font-family:Material Icons;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-content__icon,.md-footer-nav__button,.md-header-nav__button,.md-nav__button,.md-nav__title:before,.md-search-result__article--document:before{display:inline-block;margin:.2rem;padding:.4rem;font-size:1.2rem;cursor:pointer}.md-icon--arrow-back:before{content:""}.md-icon--arrow-forward:before{content:""}.md-icon--menu:before{content:""}.md-icon--search:before{content:""}[dir=rtl] .md-icon--arrow-back:before{content:""}[dir=rtl] .md-icon--arrow-forward:before{content:""}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern","liga";font-feature-settings:"kern","liga";font-family:Helvetica Neue,Helvetica,Arial,sans-serif}code,kbd,pre{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern";font-feature-settings:"kern";font-family:Courier New,Courier,monospace}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact}.md-typeset blockquote,.md-typeset ol,.md-typeset p,.md-typeset ul{margin:1em 0}.md-typeset h1{margin:0 0 2rem;color:rgba(0,0,0,.54);font-size:1.5625rem;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:2rem 0 .8rem;font-size:1.25rem;line-height:1.4}.md-typeset h3{margin:1.6rem 0 .8rem;font-size:1rem;font-weight:400;letter-spacing:-.01em;line-height:1.5}.md-typeset h2+h3{margin-top:.8rem}.md-typeset h4{font-size:.8rem}.md-typeset h4,.md-typeset h5,.md-typeset h6{margin:.8rem 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset h5{text-transform:uppercase}.md-typeset hr{margin:1.5em 0;border-bottom:.05rem dotted rgba(0,0,0,.26)}.md-typeset a{color:#3f51b5;word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color .125s}.md-typeset a:active,.md-typeset a:hover{color:#536dfe}.md-typeset code,.md-typeset pre{background-color:hsla(0,0%,92.5%,.5);color:#37474f;font-size:85%;direction:ltr}.md-typeset code{margin:0 .29412em;padding:.07353em 0;border-radius:.1rem;box-shadow:.29412em 0 0 hsla(0,0%,92.5%,.5),-.29412em 0 0 hsla(0,0%,92.5%,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:0;background-color:transparent;box-shadow:none}.md-typeset a>code{margin:inherit;padding:inherit;border-radius:initial;background-color:inherit;color:inherit;box-shadow:none}.md-typeset pre{position:relative;margin:1em 0;border-radius:.1rem;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset pre>code{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;font-size:inherit;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;overflow:auto}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset kbd{padding:0 .29412em;border-radius:.15rem;border:.05rem solid #c9c9c9;border-bottom-color:#bcbcbc;background-color:#fcfcfc;color:#555;font-size:85%;box-shadow:0 .05rem 0 #b0b0b0;word-break:break-word}.md-typeset mark{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;background-color:rgba(255,235,59,.5);box-shadow:.25em 0 0 rgba(255,235,59,.5),-.25em 0 0 rgba(255,235,59,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{border-bottom:.05rem dotted rgba(0,0,0,.54);text-decoration:none;cursor:help}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.07812em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.07812em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;border-left:.2rem solid rgba(0,0,0,.26);color:rgba(0,0,0,.54)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid rgba(0,0,0,.26);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset iframe,.md-typeset img,.md-typeset svg{max-width:100%}.md-typeset table:not([class]){box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);display:inline-block;max-width:100%;border-radius:.1rem;font-size:.64rem;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.6rem .8rem;background-color:rgba(0,0,0,.54);color:#fff;vertical-align:top}.md-typeset table:not([class]) td{padding:.6rem .8rem;border-top:.05rem solid rgba(0,0,0,.07);vertical-align:top}.md-typeset table:not([class]) tr{transition:background-color .125s}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:inset 0 .05rem 0 #fff}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;-webkit-overflow-scrolling:touch}.md-typeset .md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}.md-typeset .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{font-size:125%;overflow-x:hidden}body,html{height:100%}body{position:relative;font-size:.5rem}hr{display:block;height:.05rem;padding:0;border:0}.md-svg{display:none}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container,.md-main{overflow:auto}.md-container{display:table;width:100%;height:100%;padding-top:2.4rem;table-layout:fixed}.md-main{display:table-row;height:100%}.md-main__inner{height:100%;padding-top:1.5rem;padding-bottom:.05rem}.md-toggle{display:none}.md-overlay{position:fixed;top:0;width:0;height:0;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);opacity:0;z-index:3}.md-flex{display:table}.md-flex__cell{display:table-cell;position:relative;vertical-align:top}.md-flex__cell--shrink{width:0}.md-flex__cell--stretch{display:table;width:100%;table-layout:fixed}.md-flex__ellipsis{display:table-cell;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-skip{position:fixed;width:.05rem;height:.05rem;margin:.5rem;padding:.3rem .5rem;-webkit-transform:translateY(.4rem);transform:translateY(.4rem);border-radius:.1rem;background-color:rgba(0,0,0,.87);color:#fff;font-size:.64rem;opacity:0;overflow:hidden}.md-skip:focus{width:auto;height:auto;clip:auto;-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;z-index:10}@page{margin:25mm}.md-clipboard{position:absolute;top:.3rem;right:.3rem;width:1.4rem;height:1.4rem;border-radius:.1rem;font-size:.8rem;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-clipboard:before{transition:color .25s,opacity .25s;color:rgba(0,0,0,.07);content:"\E14D"}.codehilite:hover .md-clipboard:before,.md-typeset .highlight:hover .md-clipboard:before,pre:hover .md-clipboard:before{color:rgba(0,0,0,.54)}.md-clipboard:focus:before,.md-clipboard:hover:before{color:#536dfe}.md-clipboard__message{display:block;position:absolute;top:0;right:1.7rem;padding:.3rem .5rem;-webkit-transform:translateX(.4rem);transform:translateX(.4rem);transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s;transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);border-radius:.1rem;background-color:rgba(0,0,0,.54);color:#fff;font-size:.64rem;white-space:nowrap;opacity:0;pointer-events:none}.md-clipboard__message--active{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;pointer-events:auto}.md-clipboard__message:before{content:attr(aria-label)}.md-clipboard__message:after{display:block;position:absolute;top:50%;right:-.2rem;width:0;margin-top:-.2rem;border-color:transparent rgba(0,0,0,.54);border-style:solid;border-width:.2rem 0 .2rem .2rem;content:""}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__icon{position:relative;margin:.4rem 0;padding:0;float:right}.md-typeset .md-content__icon{color:rgba(0,0,0,.26)}.md-header{position:fixed;top:0;right:0;left:0;height:2.4rem;transition:background-color .25s,color .25s;background-color:#3f51b5;color:#fff;box-shadow:none;z-index:2;-webkit-backface-visibility:hidden;backface-visibility:hidden}.no-js .md-header{transition:none;box-shadow:none}.md-header[data-md-state=shadow]{transition:background-color .25s,color .25s,box-shadow .25s;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.md-header-nav{padding:0 .2rem}.md-header-nav__button{position:relative;transition:opacity .25s;z-index:1}.md-header-nav__button:hover{opacity:.7}.md-header-nav__button.md-logo *{display:block}.no-js .md-header-nav__button.md-icon--search{display:none}.md-header-nav__topic{display:block;position:absolute;transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem)}.no-js .md-header-nav__topic{position:static}.no-js .md-header-nav__topic+.md-header-nav__topic{display:none}.md-header-nav__title{padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.md-header-nav__title[data-md-state=active] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);opacity:1;z-index:0;pointer-events:auto}.md-header-nav__source{display:none}.md-hero{transition:background .25s;background-color:#3f51b5;color:#fff;font-size:1rem;overflow:hidden}.md-hero__inner{margin-top:1rem;padding:.8rem .8rem .4rem;transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition-delay:.1s}[data-md-state=hidden] .md-hero__inner{pointer-events:none;-webkit-transform:translateY(.625rem);transform:translateY(.625rem);transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:transform 0s .4s,opacity .1s 0s;transition:transform 0s .4s,opacity .1s 0s,-webkit-transform 0s .4s;opacity:0}.md-hero--expand .md-hero__inner{margin-bottom:1.2rem}.md-footer-nav{background-color:rgba(0,0,0,.87);color:#fff}.md-footer-nav__inner{padding:.2rem;overflow:auto}.md-footer-nav__link{padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}.md-footer-nav__link:hover{opacity:.7}.md-footer-nav__link--prev{width:25%;float:left}[dir=rtl] .md-footer-nav__link--prev{float:right}.md-footer-nav__link--next{width:75%;float:right;text-align:right}[dir=rtl] .md-footer-nav__link--next{float:left;text-align:left}.md-footer-nav__button{transition:background .25s}.md-footer-nav__title{position:relative;padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer-nav__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;color:hsla(0,0%,100%,.7);font-size:.75rem}.md-footer-meta{background-color:rgba(0,0,0,.895)}.md-footer-meta__inner{padding:.2rem;overflow:auto}html .md-footer-meta.md-typeset a{color:hsla(0,0%,100%,.7)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:#fff}.md-footer-copyright{margin:0 .6rem;padding:.4rem 0;color:hsla(0,0%,100%,.3);font-size:.64rem}.md-footer-copyright__highlight{color:hsla(0,0%,100%,.7)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;font-size:.8rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden}.md-nav__title:before{display:none;content:"\E5C4"}[dir=rtl] .md-nav__title:before{content:"\E5C8"}.md-nav__title .md-nav__button{display:none}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item:last-child{padding-bottom:.6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__item .md-nav__item:last-child{padding-bottom:0}.md-nav__button img{width:100%;height:auto}.md-nav__link{display:block;margin-top:.625em;transition:color .125s;text-overflow:ellipsis;cursor:pointer;overflow:hidden}.md-nav__item--nested>.md-nav__link:after{content:"\E313"}html .md-nav__link[for=__toc],html .md-nav__link[for=__toc]+.md-nav__link:after,html .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link[data-md-state=blur]{color:rgba(0,0,0,.54)}.md-nav__link--active,.md-nav__link:active{color:#3f51b5}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:#536dfe}.md-nav__source,.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}.md-search__form{position:relative}.md-search__input{position:relative;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;z-index:2}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input:-ms-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-ms-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-webkit-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input:-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-clear{display:none}.md-search__icon{position:absolute;transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;font-size:1.2rem;cursor:pointer;z-index:2}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}.md-search__icon[for=__search]:before{content:"\E8B6"}.md-search__icon[type=reset]{top:.3rem;right:.5rem;-webkit-transform:scale(.125);transform:scale(.125);transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);opacity:0}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{-webkit-transform:scale(1);transform:scale(1);opacity:1}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;width:100%;border-radius:0 0 .1rem .1rem;overflow:hidden;z-index:1}.md-search__scrollwrap{height:100%;background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07);overflow-y:auto;-webkit-overflow-scrolling:touch}.md-search-result{color:rgba(0,0,0,.87);word-break:break-word}.md-search-result__meta{padding:0 .8rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.8rem}.md-search-result__list{margin:0;padding:0;border-top:.05rem solid rgba(0,0,0,.07);list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 rgba(0,0,0,.07)}.md-search-result__link{display:block;transition:background .25s;outline:0;overflow:hidden}.md-search-result__link:hover,.md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}.md-search-result__link:hover .md-search-result__article:before,.md-search-result__link[data-md-state=active] .md-search-result__article:before{opacity:.7}.md-search-result__link:last-child .md-search-result__teaser{margin-bottom:.6rem}.md-search-result__article{position:relative;padding:0 .8rem;overflow:auto}.md-search-result__article--document:before{position:absolute;left:0;margin:.1rem;transition:opacity .25s;color:rgba(0,0,0,.54);content:"\E880"}[dir=rtl] .md-search-result__article--document:before{right:0;left:auto}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-size:.8rem;font-weight:400;line-height:1.4}.md-search-result__title{margin:.5em 0;font-size:.64rem;font-weight:700;line-height:1.4}.md-search-result__teaser{display:-webkit-box;max-height:1.65rem;margin:.5em 0;color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.4;text-overflow:ellipsis;overflow:hidden;-webkit-line-clamp:2}.md-search-result em{font-style:normal;font-weight:700;text-decoration:underline}.md-sidebar{position:absolute;width:12.1rem;padding:1.2rem 0;overflow:hidden}.md-sidebar[data-md-state=lock]{position:fixed;top:2.4rem}.md-sidebar--secondary{display:none}.md-sidebar__scrollwrap{max-height:100%;margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.md-source{display:block;padding-right:.6rem;transition:opacity .25s;font-size:.65rem;line-height:1.2;white-space:nowrap}[dir=rtl] .md-source{padding-right:0;padding-left:.6rem}.md-source:hover{opacity:.7}.md-source:after,.md-source__icon{display:inline-block;height:2.4rem;content:"";vertical-align:middle}.md-source__icon{width:2.4rem}.md-source__icon svg{width:1.2rem;height:1.2rem;margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2.2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2.2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:100%;margin-left:.6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden;vertical-align:middle}.md-source__facts{margin:0;padding:0;font-size:.55rem;font-weight:700;list-style-type:none;opacity:.75;overflow:hidden}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"\00B7"}.md-source__fact:first-child:before{display:none}.md-source-file{display:inline-block;margin:1em .5em 1em 0;padding-right:.25rem;border-radius:.1rem;background-color:rgba(0,0,0,.07);font-size:.64rem;list-style-type:none;cursor:pointer;overflow:hidden}.md-source-file:before{display:inline-block;margin-right:.25rem;padding:.25rem;background-color:rgba(0,0,0,.26);color:#fff;font-size:.8rem;content:"\E86F";vertical-align:middle}html .md-source-file{transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1)}html .md-source-file:before{transition:inherit}html body .md-typeset .md-source-file{color:rgba(0,0,0,.54)}.md-source-file:hover{box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36)}.md-source-file:hover:before{background-color:#536dfe}.md-tabs{width:100%;transition:background .25s;background-color:#3f51b5;color:#fff;overflow:auto}.md-tabs__list{margin:0 0 0 .2rem;padding:0;list-style:none;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);font-size:.7rem;opacity:.7}.md-tabs__link--active,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:.02s}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:.04s}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:.06s}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:.08s}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs[data-md-state=hidden] .md-tabs__link{-webkit-transform:translateY(50%);transform:translateY(50%);transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,transform 0s .4s,opacity .1s;transition:color .25s,transform 0s .4s,opacity .1s,-webkit-transform 0s .4s;opacity:0}.md-typeset .admonition,.md-typeset details{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin:1.5625em 0;padding:0 .6rem;border-left:.2rem solid #448aff;border-radius:.1rem;font-size:.64rem;overflow:auto}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition>.admonition-title,.md-typeset .admonition>summary,.md-typeset details>.admonition-title,.md-typeset details>summary{margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;border-bottom:.05rem solid rgba(68,138,255,.1);background-color:rgba(68,138,255,.1);font-weight:700}[dir=rtl] .md-typeset .admonition>.admonition-title,[dir=rtl] .md-typeset .admonition>summary,[dir=rtl] .md-typeset details>.admonition-title,[dir=rtl] .md-typeset details>summary{padding:.4rem 2rem .4rem .6rem}.md-typeset .admonition>.admonition-title:last-child,.md-typeset .admonition>summary:last-child,.md-typeset details>.admonition-title:last-child,.md-typeset details>summary:last-child{margin-bottom:0}.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before{position:absolute;left:.6rem;color:#448aff;font-size:1rem;content:"\E3C9"}[dir=rtl] .md-typeset .admonition>.admonition-title:before,[dir=rtl] .md-typeset .admonition>summary:before,[dir=rtl] .md-typeset details>.admonition-title:before,[dir=rtl] .md-typeset details>summary:before{right:.6rem;left:auto}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-left-color:#00b0ff}[dir=rtl] .md-typeset .admonition.abstract,[dir=rtl] .md-typeset .admonition.summary,[dir=rtl] .md-typeset .admonition.tldr,[dir=rtl] .md-typeset details.abstract,[dir=rtl] .md-typeset details.summary,[dir=rtl] .md-typeset details.tldr{border-right-color:#00b0ff}.md-typeset .admonition.abstract>.admonition-title,.md-typeset .admonition.abstract>summary,.md-typeset .admonition.summary>.admonition-title,.md-typeset .admonition.summary>summary,.md-typeset .admonition.tldr>.admonition-title,.md-typeset .admonition.tldr>summary,.md-typeset details.abstract>.admonition-title,.md-typeset details.abstract>summary,.md-typeset details.summary>.admonition-title,.md-typeset details.summary>summary,.md-typeset details.tldr>.admonition-title,.md-typeset details.tldr>summary{border-bottom-color:rgba(0,176,255,.1);background-color:rgba(0,176,255,.1)}.md-typeset .admonition.abstract>.admonition-title:before,.md-typeset .admonition.abstract>summary:before,.md-typeset .admonition.summary>.admonition-title:before,.md-typeset .admonition.summary>summary:before,.md-typeset .admonition.tldr>.admonition-title:before,.md-typeset .admonition.tldr>summary:before,.md-typeset details.abstract>.admonition-title:before,.md-typeset details.abstract>summary:before,.md-typeset details.summary>.admonition-title:before,.md-typeset details.summary>summary:before,.md-typeset details.tldr>.admonition-title:before,.md-typeset details.tldr>summary:before{color:#00b0ff;content:""}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-left-color:#00b8d4}[dir=rtl] .md-typeset .admonition.info,[dir=rtl] .md-typeset .admonition.todo,[dir=rtl] .md-typeset details.info,[dir=rtl] .md-typeset details.todo{border-right-color:#00b8d4}.md-typeset .admonition.info>.admonition-title,.md-typeset .admonition.info>summary,.md-typeset .admonition.todo>.admonition-title,.md-typeset .admonition.todo>summary,.md-typeset details.info>.admonition-title,.md-typeset details.info>summary,.md-typeset details.todo>.admonition-title,.md-typeset details.todo>summary{border-bottom-color:rgba(0,184,212,.1);background-color:rgba(0,184,212,.1)}.md-typeset .admonition.info>.admonition-title:before,.md-typeset .admonition.info>summary:before,.md-typeset .admonition.todo>.admonition-title:before,.md-typeset .admonition.todo>summary:before,.md-typeset details.info>.admonition-title:before,.md-typeset details.info>summary:before,.md-typeset details.todo>.admonition-title:before,.md-typeset details.todo>summary:before{color:#00b8d4;content:""}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-left-color:#00bfa5}[dir=rtl] .md-typeset .admonition.hint,[dir=rtl] .md-typeset .admonition.important,[dir=rtl] .md-typeset .admonition.tip,[dir=rtl] .md-typeset details.hint,[dir=rtl] .md-typeset details.important,[dir=rtl] .md-typeset details.tip{border-right-color:#00bfa5}.md-typeset .admonition.hint>.admonition-title,.md-typeset .admonition.hint>summary,.md-typeset .admonition.important>.admonition-title,.md-typeset .admonition.important>summary,.md-typeset .admonition.tip>.admonition-title,.md-typeset .admonition.tip>summary,.md-typeset details.hint>.admonition-title,.md-typeset details.hint>summary,.md-typeset details.important>.admonition-title,.md-typeset details.important>summary,.md-typeset details.tip>.admonition-title,.md-typeset details.tip>summary{border-bottom-color:rgba(0,191,165,.1);background-color:rgba(0,191,165,.1)}.md-typeset .admonition.hint>.admonition-title:before,.md-typeset .admonition.hint>summary:before,.md-typeset .admonition.important>.admonition-title:before,.md-typeset .admonition.important>summary:before,.md-typeset .admonition.tip>.admonition-title:before,.md-typeset .admonition.tip>summary:before,.md-typeset details.hint>.admonition-title:before,.md-typeset details.hint>summary:before,.md-typeset details.important>.admonition-title:before,.md-typeset details.important>summary:before,.md-typeset details.tip>.admonition-title:before,.md-typeset details.tip>summary:before{color:#00bfa5;content:""}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-left-color:#00c853}[dir=rtl] .md-typeset .admonition.check,[dir=rtl] .md-typeset .admonition.done,[dir=rtl] .md-typeset .admonition.success,[dir=rtl] .md-typeset details.check,[dir=rtl] .md-typeset details.done,[dir=rtl] .md-typeset details.success{border-right-color:#00c853}.md-typeset .admonition.check>.admonition-title,.md-typeset .admonition.check>summary,.md-typeset .admonition.done>.admonition-title,.md-typeset .admonition.done>summary,.md-typeset .admonition.success>.admonition-title,.md-typeset .admonition.success>summary,.md-typeset details.check>.admonition-title,.md-typeset details.check>summary,.md-typeset details.done>.admonition-title,.md-typeset details.done>summary,.md-typeset details.success>.admonition-title,.md-typeset details.success>summary{border-bottom-color:rgba(0,200,83,.1);background-color:rgba(0,200,83,.1)}.md-typeset .admonition.check>.admonition-title:before,.md-typeset .admonition.check>summary:before,.md-typeset .admonition.done>.admonition-title:before,.md-typeset .admonition.done>summary:before,.md-typeset .admonition.success>.admonition-title:before,.md-typeset .admonition.success>summary:before,.md-typeset details.check>.admonition-title:before,.md-typeset details.check>summary:before,.md-typeset details.done>.admonition-title:before,.md-typeset details.done>summary:before,.md-typeset details.success>.admonition-title:before,.md-typeset details.success>summary:before{color:#00c853;content:""}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-left-color:#64dd17}[dir=rtl] .md-typeset .admonition.faq,[dir=rtl] .md-typeset .admonition.help,[dir=rtl] .md-typeset .admonition.question,[dir=rtl] .md-typeset details.faq,[dir=rtl] .md-typeset details.help,[dir=rtl] .md-typeset details.question{border-right-color:#64dd17}.md-typeset .admonition.faq>.admonition-title,.md-typeset .admonition.faq>summary,.md-typeset .admonition.help>.admonition-title,.md-typeset .admonition.help>summary,.md-typeset .admonition.question>.admonition-title,.md-typeset .admonition.question>summary,.md-typeset details.faq>.admonition-title,.md-typeset details.faq>summary,.md-typeset details.help>.admonition-title,.md-typeset details.help>summary,.md-typeset details.question>.admonition-title,.md-typeset details.question>summary{border-bottom-color:rgba(100,221,23,.1);background-color:rgba(100,221,23,.1)}.md-typeset .admonition.faq>.admonition-title:before,.md-typeset .admonition.faq>summary:before,.md-typeset .admonition.help>.admonition-title:before,.md-typeset .admonition.help>summary:before,.md-typeset .admonition.question>.admonition-title:before,.md-typeset .admonition.question>summary:before,.md-typeset details.faq>.admonition-title:before,.md-typeset details.faq>summary:before,.md-typeset details.help>.admonition-title:before,.md-typeset details.help>summary:before,.md-typeset details.question>.admonition-title:before,.md-typeset details.question>summary:before{color:#64dd17;content:""}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-left-color:#ff9100}[dir=rtl] .md-typeset .admonition.attention,[dir=rtl] .md-typeset .admonition.caution,[dir=rtl] .md-typeset .admonition.warning,[dir=rtl] .md-typeset details.attention,[dir=rtl] .md-typeset details.caution,[dir=rtl] .md-typeset details.warning{border-right-color:#ff9100}.md-typeset .admonition.attention>.admonition-title,.md-typeset .admonition.attention>summary,.md-typeset .admonition.caution>.admonition-title,.md-typeset .admonition.caution>summary,.md-typeset .admonition.warning>.admonition-title,.md-typeset .admonition.warning>summary,.md-typeset details.attention>.admonition-title,.md-typeset details.attention>summary,.md-typeset details.caution>.admonition-title,.md-typeset details.caution>summary,.md-typeset details.warning>.admonition-title,.md-typeset details.warning>summary{border-bottom-color:rgba(255,145,0,.1);background-color:rgba(255,145,0,.1)}.md-typeset .admonition.attention>.admonition-title:before,.md-typeset .admonition.attention>summary:before,.md-typeset .admonition.caution>.admonition-title:before,.md-typeset .admonition.caution>summary:before,.md-typeset .admonition.warning>.admonition-title:before,.md-typeset .admonition.warning>summary:before,.md-typeset details.attention>.admonition-title:before,.md-typeset details.attention>summary:before,.md-typeset details.caution>.admonition-title:before,.md-typeset details.caution>summary:before,.md-typeset details.warning>.admonition-title:before,.md-typeset details.warning>summary:before{color:#ff9100;content:""}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-left-color:#ff5252}[dir=rtl] .md-typeset .admonition.fail,[dir=rtl] .md-typeset .admonition.failure,[dir=rtl] .md-typeset .admonition.missing,[dir=rtl] .md-typeset details.fail,[dir=rtl] .md-typeset details.failure,[dir=rtl] .md-typeset details.missing{border-right-color:#ff5252}.md-typeset .admonition.fail>.admonition-title,.md-typeset .admonition.fail>summary,.md-typeset .admonition.failure>.admonition-title,.md-typeset .admonition.failure>summary,.md-typeset .admonition.missing>.admonition-title,.md-typeset .admonition.missing>summary,.md-typeset details.fail>.admonition-title,.md-typeset details.fail>summary,.md-typeset details.failure>.admonition-title,.md-typeset details.failure>summary,.md-typeset details.missing>.admonition-title,.md-typeset details.missing>summary{border-bottom-color:rgba(255,82,82,.1);background-color:rgba(255,82,82,.1)}.md-typeset .admonition.fail>.admonition-title:before,.md-typeset .admonition.fail>summary:before,.md-typeset .admonition.failure>.admonition-title:before,.md-typeset .admonition.failure>summary:before,.md-typeset .admonition.missing>.admonition-title:before,.md-typeset .admonition.missing>summary:before,.md-typeset details.fail>.admonition-title:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>.admonition-title:before,.md-typeset details.failure>summary:before,.md-typeset details.missing>.admonition-title:before,.md-typeset details.missing>summary:before{color:#ff5252;content:""}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-left-color:#ff1744}[dir=rtl] .md-typeset .admonition.danger,[dir=rtl] .md-typeset .admonition.error,[dir=rtl] .md-typeset details.danger,[dir=rtl] .md-typeset details.error{border-right-color:#ff1744}.md-typeset .admonition.danger>.admonition-title,.md-typeset .admonition.danger>summary,.md-typeset .admonition.error>.admonition-title,.md-typeset .admonition.error>summary,.md-typeset details.danger>.admonition-title,.md-typeset details.danger>summary,.md-typeset details.error>.admonition-title,.md-typeset details.error>summary{border-bottom-color:rgba(255,23,68,.1);background-color:rgba(255,23,68,.1)}.md-typeset .admonition.danger>.admonition-title:before,.md-typeset .admonition.danger>summary:before,.md-typeset .admonition.error>.admonition-title:before,.md-typeset .admonition.error>summary:before,.md-typeset details.danger>.admonition-title:before,.md-typeset details.danger>summary:before,.md-typeset details.error>.admonition-title:before,.md-typeset details.error>summary:before{color:#ff1744;content:""}.md-typeset .admonition.bug,.md-typeset details.bug{border-left-color:#f50057}[dir=rtl] .md-typeset .admonition.bug,[dir=rtl] .md-typeset details.bug{border-right-color:#f50057}.md-typeset .admonition.bug>.admonition-title,.md-typeset .admonition.bug>summary,.md-typeset details.bug>.admonition-title,.md-typeset details.bug>summary{border-bottom-color:rgba(245,0,87,.1);background-color:rgba(245,0,87,.1)}.md-typeset .admonition.bug>.admonition-title:before,.md-typeset .admonition.bug>summary:before,.md-typeset details.bug>.admonition-title:before,.md-typeset details.bug>summary:before{color:#f50057;content:""}.md-typeset .admonition.example,.md-typeset details.example{border-left-color:#651fff}[dir=rtl] .md-typeset .admonition.example,[dir=rtl] .md-typeset details.example{border-right-color:#651fff}.md-typeset .admonition.example>.admonition-title,.md-typeset .admonition.example>summary,.md-typeset details.example>.admonition-title,.md-typeset details.example>summary{border-bottom-color:rgba(101,31,255,.1);background-color:rgba(101,31,255,.1)}.md-typeset .admonition.example>.admonition-title:before,.md-typeset .admonition.example>summary:before,.md-typeset details.example>.admonition-title:before,.md-typeset details.example>summary:before{color:#651fff;content:""}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-left-color:#9e9e9e}[dir=rtl] .md-typeset .admonition.cite,[dir=rtl] .md-typeset .admonition.quote,[dir=rtl] .md-typeset details.cite,[dir=rtl] .md-typeset details.quote{border-right-color:#9e9e9e}.md-typeset .admonition.cite>.admonition-title,.md-typeset .admonition.cite>summary,.md-typeset .admonition.quote>.admonition-title,.md-typeset .admonition.quote>summary,.md-typeset details.cite>.admonition-title,.md-typeset details.cite>summary,.md-typeset details.quote>.admonition-title,.md-typeset details.quote>summary{border-bottom-color:hsla(0,0%,62%,.1);background-color:hsla(0,0%,62%,.1)}.md-typeset .admonition.cite>.admonition-title:before,.md-typeset .admonition.cite>summary:before,.md-typeset .admonition.quote>.admonition-title:before,.md-typeset .admonition.quote>summary:before,.md-typeset details.cite>.admonition-title:before,.md-typeset details.cite>summary:before,.md-typeset details.quote>.admonition-title:before,.md-typeset details.quote>summary:before{color:#9e9e9e;content:""}.codehilite .o,.codehilite .ow,.md-typeset .highlight .o,.md-typeset .highlight .ow{color:inherit}.codehilite .ge,.md-typeset .highlight .ge{color:#000}.codehilite .gr,.md-typeset .highlight .gr{color:#a00}.codehilite .gh,.md-typeset .highlight .gh{color:#999}.codehilite .go,.md-typeset .highlight .go{color:#888}.codehilite .gp,.md-typeset .highlight .gp{color:#555}.codehilite .gs,.md-typeset .highlight .gs{color:inherit}.codehilite .gu,.md-typeset .highlight .gu{color:#aaa}.codehilite .gt,.md-typeset .highlight .gt{color:#a00}.codehilite .gd,.md-typeset .highlight .gd{background-color:#fdd}.codehilite .gi,.md-typeset .highlight .gi{background-color:#dfd}.codehilite .k,.md-typeset .highlight .k{color:#3b78e7}.codehilite .kc,.md-typeset .highlight .kc{color:#a71d5d}.codehilite .kd,.codehilite .kn,.md-typeset .highlight .kd,.md-typeset .highlight .kn{color:#3b78e7}.codehilite .kp,.md-typeset .highlight .kp{color:#a71d5d}.codehilite .kr,.codehilite .kt,.md-typeset .highlight .kr,.md-typeset .highlight .kt{color:#3e61a2}.codehilite .c,.codehilite .cm,.md-typeset .highlight .c,.md-typeset .highlight .cm{color:#999}.codehilite .cp,.md-typeset .highlight .cp{color:#666}.codehilite .c1,.codehilite .ch,.codehilite .cs,.md-typeset .highlight .c1,.md-typeset .highlight .ch,.md-typeset .highlight .cs{color:#999}.codehilite .na,.codehilite .nb,.md-typeset .highlight .na,.md-typeset .highlight .nb{color:#c2185b}.codehilite .bp,.md-typeset .highlight .bp{color:#3e61a2}.codehilite .nc,.md-typeset .highlight .nc{color:#c2185b}.codehilite .no,.md-typeset .highlight .no{color:#3e61a2}.codehilite .nd,.codehilite .ni,.md-typeset .highlight .nd,.md-typeset .highlight .ni{color:#666}.codehilite .ne,.codehilite .nf,.md-typeset .highlight .ne,.md-typeset .highlight .nf{color:#c2185b}.codehilite .nl,.md-typeset .highlight .nl{color:#3b5179}.codehilite .nn,.md-typeset .highlight .nn{color:#ec407a}.codehilite .nt,.md-typeset .highlight .nt{color:#3b78e7}.codehilite .nv,.codehilite .vc,.codehilite .vg,.codehilite .vi,.md-typeset .highlight .nv,.md-typeset .highlight .vc,.md-typeset .highlight .vg,.md-typeset .highlight .vi{color:#3e61a2}.codehilite .nx,.md-typeset .highlight .nx{color:#ec407a}.codehilite .il,.codehilite .m,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.md-typeset .highlight .il,.md-typeset .highlight .m,.md-typeset .highlight .mf,.md-typeset .highlight .mh,.md-typeset .highlight .mi,.md-typeset .highlight .mo{color:#e74c3c}.codehilite .s,.codehilite .sb,.codehilite .sc,.md-typeset .highlight .s,.md-typeset .highlight .sb,.md-typeset .highlight .sc{color:#0d904f}.codehilite .sd,.md-typeset .highlight .sd{color:#999}.codehilite .s2,.md-typeset .highlight .s2{color:#0d904f}.codehilite .se,.codehilite .sh,.codehilite .si,.codehilite .sx,.md-typeset .highlight .se,.md-typeset .highlight .sh,.md-typeset .highlight .si,.md-typeset .highlight .sx{color:#183691}.codehilite .sr,.md-typeset .highlight .sr{color:#009926}.codehilite .s1,.codehilite .ss,.md-typeset .highlight .s1,.md-typeset .highlight .ss{color:#0d904f}.codehilite .err,.md-typeset .highlight .err{color:#a61717}.codehilite .w,.md-typeset .highlight .w{color:transparent}.codehilite .hll,.md-typeset .highlight .hll{display:block;margin:0 -.6rem;padding:0 .6rem;background-color:rgba(255,235,59,.5)}.md-typeset .codehilite,.md-typeset .highlight{position:relative;margin:1em 0;padding:0;border-radius:.1rem;background-color:hsla(0,0%,92.5%,.5);color:#37474f;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset .codehilite code,.md-typeset .codehilite pre,.md-typeset .highlight code,.md-typeset .highlight pre{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;overflow:auto;vertical-align:top}.md-typeset .codehilite code::-webkit-scrollbar,.md-typeset .codehilite pre::-webkit-scrollbar,.md-typeset .highlight code::-webkit-scrollbar,.md-typeset .highlight pre::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset .codehilite code::-webkit-scrollbar-thumb,.md-typeset .codehilite pre::-webkit-scrollbar-thumb,.md-typeset .highlight code::-webkit-scrollbar-thumb,.md-typeset .highlight pre::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset .codehilite code::-webkit-scrollbar-thumb:hover,.md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,.md-typeset .highlight code::-webkit-scrollbar-thumb:hover,.md-typeset .highlight pre::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset pre.codehilite,.md-typeset pre.highlight{overflow:visible}.md-typeset pre.codehilite code,.md-typeset pre.highlight code{display:block;padding:.525rem .6rem;overflow:auto}.md-typeset .codehilitetable,.md-typeset .highlighttable{display:block;margin:1em 0;border-radius:.2em;font-size:.8rem;overflow:hidden}.md-typeset .codehilitetable tbody,.md-typeset .codehilitetable td,.md-typeset .highlighttable tbody,.md-typeset .highlighttable td{display:block;padding:0}.md-typeset .codehilitetable tr,.md-typeset .highlighttable tr{display:flex}.md-typeset .codehilitetable .codehilite,.md-typeset .codehilitetable .highlight,.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .codehilite,.md-typeset .highlighttable .highlight,.md-typeset .highlighttable .linenodiv{margin:0;border-radius:0}.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .linenodiv{padding:.525rem .6rem}.md-typeset .codehilitetable .linenos,.md-typeset .highlighttable .linenos{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.26);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset .codehilitetable .linenos pre,.md-typeset .highlighttable .linenos pre{margin:0;padding:0;background-color:transparent;color:inherit;text-align:right}.md-typeset .codehilitetable .code,.md-typeset .highlighttable .code{flex:1;overflow:hidden}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{box-shadow:none}.md-typeset [id^="fnref:"]{display:inline-block}.md-typeset [id^="fnref:"]:target{margin-top:-3.8rem;padding-top:3.8rem;pointer-events:none}.md-typeset [id^="fn:"]:before{display:none;height:0;content:""}.md-typeset [id^="fn:"]:target:before{display:block;margin-top:-3.5rem;padding-top:3.5rem;pointer-events:none}.md-typeset .footnote{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{transition:color .25s}.md-typeset .footnote li:target{color:rgba(0,0,0,.87)}.md-typeset .footnote li :first-child{margin-top:0}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}.md-typeset .footnote li:hover .footnote-backref:hover,.md-typeset .footnote li:target .footnote-backref{color:#536dfe}.md-typeset .footnote-ref{display:inline-block;pointer-events:auto}.md-typeset .footnote-ref:before{display:inline;margin:0 .2em;border-left:.05rem solid rgba(0,0,0,.26);font-size:1.25em;content:"";vertical-align:-.25rem}.md-typeset .footnote-backref{display:inline-block;-webkit-transform:translateX(.25rem);transform:translateX(.25rem);transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s,-webkit-transform .25s .125s;color:rgba(0,0,0,.26);font-size:0;opacity:0;vertical-align:text-bottom}[dir=rtl] .md-typeset .footnote-backref{-webkit-transform:translateX(-.25rem);transform:translateX(-.25rem)}.md-typeset .footnote-backref:before{display:inline-block;font-size:.8rem;content:"\E31B"}[dir=rtl] .md-typeset .footnote-backref:before{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;-webkit-transform:translateY(.25rem);transform:translateY(.25rem);transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s,-webkit-transform .25s .25s;opacity:0}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}html body .md-typeset .headerlink{color:rgba(0,0,0,.26)}.md-typeset h1[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h1[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink,.md-typeset h1[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink:hover,.md-typeset h1[id]:target .headerlink{color:#536dfe}.md-typeset h2[id]:before{display:block;margin-top:-8px;padding-top:8px;content:""}.md-typeset h2[id]:target:before{margin-top:-3.4rem;padding-top:3.4rem}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink,.md-typeset h2[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink:hover,.md-typeset h2[id]:target .headerlink{color:#536dfe}.md-typeset h3[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h3[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink,.md-typeset h3[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink:hover,.md-typeset h3[id]:target .headerlink{color:#536dfe}.md-typeset h4[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h4[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink,.md-typeset h4[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink:hover,.md-typeset h4[id]:target .headerlink{color:#536dfe}.md-typeset h5[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h5[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink,.md-typeset h5[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink:hover,.md-typeset h5[id]:target .headerlink{color:#536dfe}.md-typeset h6[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h6[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink,.md-typeset h6[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink:hover,.md-typeset h6[id]:target .headerlink{color:#536dfe}.md-typeset .MJXc-display{margin:.75em 0;padding:.75em 0;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset .MathJax_CHTML{outline:0}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:#fdd;box-shadow:.25em 0 0 #fdd,-.25em 0 0 #fdd}.md-typeset ins.critic{background-color:#dfd;box-shadow:.25em 0 0 #dfd,-.25em 0 0 #dfd}.md-typeset .critic.comment{background-color:hsla(0,0%,92.5%,.5);color:#37474f;box-shadow:.25em 0 0 hsla(0,0%,92.5%,.5),-.25em 0 0 hsla(0,0%,92.5%,.5)}.md-typeset .critic.comment:before{padding-right:.125em;color:rgba(0,0,0,.26);content:"\E0B7";vertical-align:-.125em}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;box-shadow:none}.md-typeset .critic.block :first-child{margin-top:.5em}.md-typeset .critic.block :last-child{margin-bottom:.5em}.md-typeset details{display:block;padding-top:0}.md-typeset details[open]>summary:after{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.md-typeset details:not([open]){padding-bottom:0}.md-typeset details:not([open])>summary{border-bottom:none}.md-typeset details summary{padding-right:2rem}[dir=rtl] .md-typeset details summary{padding-left:2rem}.no-details .md-typeset details:not([open])>*{display:none}.no-details .md-typeset details:not([open]) summary{display:block}.md-typeset summary{display:block;outline:none;cursor:pointer}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset summary:after{position:absolute;top:.4rem;right:.6rem;color:rgba(0,0,0,.26);font-size:1rem;content:"\E313"}[dir=rtl] .md-typeset summary:after{right:auto;left:.6rem}.md-typeset .emojione{width:1rem;vertical-align:text-top}.md-typeset code.codehilite,.md-typeset code.highlight{margin:0 .29412em;padding:.07353em 0}.md-typeset .superfences-content{display:none;order:99;width:100%;background-color:#fff}.md-typeset .superfences-content>*{margin:0;border-radius:0}.md-typeset .superfences-tabs{display:flex;position:relative;flex-wrap:wrap;margin:1em 0;border:.05rem solid rgba(0,0,0,.07);border-radius:.2em}.md-typeset .superfences-tabs>input{display:none}.md-typeset .superfences-tabs>input:checked+label{font-weight:700}.md-typeset .superfences-tabs>input:checked+label+.superfences-content{display:block}.md-typeset .superfences-tabs>label{width:auto;padding:.6rem;transition:color .125s;font-size:.64rem;cursor:pointer}html .md-typeset .superfences-tabs>label:hover{color:#536dfe}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control .task-list-indicator:before{position:absolute;top:.15em;left:-1.25em;color:rgba(0,0,0,.26);font-size:1.25em;content:"\E835";vertical-align:-.25em}[dir=rtl] .md-typeset .task-list-control .task-list-indicator:before{right:-1.25em;left:auto}.md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator:before{content:"\E834"}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}@media print{.md-typeset a:after{color:rgba(0,0,0,.54);content:" [" attr(href) "]"}.md-typeset code,.md-typeset pre{white-space:pre-wrap}.md-typeset code{box-shadow:none;-webkit-box-decoration-break:initial;box-decoration-break:slice}.md-clipboard,.md-content__icon,.md-footer,.md-header,.md-sidebar,.md-tabs,.md-typeset .headerlink{display:none}}@media only screen and (max-width:44.9375em){.md-typeset pre{margin:1em -.8rem;border-radius:0}.md-typeset pre>code{padding:.525rem .8rem}.md-footer-nav__link--prev .md-footer-nav__title{display:none}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}.codehilite .hll,.md-typeset .highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.codehilite,.md-typeset>.highlight{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilite code,.md-typeset>.codehilite pre,.md-typeset>.highlight code,.md-typeset>.highlight pre{padding:.525rem .8rem}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilitetable .codehilite>code,.md-typeset>.codehilitetable .codehilite>pre,.md-typeset>.codehilitetable .highlight>code,.md-typeset>.codehilitetable .highlight>pre,.md-typeset>.codehilitetable .linenodiv,.md-typeset>.highlighttable .codehilite>code,.md-typeset>.highlighttable .codehilite>pre,.md-typeset>.highlighttable .highlight>code,.md-typeset>.highlighttable .highlight>pre,.md-typeset>.highlighttable .linenodiv{padding:.5rem .8rem}.md-typeset>p>.MJXc-display{margin:.75em -.8rem;padding:.25em .8rem}.md-typeset>.superfences-tabs{margin:1em -.8rem;border:0;border-top:.05rem solid rgba(0,0,0,.07);border-radius:0}.md-typeset>.superfences-tabs code,.md-typeset>.superfences-tabs pre{padding:.525rem .8rem}}@media only screen and (min-width:100em){html{font-size:137.5%}}@media only screen and (min-width:125em){html{font-size:150%}}@media only screen and (max-width:59.9375em){body[data-md-state=lock]{overflow:hidden}.ios body[data-md-state=lock] .md-container{display:none}html .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}html .md-nav__link[for=__toc]:after{color:inherit;content:"\E8DE"}html .md-nav__link[for=__toc]+.md-nav__link{display:none}html .md-nav__link[for=__toc]~.md-nav{display:flex}html [dir=rtl] .md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav__source{display:block;padding:0 .2rem;background-color:rgba(50,64,144,.9675);color:#fff}.md-search__overlay{position:absolute;top:.2rem;left:.2rem;width:1.8rem;height:1.8rem;-webkit-transform-origin:center;transform-origin:center;transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:transform .3s .1s,opacity .2s .2s;transition:transform .3s .1s,opacity .2s .2s,-webkit-transform .3s .1s;border-radius:1rem;background-color:#fff;overflow:hidden;pointer-events:none}[dir=rtl] .md-search__overlay{right:.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{transition:opacity .1s,-webkit-transform .4s;transition:transform .4s,opacity .1s;transition:transform .4s,opacity .1s,-webkit-transform .4s;opacity:1}.md-search__inner{position:fixed;top:0;left:100%;width:100%;height:100%;-webkit-transform:translateX(5%);transform:translateX(5%);transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;opacity:0;z-index:2}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;-webkit-transform:translateX(0);transform:translateX(0);transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;opacity:1}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;-webkit-transform:translateX(-5%);transform:translateX(-5%)}.md-search__input{width:100%;height:2.4rem;font-size:.9rem}.md-search__icon[for=__search]{top:.6rem;left:.8rem}.md-search__icon[for=__search][for=__search]:before{content:"\E5C4"}[dir=rtl] .md-search__icon[for=__search][for=__search]:before{content:"\E5C8"}.md-search__icon[type=reset]{top:.6rem;right:.8rem}.md-search__output{top:2.4rem;bottom:0}.md-search-result__article--document:before{display:none}}@media only screen and (max-width:76.1875em){[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-header-nav__button.md-icon--home,.md-header-nav__button.md-logo{display:none}.md-hero__inner{margin-top:2.4rem;margin-bottom:1.2rem}.md-nav{background-color:#fff}.md-nav--primary,.md-nav--primary .md-nav{display:flex;position:absolute;top:0;right:0;left:0;flex-direction:column;height:100%;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}html .md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-weight:400;line-height:2.4rem;white-space:nowrap;cursor:pointer}html .md-nav--primary .md-nav__title:before{display:block;position:absolute;top:.2rem;left:.2rem;width:2rem;height:2rem;color:rgba(0,0,0,.54)}html .md-nav--primary .md-nav__title~.md-nav__list{background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07)}html .md-nav--primary .md-nav__title~.md-nav__list>.md-nav__item:first-child{border-top:0}html .md-nav--primary .md-nav__title--site{position:relative;background-color:#3f51b5;color:#fff}html .md-nav--primary .md-nav__title--site .md-nav__button{display:block;position:absolute;top:.2rem;left:.2rem;width:3.2rem;height:3.2rem;font-size:2.4rem}html .md-nav--primary .md-nav__title--site:before{display:none}html [dir=rtl] .md-nav--primary .md-nav__title--site .md-nav__button,html [dir=rtl] .md-nav--primary .md-nav__title:before{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1;overflow-y:auto}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid rgba(0,0,0,.07)}[dir=rtl] .md-nav--primary .md-nav__item{padding:0}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E315"}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E314"}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link:after{position:absolute;top:50%;right:.6rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link:after{right:auto;left:.6rem}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:transparent}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav__toggle~.md-nav{display:flex;-webkit-transform:translateX(100%);transform:translateX(100%);transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);opacity:0}[dir=rtl] .md-nav__toggle~.md-nav{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.no-csstransforms3d .md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1}.no-csstransforms3d .md-nav__toggle:checked~.md-nav{display:flex}.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;width:12.1rem;height:100%;-webkit-transform:translateX(0);transform:translateX(0);transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);background-color:#fff;z-index:3}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}.no-csstransforms3d .md-sidebar--primary{display:none}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);-webkit-transform:translateX(12.1rem);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{-webkit-transform:translateX(-12.1rem);transform:translateX(-12.1rem)}.no-csstransforms3d [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{display:block}.md-sidebar--primary .md-sidebar__scrollwrap{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;margin:0}.md-tabs{display:none}}@media only screen and (min-width:60em){.md-content{margin-right:12.1rem}[dir=rtl] .md-content{margin-right:0;margin-left:12.1rem}.md-header-nav__button.md-icon--search{display:none}.md-header-nav__source{display:block;width:11.5rem;max-width:11.5rem;padding-right:.6rem}[dir=rtl] .md-header-nav__source{padding-right:0;padding-left:.6rem}.md-search{padding:.2rem}.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);cursor:pointer}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:100%;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-search__inner{position:relative;width:11.5rem;margin-right:1rem;padding:.1rem 0;float:right;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{margin-right:0;margin-left:1rem;float:left}.md-search__form,.md-search__input{border-radius:.1rem}.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);background-color:rgba(0,0,0,.26);color:inherit;font-size:.8rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:inherit}.md-search__input::-webkit-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{border-radius:.1rem .1rem 0 0;background-color:#fff;color:rgba(0,0,0,.87);text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:rgba(0,0,0,.54)}.md-search__output{top:1.9rem;transition:opacity .4s;opacity:0}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}.md-search__scrollwrap{max-height:0}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}.md-sidebar--secondary{display:block;margin-left:100%;-webkit-transform:translate(-100%);transform:translate(-100%)}[dir=rtl] .md-sidebar--secondary{margin-right:100%;margin-left:0;-webkit-transform:translate(100%);transform:translate(100%)}}@media only screen and (min-width:76.25em){.md-content{margin-left:12.1rem}[dir=rtl] .md-content{margin-right:12.1rem}.md-content__inner{margin-right:1.2rem;margin-left:1.2rem}.md-header-nav__button.md-icon--menu{display:none}.md-nav[data-md-state=animate]{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav__toggle~.md-nav{max-height:0;overflow:hidden}.no-js .md-nav__toggle~.md-nav{display:none}.md-nav[data-md-state=expand],.md-nav__toggle:checked~.md-nav{max-height:100%}.no-js .md-nav[data-md-state=expand],.no-js .md-nav__toggle:checked~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--nested>.md-nav__link:after{display:inline-block;-webkit-transform-origin:.45em .45em;transform-origin:.45em .45em;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;vertical-align:-.125em}.js .md-nav__item--nested>.md-nav__link:after{transition:-webkit-transform .4s;transition:transform .4s;transition:transform .4s,-webkit-transform .4s}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link:after{-webkit-transform:rotateX(180deg);transform:rotateX(180deg)}.md-search__inner{margin-right:1.4rem}[dir=rtl] .md-search__inner{margin-left:1.4rem}.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}.md-sidebar--secondary{margin-left:61rem}[dir=rtl] .md-sidebar--secondary{margin-right:61rem;margin-left:0}.md-tabs~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary .md-nav__title{display:block;padding:0}.md-tabs--active~.md-main .md-nav--primary .md-nav__title--site{display:none}.no-js .md-tabs--active~.md-main .md-nav--primary .md-nav{display:block}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{display:none;font-size:.7rem;overflow:auto;visibility:visible}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested>.md-nav__link{display:none}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--active{display:block}.md-tabs--active~.md-main .md-nav[data-md-level="1"]{max-height:none;overflow:visible}.md-tabs--active~.md-main .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:0}.md-tabs--active~.md-main .md-nav[data-md-level="1"] .md-nav .md-nav__title{display:none}}@media only screen and (min-width:45em){.md-footer-nav__link{width:50%}.md-footer-copyright{max-width:75%;float:left}[dir=rtl] .md-footer-copyright{float:right}.md-footer-social{padding:.6rem 0;float:right}[dir=rtl] .md-footer-social{float:left}}@media only screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(45);transform:scale(45)}}@media only screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(60);transform:scale(60)}}@media only screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(75);transform:scale(75)}}@media only screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}} \ No newline at end of file diff --git a/docs/convert/index.html b/docs/convert/index.html deleted file mode 100644 index 06db1c82..00000000 --- a/docs/convert/index.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Convert - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Convert

        - -

        Yaml to Json

        -

        To convert output to json, use the --tojson (or -j) flag. This is supported by all commands.

        -

        Each matching yaml node will be converted to json and printed out on a separate line.

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -
        - -

        then

        -
        yq r -j sample.yaml
        -
        - -

        will output

        -
        {"b":{"c":2}}
        -
        - -

        Given a sample.yaml file of:

        -
        bob:
        -  c: 2
        -bab:
        -  c: 5
        -
        - -

        then

        -
        yq r -j sample.yaml b*
        -
        - -

        will output

        -
        {"c":2}
        -{"c":5}
        -
        - -

        Json to Yaml

        -

        To read in json, just pass in a json file instead of yaml, it will just work :)

        -

        e.g given a json file

        -
        {"a":"Easy! as one two three","b":{"c":2,"d":[3,4]}}
        -
        - -

        then

        -
        yq r sample.json
        -
        - -

        will output

        -
        a: Easy! as one two three
        -b:
        -  c: 2
        -  d:
        -  - 3
        -  - 4
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/create/index.html b/docs/create/index.html deleted file mode 100644 index 550fc9c9..00000000 --- a/docs/create/index.html +++ /dev/null @@ -1,562 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Create

        - -
        yq n <path_expression> <new value>
        -
        - -

        Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.

        -

        See docs for path expression

        -

        Creating a simple yaml file

        -
        yq n b.c cat
        -
        - -

        will output:

        -
        b:
        -  c: cat
        -
        - -

        Creating using a create script

        -

        Create scripts follow the same format as the update scripts.

        -

        Given a script create_instructions.yaml of:

        -
        - command: update 
        -  path: b.c
        -  value:
        -    #great 
        -    things: frog # wow!
        -
        - -

        then

        -
        yq n -s create_instructions.yaml
        -
        - -

        will output:

        -
        b:
        -  c:
        -    #great
        -    things: frog # wow!
        -
        - -

        You can also pipe the instructions in:

        -
        cat create_instructions.yaml | yq n -s -
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/delete/index.html b/docs/delete/index.html deleted file mode 100644 index 87a60ee4..00000000 --- a/docs/delete/index.html +++ /dev/null @@ -1,666 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Delete - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Delete

        - -
        yq delete <yaml_file|-> <path_expression>
        -
        - -

        The delete command will delete all the matching nodes for the path expression in the given yaml input.

        -

        See docs for path expression for more details.

        -

        Deleting from a simple document

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -  apples: green
        -
        - -

        then

        -
        yq d sample.yaml b.c
        -
        - -

        will output

        -
        b:
        -  apples: green
        -
        - -

        From STDIN

        -

        Use "-" (without quotes) in-place of a file name if you wish to pipe in input from STDIN.

        -
        cat sample.yaml | yq d - b.c
        -
        - -

        Deleting in-place

        -
        yq d -i sample.yaml b.c
        -
        - -

        will update the sample.yaml file so that the 'c' node is deleted

        -

        Multiple Documents

        -

        Delete from single document

        -

        Given a sample.yaml file of:

        -
        something: else
        -field: leaveMe
        ----
        -b:
        -  c: 2
        -field: deleteMe
        -
        - -

        then

        -
        yq w -d1 sample.yaml field
        -
        - -

        will output:

        -
        something: else
        -field: leaveMe
        ----
        -b:
        -  c: 2
        -
        - -

        Delete from all documents

        -

        Given a sample.yaml file of:

        -
        something: else
        -field: deleteMe
        ----
        -b:
        -  c: 2
        -field: deleteMeToo
        -
        - -

        then

        -
        yq w -d'*' sample.yaml field
        -
        - -

        will output:

        -
        something: else
        ----
        -b:
        -  c: 2
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index af5bfefc..e66dceed 100644 --- a/docs/index.html +++ b/docs/index.html @@ -32,7 +32,7 @@ - + @@ -40,17 +40,17 @@ - + - + - + @@ -69,24 +69,7 @@ - - - + @@ -94,7 +77,7 @@ - + Skip to content @@ -118,9 +101,7 @@ Yq - - Install - + @@ -128,28 +109,6 @@ - @@ -184,275 +143,25 @@ -
        +
        -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        +
        - +

        New documentation website

        -

        yq

        -

        yq is a lightweight and portable command-line YAML processor

        -

        The aim of the project is to be the jq or sed of yaml files.

        -

        Install

        -

        On MacOS:

        -
        brew install yq
        -
        - -

        On Ubuntu and other Linux distros supporting snap packages:

        -
        snap install yq
        -
        - -

        On Ubuntu 16.04 or higher from Debian package:

        -
        sudo add-apt-repository ppa:rmescandon/yq
        -sudo apt update
        -sudo apt install yq -y
        -
        - -

        or, Download latest binary or alternatively:

        -
        GO111MODULE=on go get github.com/mikefarah/yq/v3
        -
        - -

        View on GitHub

        +

        User docs are better than ever, and have been moved here

        + @@ -467,27 +176,6 @@ sudo apt install yq -y
        - + diff --git a/docs/merge/index.html b/docs/merge/index.html deleted file mode 100644 index 23e88afe..00000000 --- a/docs/merge/index.html +++ /dev/null @@ -1,780 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Merge - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        - -
        -
        - - -
        -
        - - - - - -

        Merge

        - -

        Yaml files can be merged using the 'merge' command. Each additional file merged with the first file will -set values for any key not existing already or where the key has no value.

        -
        yq m <yaml_file> <path>...
        -
        - -

        Merge example

        -

        Given a data1.yaml file of:

        -
        a: simple
        -b: [1, 2]
        -
        - -

        and data2.yaml file of:

        -
        a: other
        -c:
        -  test: 1
        -
        - -

        then

        -
        yq m data1.yaml data2.yaml
        -
        - -

        will output:

        -
        a: simple
        -b: [1, 2]
        -c:
        -  test: 1
        -
        - -

        Updating files in-place

        -
        yq m -i data1.yaml data2.yaml
        -
        - -

        will update the data1.yaml file with the merged result.

        -

        Overwrite values

        -

        Given a data1.yaml file of:

        -
        a: simple
        -b: [1, 2]
        -
        - -

        and data2.yaml file of:

        -
        a: other
        -c:
        -  test: 1
        -
        - -

        then

        -
        yq m -x data1.yaml data2.yaml
        -
        - -

        will output:

        -
        a: other
        -b: [1, 2]
        -c:
        -  test: 1
        -
        - -

        Overwrite values with arrays

        -

        Given a data1.yaml file of:

        -
        a: simple
        -b: [1, 2]
        -
        - -

        and data3.yaml file of:

        -
        b: [3, 4]
        -c:
        -  test: 2
        -  other: true
        -d: false
        -
        - -

        then

        -
        yq m -x data1.yaml data3.yaml
        -
        - -

        will output:

        -
        a: simple
        -b: [3, 4]
        -c:
        -  test: 2
        -  other: true
        -d: false
        -
        - -

        Notice that 'b' does not result in the merging of the values within an array.

        -

        Append values with arrays

        -

        Given a data1.yaml file of:

        -
        a: simple
        -b: [1, 2]
        -d: hi
        -
        - -

        and data3.yaml file of:

        -
        a: something
        -b: [3, 4]
        -c:
        -  test: 2
        -  other: true
        -
        - -

        then

        -
        yq m -a data1.yaml data3.yaml
        -
        - -

        will output:

        -
        a: simple
        -b: [1, 2, 3, 4]
        -c:
        -  test: 2
        -  other: true
        -d: hi
        -
        - -

        Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).

        -

        Multiple Documents

        -

        Merge into single document

        -

        Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected.

        -

        Given a data1.yaml file of:

        -
        something: else
        ----
        -a: simple
        -b: cat
        -
        - -

        and data3.yaml file of:

        -
        b: dog
        -
        - -

        then

        -
        yq m -x -d1 data1.yaml data3.yaml
        -
        - -

        will output:

        -
        something: else
        ----
        -a: simple
        -b: dog
        -
        - -

        Merge into all documents

        -

        Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected.

        -

        Given a data1.yaml file of:

        -
        something: else
        ----
        -a: simple
        -b: cat
        -
        - -

        and data3.yaml file of:

        -
        b: dog
        -
        - -

        then

        -
        yq m -x -d'*' data1.yaml data3.yaml
        -
        - -

        will output:

        -
        b: dog
        -something: else
        ----
        -a: simple
        -b: dog
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/path_expressions/index.html b/docs/path_expressions/index.html deleted file mode 100644 index 4f5c3105..00000000 --- a/docs/path_expressions/index.html +++ /dev/null @@ -1,924 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path Expressions - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Path Expressions

        - -

        Path expressions are used to deeply navigate and match particular yaml nodes.

        -

        As a general rule, you should wrap paths in quotes to prevent your CLI from processing '*, []' and other special characters.

        -

        Simple expressions

        -

        Maps

        -

        a.b.c

        -
        a:
        -  b:
        -    c: thing # MATCHES
        -
        - -

        Arrays

        -

        a.b[1].c

        -
        a:
        -  b:
        -  - c: thing0 
        -  - c: thing1 # MATCHES
        -  - c: thing2
        -
        - -

        Appending to arrays

        -

        (e.g. when using the write command)

        -

        a.b[+].c

        -
        a:
        -  b:
        -  - c: thing0 
        -
        - -

        Will add a new entry:

        -
        a:
        -  b:
        -  - c: thing0 
        -  - c: thing1 # NEW entry from [+] on B array.
        -
        - -

        Splat

        -

        Maps

        -

        a.*.c

        -
        a:
        -  b1:
        -    c: thing # MATCHES
        -    d: whatever
        -  b2:
        -    c: thing # MATCHES
        -    f: something irrelevant
        -
        - -

        Prefix splat

        -

        bob.item*.cats

        -
        bob:
        -  item:
        -    cats: bananas # MATCHES
        -  something:
        -    cats: lemons
        -  itemThing:
        -    cats: more bananas # MATCHES
        -  item2:
        -    cats: apples # MATCHES
        -  thing:
        -    cats: oranges
        -
        - -

        Arrays

        -

        a.b[*].c

        -
        a:
        -  b:
        -  - c: thing0 # MATCHES
        -    d: what..ever
        -  - c: thing1 # MATCHES
        -    d: blarh
        -  - c: thing2 # MATCHES
        -    f: thingamabob
        -
        - -

        Deep Splat

        -

        '**' will match arbitrary nodes for both maps and arrays:

        -

        a.**.c

        -
        a:
        -  b1:
        -    c: thing1 # MATCHES
        -    d: cat cat
        -  b2:
        -    c: thing2 # MATCHES
        -    d: dog dog
        -  b3:
        -    d:
        -    - f:
        -        c: thing3 # MATCHES
        -        d: beep
        -    - f:
        -        g:
        -          c: thing4 # MATCHES
        -          d: boop
        -    - d: mooo
        -
        - -

        Search by children nodes

        -

        a.(b.d==cat).b.c

        -
        a:
        -  - b:
        -      c: thing0
        -      d: leopard
        -    ba: fast
        -  - b:
        -      c: thing1 # MATCHES
        -      d: cat
        -    ba: meowy
        -  - b:
        -      c: thing2
        -      d: caterpillar
        -    ba: icky
        -  - b:
        -      c: thing3 # MATCHES
        -      d: cat
        -    ba: also meowy
        -
        - -

        With prefixes

        -

        a.(b.d==cat*).c

        -
        a:
        -  - b:
        -      c: thing0
        -      d: leopard
        -    ba: fast
        -  - b:
        -      c: thing1 # MATCHES
        -      d: cat
        -    ba: meowy
        -  - b:
        -      c: thing2 # MATCHES
        -      d: caterpillar
        -    ba: icky
        -  - b:
        -      c: thing3 # MATCHES
        -      d: cat
        -    ba: also meowy
        -
        - -

        Special Characters

        -

        Keys with dots

        -

        When specifying a key that has a dot use key lookup indicator.

        -
        b:
        -  foo.bar: 7
        -
        - -
        yaml r sample.yaml 'b[foo.bar]'
        -
        - -
        yaml w sample.yaml 'b[foo.bar]' 9
        -
        - -

        Any valid yaml key can be specified as part of a key lookup.

        -

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Keys (and values) with leading dashes

        -

        The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags, if they start if a dash.

        -
        yq n -j -- --key --value
        -
        - -

        Will result in

        -
        --key: --value
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/prefix/index.html b/docs/prefix/index.html deleted file mode 100644 index a578a376..00000000 --- a/docs/prefix/index.html +++ /dev/null @@ -1,646 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Prefix - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Prefix

        - -
        yq p <yaml_file> <path>
        -
        - -

        Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path.

        -

        See docs for path expression for more details.

        -

        Prefix a document

        -

        Given a data1.yaml file of:

        -
        a:
        -  b: [1, 2]
        -
        - -

        then

        -
        yq p data1.yaml c.d
        -
        - -

        will output:

        -
        c:
        -  d:
        -    a:
        -      b: [1, 2]
        -
        - -

        Updating files in-place

        -
        yq p -i data1.yaml c
        -
        - -

        will update the data1.yaml file so that the path 'c' prefixes the document.

        -

        Multiple Documents

        -

        Prefix a single document

        -

        Given a data1.yaml file of:

        -
        something: else
        ----
        -a: simple
        -b: cat
        -
        - -

        then

        -
        yq p -d1 data1.yaml c
        -
        - -

        will output:

        -
        something: else
        ----
        -c:
        -  a: simple
        -  b: cat
        -
        - -

        Prefix all documents

        -

        Given a data1.yaml file of:

        -
        something: else
        ----
        -a: simple
        -b: cat
        -
        - -

        then

        -
        yq p -d'*' data1.yaml c
        -
        - -

        will output:

        -
        c:
        -  something: else
        ----
        -c:
        -  a: simple
        -  b: cat
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/read/index.html b/docs/read/index.html deleted file mode 100644 index af7557e4..00000000 --- a/docs/read/index.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Read - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Read

        - -
        yq r <yaml_file|json_file> <path_expression>
        -
        - -

        TALK PRINTING ABOUT KEYS AND VALUES

        -

        Returns the matching nodes of the path expression for the given yaml file (or STDIN).

        -

        See docs for path expression for more details.

        -

        Basic

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -
        - -

        then

        -
        yq r sample.yaml b.c
        -
        - -

        will output the value of '2'.

        -

        From Stdin

        -

        Given a sample.yaml file of:

        -
        cat sample.yaml | yq r - b.c
        -
        - -

        will output the value of '2'.

        -

        Multiple Documents

        -

        Reading from a single document

        -

        Given a sample.yaml file of:

        -
        something: else
        ----
        -b:
        -  c: 2
        -
        - -

        then

        -
        yq r -d1 sample.yaml b.c
        -
        - -

        will output the value of '2'.

        -

        Read from all documents

        -

        Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired.

        -

        Given a sample.yaml file of:

        -
        name: Fred
        -age: 22
        ----
        -name: Stella
        -age: 23
        ----
        -name: Android
        -age: 232
        -
        - -

        then

        -
        yq r -d'*' sample.yaml name
        -
        - -

        will output:

        -
        - Fred
        -- Stella
        -- Android
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/search/search_index.json b/docs/search/search_index.json index bb3d8fde..d033dff2 100644 --- a/docs/search/search_index.json +++ b/docs/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"yq \u00b6 yq is a lightweight and portable command-line YAML processor The aim of the project is to be the jq or sed of yaml files. Install \u00b6 On MacOS: brew install yq On Ubuntu and other Linux distros supporting snap packages: snap install yq On Ubuntu 16.04 or higher from Debian package: sudo add-apt-repository ppa:rmescandon/yq sudo apt update sudo apt install yq -y or, Download latest binary or alternatively: GO111MODULE=on go get github.com/mikefarah/yq/v3 View on GitHub","title":"Install"},{"location":"#yq","text":"yq is a lightweight and portable command-line YAML processor The aim of the project is to be the jq or sed of yaml files.","title":"yq"},{"location":"#install","text":"On MacOS: brew install yq On Ubuntu and other Linux distros supporting snap packages: snap install yq On Ubuntu 16.04 or higher from Debian package: sudo add-apt-repository ppa:rmescandon/yq sudo apt update sudo apt install yq -y or, Download latest binary or alternatively: GO111MODULE=on go get github.com/mikefarah/yq/v3 View on GitHub","title":"Install"},{"location":"convert/","text":"Yaml to Json \u00b6 To convert output to json, use the --tojson (or -j) flag. This is supported by all commands. Each matching yaml node will be converted to json and printed out on a separate line. Given a sample.yaml file of: b: c: 2 then yq r -j sample.yaml will output {\"b\":{\"c\":2}} Given a sample.yaml file of: bob: c: 2 bab: c: 5 then yq r -j sample.yaml b* will output {\"c\":2} {\"c\":5} Json to Yaml \u00b6 To read in json, just pass in a json file instead of yaml, it will just work :) e.g given a json file {\"a\":\"Easy! as one two three\",\"b\":{\"c\":2,\"d\":[3,4]}} then yq r sample.json will output a: Easy! as one two three b: c: 2 d: - 3 - 4","title":"Convert"},{"location":"convert/#yaml-to-json","text":"To convert output to json, use the --tojson (or -j) flag. This is supported by all commands. Each matching yaml node will be converted to json and printed out on a separate line. Given a sample.yaml file of: b: c: 2 then yq r -j sample.yaml will output {\"b\":{\"c\":2}} Given a sample.yaml file of: bob: c: 2 bab: c: 5 then yq r -j sample.yaml b* will output {\"c\":2} {\"c\":5}","title":"Yaml to Json"},{"location":"convert/#json-to-yaml","text":"To read in json, just pass in a json file instead of yaml, it will just work :) e.g given a json file {\"a\":\"Easy! as one two three\",\"b\":{\"c\":2,\"d\":[3,4]}} then yq r sample.json will output a: Easy! as one two three b: c: 2 d: - 3 - 4","title":"Json to Yaml"},{"location":"create/","text":"yq n Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file. See docs for path expression Creating a simple yaml file \u00b6 yq n b.c cat will output: b: c: cat Creating using a create script \u00b6 Create scripts follow the same format as the update scripts. Given a script create_instructions.yaml of: - command: update path: b.c value: #great things: frog # wow! then yq n -s create_instructions.yaml will output: b: c: #great things: frog # wow! You can also pipe the instructions in: cat create_instructions.yaml | yq n -s -","title":"Create"},{"location":"create/#creating-a-simple-yaml-file","text":"yq n b.c cat will output: b: c: cat","title":"Creating a simple yaml file"},{"location":"create/#creating-using-a-create-script","text":"Create scripts follow the same format as the update scripts. Given a script create_instructions.yaml of: - command: update path: b.c value: #great things: frog # wow! then yq n -s create_instructions.yaml will output: b: c: #great things: frog # wow! You can also pipe the instructions in: cat create_instructions.yaml | yq n -s -","title":"Creating using a create script"},{"location":"delete/","text":"yq delete The delete command will delete all the matching nodes for the path expression in the given yaml input. See docs for path expression for more details. Deleting from a simple document \u00b6 Given a sample.yaml file of: b: c: 2 apples: green then yq d sample.yaml b.c will output b: apples: green From STDIN \u00b6 Use \"-\" (without quotes) in-place of a file name if you wish to pipe in input from STDIN. cat sample.yaml | yq d - b.c Deleting in-place \u00b6 yq d -i sample.yaml b.c will update the sample.yaml file so that the 'c' node is deleted Multiple Documents \u00b6 Delete from single document \u00b6 Given a sample.yaml file of: something: else field: leaveMe --- b: c: 2 field: deleteMe then yq w -d1 sample.yaml field will output: something: else field: leaveMe --- b: c: 2 Delete from all documents \u00b6 Given a sample.yaml file of: something: else field: deleteMe --- b: c: 2 field: deleteMeToo then yq w -d'*' sample.yaml field will output: something: else --- b: c: 2","title":"Delete"},{"location":"delete/#deleting-from-a-simple-document","text":"Given a sample.yaml file of: b: c: 2 apples: green then yq d sample.yaml b.c will output b: apples: green","title":"Deleting from a simple document"},{"location":"delete/#from-stdin","text":"Use \"-\" (without quotes) in-place of a file name if you wish to pipe in input from STDIN. cat sample.yaml | yq d - b.c","title":"From STDIN"},{"location":"delete/#deleting-in-place","text":"yq d -i sample.yaml b.c will update the sample.yaml file so that the 'c' node is deleted","title":"Deleting in-place"},{"location":"delete/#multiple-documents","text":"","title":"Multiple Documents"},{"location":"delete/#delete-from-single-document","text":"Given a sample.yaml file of: something: else field: leaveMe --- b: c: 2 field: deleteMe then yq w -d1 sample.yaml field will output: something: else field: leaveMe --- b: c: 2","title":"Delete from single document"},{"location":"delete/#delete-from-all-documents","text":"Given a sample.yaml file of: something: else field: deleteMe --- b: c: 2 field: deleteMeToo then yq w -d'*' sample.yaml field will output: something: else --- b: c: 2","title":"Delete from all documents"},{"location":"merge/","text":"Yaml files can be merged using the 'merge' command. Each additional file merged with the first file will set values for any key not existing already or where the key has no value. yq m ... Merge example \u00b6 Given a data1.yaml file of: a: simple b: [1, 2] and data2.yaml file of: a: other c: test: 1 then yq m data1.yaml data2.yaml will output: a: simple b: [1, 2] c: test: 1 Updating files in-place \u00b6 yq m -i data1.yaml data2.yaml will update the data1.yaml file with the merged result. Overwrite values \u00b6 Given a data1.yaml file of: a: simple b: [1, 2] and data2.yaml file of: a: other c: test: 1 then yq m -x data1.yaml data2.yaml will output: a: other b: [1, 2] c: test: 1 Overwrite values with arrays \u00b6 Given a data1.yaml file of: a: simple b: [1, 2] and data3.yaml file of: b: [3, 4] c: test: 2 other: true d: false then yq m -x data1.yaml data3.yaml will output: a: simple b: [3, 4] c: test: 2 other: true d: false Notice that 'b' does not result in the merging of the values within an array. Append values with arrays \u00b6 Given a data1.yaml file of: a: simple b: [1, 2] d: hi and data3.yaml file of: a: something b: [3, 4] c: test: 2 other: true then yq m -a data1.yaml data3.yaml will output: a: simple b: [1, 2, 3, 4] c: test: 2 other: true d: hi Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a). Multiple Documents \u00b6 Merge into single document \u00b6 Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected. Given a data1.yaml file of: something: else --- a: simple b: cat and data3.yaml file of: b: dog then yq m -x -d1 data1.yaml data3.yaml will output: something: else --- a: simple b: dog Merge into all documents \u00b6 Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected. Given a data1.yaml file of: something: else --- a: simple b: cat and data3.yaml file of: b: dog then yq m -x -d'*' data1.yaml data3.yaml will output: b: dog something: else --- a: simple b: dog","title":"Merge"},{"location":"merge/#merge-example","text":"Given a data1.yaml file of: a: simple b: [1, 2] and data2.yaml file of: a: other c: test: 1 then yq m data1.yaml data2.yaml will output: a: simple b: [1, 2] c: test: 1","title":"Merge example"},{"location":"merge/#updating-files-in-place","text":"yq m -i data1.yaml data2.yaml will update the data1.yaml file with the merged result.","title":"Updating files in-place"},{"location":"merge/#overwrite-values","text":"Given a data1.yaml file of: a: simple b: [1, 2] and data2.yaml file of: a: other c: test: 1 then yq m -x data1.yaml data2.yaml will output: a: other b: [1, 2] c: test: 1","title":"Overwrite values"},{"location":"merge/#overwrite-values-with-arrays","text":"Given a data1.yaml file of: a: simple b: [1, 2] and data3.yaml file of: b: [3, 4] c: test: 2 other: true d: false then yq m -x data1.yaml data3.yaml will output: a: simple b: [3, 4] c: test: 2 other: true d: false Notice that 'b' does not result in the merging of the values within an array.","title":"Overwrite values with arrays"},{"location":"merge/#append-values-with-arrays","text":"Given a data1.yaml file of: a: simple b: [1, 2] d: hi and data3.yaml file of: a: something b: [3, 4] c: test: 2 other: true then yq m -a data1.yaml data3.yaml will output: a: simple b: [1, 2, 3, 4] c: test: 2 other: true d: hi Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).","title":"Append values with arrays"},{"location":"merge/#multiple-documents","text":"","title":"Multiple Documents"},{"location":"merge/#merge-into-single-document","text":"Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected. Given a data1.yaml file of: something: else --- a: simple b: cat and data3.yaml file of: b: dog then yq m -x -d1 data1.yaml data3.yaml will output: something: else --- a: simple b: dog","title":"Merge into single document"},{"location":"merge/#merge-into-all-documents","text":"Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected. Given a data1.yaml file of: something: else --- a: simple b: cat and data3.yaml file of: b: dog then yq m -x -d'*' data1.yaml data3.yaml will output: b: dog something: else --- a: simple b: dog","title":"Merge into all documents"},{"location":"path_expressions/","text":"Path expressions are used to deeply navigate and match particular yaml nodes. As a general rule, you should wrap paths in quotes to prevent your CLI from processing '*, []' and other special characters. Simple expressions \u00b6 Maps \u00b6 a.b.c a: b: c: thing # MATCHES Arrays \u00b6 a.b[1].c a: b: - c: thing0 - c: thing1 # MATCHES - c: thing2 Appending to arrays \u00b6 (e.g. when using the write command) a.b[+].c a: b: - c: thing0 Will add a new entry: a: b: - c: thing0 - c: thing1 # NEW entry from [+] on B array. Splat \u00b6 Maps \u00b6 a.*.c a: b1: c: thing # MATCHES d: whatever b2: c: thing # MATCHES f: something irrelevant Prefix splat \u00b6 bob.item*.cats bob: item: cats: bananas # MATCHES something: cats: lemons itemThing: cats: more bananas # MATCHES item2: cats: apples # MATCHES thing: cats: oranges Arrays \u00b6 a.b[*].c a: b: - c: thing0 # MATCHES d: what..ever - c: thing1 # MATCHES d: blarh - c: thing2 # MATCHES f: thingamabob Deep Splat \u00b6 '**' will match arbitrary nodes for both maps and arrays: a.**.c a: b1: c: thing1 # MATCHES d: cat cat b2: c: thing2 # MATCHES d: dog dog b3: d: - f: c: thing3 # MATCHES d: beep - f: g: c: thing4 # MATCHES d: boop - d: mooo Search by children nodes \u00b6 a.(b.d==cat).b.c a: - b: c: thing0 d: leopard ba: fast - b: c: thing1 # MATCHES d: cat ba: meowy - b: c: thing2 d: caterpillar ba: icky - b: c: thing3 # MATCHES d: cat ba: also meowy With prefixes \u00b6 a.(b.d==cat*).c a: - b: c: thing0 d: leopard ba: fast - b: c: thing1 # MATCHES d: cat ba: meowy - b: c: thing2 # MATCHES d: caterpillar ba: icky - b: c: thing3 # MATCHES d: cat ba: also meowy Special Characters \u00b6 Keys with dots \u00b6 When specifying a key that has a dot use key lookup indicator. b: foo.bar: 7 yaml r sample.yaml 'b[foo.bar]' yaml w sample.yaml 'b[foo.bar]' 9 Any valid yaml key can be specified as part of a key lookup. Note that the path is in quotes to avoid the square brackets being interpreted by your shell. Keys (and values) with leading dashes \u00b6 The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags, if they start if a dash. yq n -j -- --key --value Will result in --key: --value","title":"Path Expressions"},{"location":"path_expressions/#simple-expressions","text":"","title":"Simple expressions"},{"location":"path_expressions/#maps","text":"a.b.c a: b: c: thing # MATCHES","title":"Maps"},{"location":"path_expressions/#arrays","text":"a.b[1].c a: b: - c: thing0 - c: thing1 # MATCHES - c: thing2","title":"Arrays"},{"location":"path_expressions/#appending-to-arrays","text":"(e.g. when using the write command) a.b[+].c a: b: - c: thing0 Will add a new entry: a: b: - c: thing0 - c: thing1 # NEW entry from [+] on B array.","title":"Appending to arrays"},{"location":"path_expressions/#splat","text":"","title":"Splat"},{"location":"path_expressions/#maps_1","text":"a.*.c a: b1: c: thing # MATCHES d: whatever b2: c: thing # MATCHES f: something irrelevant","title":"Maps"},{"location":"path_expressions/#prefix-splat","text":"bob.item*.cats bob: item: cats: bananas # MATCHES something: cats: lemons itemThing: cats: more bananas # MATCHES item2: cats: apples # MATCHES thing: cats: oranges","title":"Prefix splat"},{"location":"path_expressions/#arrays_1","text":"a.b[*].c a: b: - c: thing0 # MATCHES d: what..ever - c: thing1 # MATCHES d: blarh - c: thing2 # MATCHES f: thingamabob","title":"Arrays"},{"location":"path_expressions/#deep-splat","text":"'**' will match arbitrary nodes for both maps and arrays: a.**.c a: b1: c: thing1 # MATCHES d: cat cat b2: c: thing2 # MATCHES d: dog dog b3: d: - f: c: thing3 # MATCHES d: beep - f: g: c: thing4 # MATCHES d: boop - d: mooo","title":"Deep Splat"},{"location":"path_expressions/#search-by-children-nodes","text":"a.(b.d==cat).b.c a: - b: c: thing0 d: leopard ba: fast - b: c: thing1 # MATCHES d: cat ba: meowy - b: c: thing2 d: caterpillar ba: icky - b: c: thing3 # MATCHES d: cat ba: also meowy","title":"Search by children nodes"},{"location":"path_expressions/#with-prefixes","text":"a.(b.d==cat*).c a: - b: c: thing0 d: leopard ba: fast - b: c: thing1 # MATCHES d: cat ba: meowy - b: c: thing2 # MATCHES d: caterpillar ba: icky - b: c: thing3 # MATCHES d: cat ba: also meowy","title":"With prefixes"},{"location":"path_expressions/#special-characters","text":"","title":"Special Characters"},{"location":"path_expressions/#keys-with-dots","text":"When specifying a key that has a dot use key lookup indicator. b: foo.bar: 7 yaml r sample.yaml 'b[foo.bar]' yaml w sample.yaml 'b[foo.bar]' 9 Any valid yaml key can be specified as part of a key lookup. Note that the path is in quotes to avoid the square brackets being interpreted by your shell.","title":"Keys with dots"},{"location":"path_expressions/#keys-and-values-with-leading-dashes","text":"The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags, if they start if a dash. yq n -j -- --key --value Will result in --key: --value","title":"Keys (and values) with leading dashes"},{"location":"prefix/","text":"yq p Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path. See docs for path expression for more details. Prefix a document \u00b6 Given a data1.yaml file of: a: b: [1, 2] then yq p data1.yaml c.d will output: c: d: a: b: [1, 2] Updating files in-place \u00b6 yq p -i data1.yaml c will update the data1.yaml file so that the path 'c' prefixes the document. Multiple Documents \u00b6 Prefix a single document \u00b6 Given a data1.yaml file of: something: else --- a: simple b: cat then yq p -d1 data1.yaml c will output: something: else --- c: a: simple b: cat Prefix all documents \u00b6 Given a data1.yaml file of: something: else --- a: simple b: cat then yq p -d'*' data1.yaml c will output: c: something: else --- c: a: simple b: cat","title":"Prefix"},{"location":"prefix/#prefix-a-document","text":"Given a data1.yaml file of: a: b: [1, 2] then yq p data1.yaml c.d will output: c: d: a: b: [1, 2]","title":"Prefix a document"},{"location":"prefix/#updating-files-in-place","text":"yq p -i data1.yaml c will update the data1.yaml file so that the path 'c' prefixes the document.","title":"Updating files in-place"},{"location":"prefix/#multiple-documents","text":"","title":"Multiple Documents"},{"location":"prefix/#prefix-a-single-document","text":"Given a data1.yaml file of: something: else --- a: simple b: cat then yq p -d1 data1.yaml c will output: something: else --- c: a: simple b: cat","title":"Prefix a single document"},{"location":"prefix/#prefix-all-documents","text":"Given a data1.yaml file of: something: else --- a: simple b: cat then yq p -d'*' data1.yaml c will output: c: something: else --- c: a: simple b: cat","title":"Prefix all documents"},{"location":"read/","text":"yq r TALK PRINTING ABOUT KEYS AND VALUES Returns the matching nodes of the path expression for the given yaml file (or STDIN). See docs for path expression for more details. Basic \u00b6 Given a sample.yaml file of: b: c: 2 then yq r sample.yaml b.c will output the value of '2'. From Stdin \u00b6 Given a sample.yaml file of: cat sample.yaml | yq r - b.c will output the value of '2'. Multiple Documents \u00b6 Reading from a single document \u00b6 Given a sample.yaml file of: something: else --- b: c: 2 then yq r -d1 sample.yaml b.c will output the value of '2'. Read from all documents \u00b6 Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired. Given a sample.yaml file of: name: Fred age: 22 --- name: Stella age: 23 --- name: Android age: 232 then yq r -d'*' sample.yaml name will output: - Fred - Stella - Android","title":"Read"},{"location":"read/#basic","text":"Given a sample.yaml file of: b: c: 2 then yq r sample.yaml b.c will output the value of '2'.","title":"Basic"},{"location":"read/#from-stdin","text":"Given a sample.yaml file of: cat sample.yaml | yq r - b.c will output the value of '2'.","title":"From Stdin"},{"location":"read/#multiple-documents","text":"","title":"Multiple Documents"},{"location":"read/#reading-from-a-single-document","text":"Given a sample.yaml file of: something: else --- b: c: 2 then yq r -d1 sample.yaml b.c will output the value of '2'.","title":"Reading from a single document"},{"location":"read/#read-from-all-documents","text":"Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired. Given a sample.yaml file of: name: Fred age: 22 --- name: Stella age: 23 --- name: Android age: 232 then yq r -d'*' sample.yaml name will output: - Fred - Stella - Android","title":"Read from all documents"},{"location":"value_parsing/","text":"This describes how values are parsed from the CLI to commands that create/update yaml (e.g. new/write). yq attempts to parse values intelligently, e.g. when a number is passed it - it will assume it's a number as opposed to a string. yq will not alter the representation of what you give. So if you pass '03.0' in, it will assume it's a number and keep the value formatted as it was passed in, that is '03.0'. The --tag flag can be used to override the tag type to force particular tags. Default behaviour \u00b6 Integers \u00b6 Given yq new key 3 results in key: 3 Given a formatted number yq new key 03 results in key: 03 yq keeps the number formatted as it was passed in. Float \u00b6 Given yq new key \"3.1\" results in key: 3.1 Note that quoting the number does not make a difference. Given a formatted decimal number yq new key 03.0 results in key: 03.0 yq will keep the number formatted as it was passed in Booleans \u00b6 yq new key true results in key: true Nulls \u00b6 yq new key null results in key: null yq new key '~' results in key: ~ yq new key '' results in key: Strings \u00b6 yq new key whatever results in key: whatever yq new key ' whatever ' results in key: ' whatever ' Using the tag field to override \u00b6 Previous versions of yq required double quoting to force values to be strings, this no longer works - instead use the --tag flag. Casting booleans \u00b6 yq new --tag '!!str' key true results in key: 'true' Casting nulls \u00b6 yq new --tag '!!str' key null results in key: 'null' Custom types \u00b6 yq new --tag '!!farah' key gold results in key: !!farah gold","title":"Value Parsing"},{"location":"value_parsing/#default-behaviour","text":"","title":"Default behaviour"},{"location":"value_parsing/#integers","text":"Given yq new key 3 results in key: 3 Given a formatted number yq new key 03 results in key: 03 yq keeps the number formatted as it was passed in.","title":"Integers"},{"location":"value_parsing/#float","text":"Given yq new key \"3.1\" results in key: 3.1 Note that quoting the number does not make a difference. Given a formatted decimal number yq new key 03.0 results in key: 03.0 yq will keep the number formatted as it was passed in","title":"Float"},{"location":"value_parsing/#booleans","text":"yq new key true results in key: true","title":"Booleans"},{"location":"value_parsing/#nulls","text":"yq new key null results in key: null yq new key '~' results in key: ~ yq new key '' results in key:","title":"Nulls"},{"location":"value_parsing/#strings","text":"yq new key whatever results in key: whatever yq new key ' whatever ' results in key: ' whatever '","title":"Strings"},{"location":"value_parsing/#using-the-tag-field-to-override","text":"Previous versions of yq required double quoting to force values to be strings, this no longer works - instead use the --tag flag.","title":"Using the tag field to override"},{"location":"value_parsing/#casting-booleans","text":"yq new --tag '!!str' key true results in key: 'true'","title":"Casting booleans"},{"location":"value_parsing/#casting-nulls","text":"yq new --tag '!!str' key null results in key: 'null'","title":"Casting nulls"},{"location":"value_parsing/#custom-types","text":"yq new --tag '!!farah' key gold results in key: !!farah gold","title":"Custom types"},{"location":"write/","text":"yq w Updates all the matching nodes of path expression to the supplied value. See docs for path expression for more details. Basic \u00b6 Given a sample.yaml file of: b: c: 2 then yq w sample.yaml b.c cat will output: b: c: cat Updating files in-place \u00b6 yq w -i sample.yaml b.c cat will update the sample.yaml file so that the value of 'c' is cat. From STDIN \u00b6 cat sample.yaml | yq w - b.c blah Adding new fields \u00b6 Any missing fields in the path will be created on the fly. Given a sample.yaml file of: b: c: 2 then yq w sample.yaml b.d[+] \"new thing\" will output: b: c: cat d: - new thing Appending value to an array field \u00b6 Given a sample.yaml file of: b: c: 2 d: - new thing - foo thing then yq w sample.yaml \"b.d[+]\" \"bar thing\" will output: b: c: cat d: - new thing - foo thing - bar thing Note that the path is in quotes to avoid the square brackets being interpreted by your shell. Multiple Documents \u00b6 Update a single document \u00b6 Given a sample.yaml file of: something: else --- b: c: 2 then yq w -d1 sample.yaml b.c 5 will output: something: else --- b: c: 5 Update all documents \u00b6 Given a sample.yaml file of: something: else --- b: c: 2 then yq w -d'*' sample.yaml b.c 5 will output: something: else b: c: 5 --- b: c: 5 UPDATE THIS UPDATE THIS INCLUDE DELETE EXAMPLE Updating multiple values with a script \u00b6 Given a sample.yaml file of: b: c: 2 e: - name: Billy Bob and a script update_instructions.yaml of: b.c: 3 b.e[+].name: Howdy Partner then yq w -s update_instructions.yaml sample.yaml will output: b: c: 3 e: - name: Howdy Partner And, of course, you can pipe the instructions in using '-': cat update_instructions.yaml | yq w -s - sample.yaml","title":"Write/Update"},{"location":"write/#basic","text":"Given a sample.yaml file of: b: c: 2 then yq w sample.yaml b.c cat will output: b: c: cat","title":"Basic"},{"location":"write/#updating-files-in-place","text":"yq w -i sample.yaml b.c cat will update the sample.yaml file so that the value of 'c' is cat.","title":"Updating files in-place"},{"location":"write/#from-stdin","text":"cat sample.yaml | yq w - b.c blah","title":"From STDIN"},{"location":"write/#adding-new-fields","text":"Any missing fields in the path will be created on the fly. Given a sample.yaml file of: b: c: 2 then yq w sample.yaml b.d[+] \"new thing\" will output: b: c: cat d: - new thing","title":"Adding new fields"},{"location":"write/#appending-value-to-an-array-field","text":"Given a sample.yaml file of: b: c: 2 d: - new thing - foo thing then yq w sample.yaml \"b.d[+]\" \"bar thing\" will output: b: c: cat d: - new thing - foo thing - bar thing Note that the path is in quotes to avoid the square brackets being interpreted by your shell.","title":"Appending value to an array field"},{"location":"write/#multiple-documents","text":"","title":"Multiple Documents"},{"location":"write/#update-a-single-document","text":"Given a sample.yaml file of: something: else --- b: c: 2 then yq w -d1 sample.yaml b.c 5 will output: something: else --- b: c: 5","title":"Update a single document"},{"location":"write/#update-all-documents","text":"Given a sample.yaml file of: something: else --- b: c: 2 then yq w -d'*' sample.yaml b.c 5 will output: something: else b: c: 5 --- b: c: 5 UPDATE THIS UPDATE THIS INCLUDE DELETE EXAMPLE","title":"Update all documents"},{"location":"write/#updating-multiple-values-with-a-script","text":"Given a sample.yaml file of: b: c: 2 e: - name: Billy Bob and a script update_instructions.yaml of: b.c: 3 b.e[+].name: Howdy Partner then yq w -s update_instructions.yaml sample.yaml will output: b: c: 3 e: - name: Howdy Partner And, of course, you can pipe the instructions in using '-': cat update_instructions.yaml | yq w -s - sample.yaml","title":"Updating multiple values with a script"}]} \ No newline at end of file +{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"New documentation website User docs are better than ever, and have been moved here","title":"Home"},{"location":"#new-documentation-website","text":"User docs are better than ever, and have been moved here","title":"New documentation website"}]} \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 5925ffac..364990d7 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,52 +2,7 @@ None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 - daily - - - None - 2020-01-17 + 2020-01-30 daily \ No newline at end of file diff --git a/docs/sitemap.xml.gz b/docs/sitemap.xml.gz index 6f94a6b08a0b55c6dcc03e301cdf9019de6286cc..005f405c62b1182f63d7a199bc4fdcdc2dce8ee8 100644 GIT binary patch literal 189 zcmV;u07CyCiwFpd4>Dc?|8r?{Wo=<_E_iKh08Nff62c%1h4-FX@36e>Zhx0n7nV>WsD@+L2$c@K`fOhg-eO-sfpF$y14M()HjIy8x7 zv&WkDDUBz9X_^??WCk@N3$X*#eI5`t2%H_M+>u=KfmIRVf|i%yjeuGGpu#kSR#v>? r1uu&|&+CnC7Tqh~nR_%3*(&Qk0{+OF0!z%&^1Jd2S4&c1-T(jqrp$|*a_%nobmR5QI zhG4=Bfe2{y_7W2v?_jpsw{PF<*Db3by+L>7gLbyU1)&%zD_x^)TjBHRkZ*C#Gj{VP zsEAV7h6;Bv4ts!c97)ho^%Bm=$8iNq%%zF__fH6e0Gm z;Uz6;PK&(Qr1j!05!ngbs*6`w-Uw};41S!M0$ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Value Parsing - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        -
        -
        - - -
        -
        -
        - - -
        -
        - - - - - -

        Value Parsing

        - -

        This describes how values are parsed from the CLI to commands that create/update yaml (e.g. new/write).

        -

        yq attempts to parse values intelligently, e.g. when a number is passed it - it will assume it's a number as opposed to a string. yq will not alter the representation of what you give. So if you pass '03.0' in, it will assume it's a number and keep the value formatted as it was passed in, that is '03.0'.

        -

        The --tag flag can be used to override the tag type to force particular tags.

        -

        Default behaviour

        -

        Integers

        -

        Given

        -
        yq new key 3
        -
        - -

        results in

        -
        key: 3
        -
        - -

        Given a formatted number

        -
        yq new key 03
        -
        - -

        results in

        -
        key: 03
        -
        - -

        yq keeps the number formatted as it was passed in.

        -

        Float

        -

        Given

        -
        yq new key "3.1"
        -
        - -

        results in

        -
        key: 3.1
        -
        - -

        Note that quoting the number does not make a difference.

        -

        Given a formatted decimal number

        -
        yq new key 03.0
        -
        - -

        results in

        -
        key: 03.0
        -
        - -

        yq will keep the number formatted as it was passed in

        -

        Booleans

        -
        yq new key true
        -
        - -

        results in

        -
        key: true
        -
        - -

        Nulls

        -
        yq new key null
        -
        - -

        results in

        -
        key: null
        -
        - -
        yq new key '~'
        -
        - -

        results in

        -
        key: ~
        -
        - -
        yq new key ''
        -
        - -

        results in

        -
        key:
        -
        - -

        Strings

        -
        yq new key whatever
        -
        - -

        results in

        -
        key: whatever
        -
        - -
        yq new key ' whatever '
        -
        - -

        results in

        -
        key: ' whatever '
        -
        - -

        Using the tag field to override

        -

        Previous versions of yq required double quoting to force values to be strings, this no longer works - instead use the --tag flag.

        -

        Casting booleans

        -
        yq new --tag '!!str' key true
        -
        - -

        results in

        -
        key: 'true'
        -
        - -

        Casting nulls

        -
        yq new --tag '!!str' key null
        -
        - -

        results in

        -
        key: 'null'
        -
        - -

        Custom types

        -
        yq new --tag '!!farah' key gold
        -
        - -

        results in

        -
        key: !!farah gold
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/write/index.html b/docs/write/index.html deleted file mode 100644 index 5438cae5..00000000 --- a/docs/write/index.html +++ /dev/null @@ -1,787 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Write/Update - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -
        - -
        - -
        - - - - -
        -
        - - -
        -
        -
        - -
        -
        -
        - - -
        - -
        - - -
        -
        - - - - - -

        Write/Update

        - -
        yq w <yaml_file> <path_expression> <new value>
        -
        - -

        Updates all the matching nodes of path expression to the supplied value.

        -

        See docs for path expression for more details.

        -

        Basic

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -
        - -

        then

        -
        yq w sample.yaml b.c cat
        -
        - -

        will output:

        -
        b:
        -  c: cat
        -
        - -

        Updating files in-place

        -
        yq w -i sample.yaml b.c cat
        -
        - -

        will update the sample.yaml file so that the value of 'c' is cat.

        -

        From STDIN

        -
        cat sample.yaml | yq w - b.c blah
        -
        - -

        Adding new fields

        -

        Any missing fields in the path will be created on the fly.

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -
        - -

        then

        -
        yq w sample.yaml b.d[+] "new thing"
        -
        - -

        will output:

        -
        b:
        -  c: cat
        -  d:
        -    - new thing
        -
        - -

        Appending value to an array field

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -  d:
        -    - new thing
        -    - foo thing
        -
        - -

        then

        -
        yq w sample.yaml "b.d[+]" "bar thing"
        -
        - -

        will output:

        -
        b:
        -  c: cat
        -  d:
        -    - new thing
        -    - foo thing
        -    - bar thing
        -
        - -

        Note that the path is in quotes to avoid the square brackets being interpreted by your shell.

        -

        Multiple Documents

        -

        Update a single document

        -

        Given a sample.yaml file of:

        -
        something: else
        ----
        -b:
        -  c: 2
        -
        - -

        then

        -
        yq w -d1 sample.yaml b.c 5
        -
        - -

        will output:

        -
        something: else
        ----
        -b:
        -  c: 5
        -
        - -

        Update all documents

        -

        Given a sample.yaml file of:

        -
        something: else
        ----
        -b:
        -  c: 2
        -
        - -

        then

        -
        yq w -d'*' sample.yaml b.c 5
        -
        - -

        will output:

        -
        something: else
        -b:
        -  c: 5
        ----
        -b:
        -  c: 5
        -
        - -

        UPDATE THIS -UPDATE THIS -INCLUDE DELETE EXAMPLE

        -

        Updating multiple values with a script

        -

        Given a sample.yaml file of:

        -
        b:
        -  c: 2
        -  e:
        -    - name: Billy Bob
        -
        - -

        and a script update_instructions.yaml of:

        -
        b.c: 3
        -b.e[+].name: Howdy Partner
        -
        - -

        then

        -
        yq w -s update_instructions.yaml sample.yaml
        -
        - -

        will output:

        -
        b:
        -  c: 3
        -  e:
        -    - name: Howdy Partner
        -
        - -

        And, of course, you can pipe the instructions in using '-':

        -
        cat update_instructions.yaml | yq w -s - sample.yaml
        -
        - - - - - - - - - -
        -
        -
        -
        - - - - -
        - - - - - - - - \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index a3755863..9896ee02 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,29 +2,6 @@ docs_dir: mkdocs site_dir: docs site_name: Yq theme: 'material' -pages: - - Install: index.md - - Path Expressions: path_expressions.md - - Value Parsing: value_parsing.md - - Read: read.md - - Write/Update: write.md - - Prefix: prefix.md - - Delete: delete.md - - Create: create.md - - Convert: convert.md - - Merge: merge.md repo_name: 'mikefarah/yq' repo_url: 'https://github.com/mikefarah/yq' -extra: - social: - - type: 'github' - link: 'https://github.com/mikefarah' - - type: 'linkedin' - link: 'https://www.linkedin.com/in/mike-farah-b5a75b2/' - -markdown_extensions: - - markdown_include.include: - base_path: mkdocs - - toc: - permalink: True diff --git a/mkdocs/convert.md b/mkdocs/convert.md deleted file mode 100644 index eb159335..00000000 --- a/mkdocs/convert.md +++ /dev/null @@ -1,60 +0,0 @@ -## Yaml to Json -To convert output to json, use the --tojson (or -j) flag. This is supported by all commands. - -Each matching yaml node will be converted to json and printed out on a separate line. - -Given a sample.yaml file of: -```yaml -b: - c: 2 -``` -then -```bash -yq r -j sample.yaml -``` - -will output -```json -{"b":{"c":2}} -``` - -Given a sample.yaml file of: -```yaml -bob: - c: 2 -bab: - c: 5 -``` -then -```bash -yq r -j sample.yaml b* -``` - -will output -```json -{"c":2} -{"c":5} -``` - -## Json to Yaml -To read in json, just pass in a json file instead of yaml, it will just work :) - -e.g given a json file - -```json -{"a":"Easy! as one two three","b":{"c":2,"d":[3,4]}} -``` -then -```bash -yq r sample.json -``` -will output -```yaml -a: Easy! as one two three -b: - c: 2 - d: - - 3 - - 4 -``` - diff --git a/mkdocs/create.md b/mkdocs/create.md deleted file mode 100644 index 457e29bc..00000000 --- a/mkdocs/create.md +++ /dev/null @@ -1,47 +0,0 @@ -``` -yq n -``` - -Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file. - -See docs for [path expression](path_expressions.md) - -## Creating a simple yaml file -```bash -yq n b.c cat -``` -will output: -```yaml -b: - c: cat -``` - -## Creating using a create script -Create scripts follow the same format as the update scripts. - -Given a script create_instructions.yaml of: -```yaml -- command: update - path: b.c - value: - #great - things: frog # wow! -``` -then - -```bash -yq n -s create_instructions.yaml -``` -will output: -```yaml -b: - c: - #great - things: frog # wow! -``` - -You can also pipe the instructions in: - -```bash -cat create_instructions.yaml | yq n -s - -``` \ No newline at end of file diff --git a/mkdocs/delete.md b/mkdocs/delete.md deleted file mode 100644 index c80548e5..00000000 --- a/mkdocs/delete.md +++ /dev/null @@ -1,86 +0,0 @@ -``` -yq delete -``` - -The delete command will delete all the matching nodes for the path expression in the given yaml input. - -See docs for [path expression](path_expressions.md) for more details. - - -## Deleting from a simple document -Given a sample.yaml file of: -```yaml -b: - c: 2 - apples: green -``` -then -```bash -yq d sample.yaml b.c -``` -will output -```yaml -b: - apples: green -``` - -## From STDIN -Use "-" (without quotes) in-place of a file name if you wish to pipe in input from STDIN. - -```bash -cat sample.yaml | yq d - b.c -``` - -## Deleting in-place -```bash -yq d -i sample.yaml b.c -``` -will update the sample.yaml file so that the 'c' node is deleted - - -## Multiple Documents - -### Delete from single document -Given a sample.yaml file of: -```yaml -something: else -field: leaveMe ---- -b: - c: 2 -field: deleteMe -``` -then -```bash -yq w -d1 sample.yaml field -``` -will output: -```yaml -something: else -field: leaveMe ---- -b: - c: 2 -``` - -### Delete from all documents -Given a sample.yaml file of: -```yaml -something: else -field: deleteMe ---- -b: - c: 2 -field: deleteMeToo -``` -then -```bash -yq w -d'*' sample.yaml field -``` -will output: -```yaml -something: else ---- -b: - c: 2 -``` diff --git a/mkdocs/index.md b/mkdocs/index.md deleted file mode 100644 index 50df4490..00000000 --- a/mkdocs/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# yq -yq is a lightweight and portable command-line YAML processor - -The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files. - -## Install -On MacOS: -``` -brew install yq -``` -On Ubuntu and other Linux distros supporting `snap` packages: -``` -snap install yq -``` -On Ubuntu 16.04 or higher from Debian package: -``` -sudo add-apt-repository ppa:rmescandon/yq -sudo apt update -sudo apt install yq -y -``` -or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: -``` -GO111MODULE=on go get github.com/mikefarah/yq/v3 -``` - -[View on GitHub](https://github.com/mikefarah/yq) diff --git a/mkdocs/merge.md b/mkdocs/merge.md deleted file mode 100644 index 22aa79a8..00000000 --- a/mkdocs/merge.md +++ /dev/null @@ -1,176 +0,0 @@ -Yaml files can be merged using the 'merge' command. Each additional file merged with the first file will -set values for any key not existing already or where the key has no value. - -``` -yq m ... -``` - - -## Merge example -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -``` -and data2.yaml file of: -```yaml -a: other -c: - test: 1 -``` -then -```bash -yq m data1.yaml data2.yaml -``` -will output: -```yaml -a: simple -b: [1, 2] -c: - test: 1 -``` - -## Updating files in-place -```bash -yq m -i data1.yaml data2.yaml -``` -will update the data1.yaml file with the merged result. - -## Overwrite values -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -``` -and data2.yaml file of: -```yaml -a: other -c: - test: 1 -``` -then -```bash -yq m -x data1.yaml data2.yaml -``` -will output: -```yaml -a: other -b: [1, 2] -c: - test: 1 -``` - -### Overwrite values with arrays -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -``` -and data3.yaml file of: -```yaml -b: [3, 4] -c: - test: 2 - other: true -d: false -``` -then -```bash -yq m -x data1.yaml data3.yaml -``` -will output: -```yaml -a: simple -b: [3, 4] -c: - test: 2 - other: true -d: false -``` - -Notice that 'b' does not result in the merging of the values within an array. - -## Append values with arrays -Given a data1.yaml file of: -```yaml -a: simple -b: [1, 2] -d: hi -``` -and data3.yaml file of: -```yaml -a: something -b: [3, 4] -c: - test: 2 - other: true -``` -then -```bash -yq m -a data1.yaml data3.yaml -``` -will output: -```yaml -a: simple -b: [1, 2, 3, 4] -c: - test: 2 - other: true -d: hi -``` - -Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a). - -## Multiple Documents -### Merge into single document -Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected. - -Given a data1.yaml file of: -```yaml -something: else ---- -a: simple -b: cat -``` -and data3.yaml file of: -```yaml -b: dog -``` -then -```bash -yq m -x -d1 data1.yaml data3.yaml -``` -will output: -```yaml -something: else ---- -a: simple -b: dog -``` - -### Merge into all documents -Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected. - -Given a data1.yaml file of: -```yaml -something: else ---- -a: simple -b: cat -``` -and data3.yaml file of: -```yaml -b: dog -``` -then -```bash -yq m -x -d'*' data1.yaml data3.yaml -``` -will output: -```yaml -b: dog -something: else ---- -a: simple -b: dog -``` \ No newline at end of file diff --git a/mkdocs/path_expressions.md b/mkdocs/path_expressions.md deleted file mode 100644 index 8fc038c7..00000000 --- a/mkdocs/path_expressions.md +++ /dev/null @@ -1,208 +0,0 @@ -Path expressions are used to deeply navigate and match particular yaml nodes. - -_As a general rule, you should wrap paths in quotes to prevent your CLI from processing '*, []' and other special characters._ - -## Simple expressions - -### Maps - -a.b.c - -```yaml -a: - b: - c: thing # MATCHES -``` - -### Arrays - -a.b[1].c - -```yaml -a: - b: - - c: thing0 - - c: thing1 # MATCHES - - c: thing2 -``` - -#### Appending to arrays -(e.g. when using the write command) - -a.b[+].c - - -```yaml -a: - b: - - c: thing0 -``` - -Will add a new entry: - -```yaml -a: - b: - - c: thing0 - - c: thing1 # NEW entry from [+] on B array. -``` - - -## Splat - -### Maps -a.*.c - -```yaml -a: - b1: - c: thing # MATCHES - d: whatever - b2: - c: thing # MATCHES - f: something irrelevant -``` - -#### Prefix splat - -bob.item*.cats - -```yaml -bob: - item: - cats: bananas # MATCHES - something: - cats: lemons - itemThing: - cats: more bananas # MATCHES - item2: - cats: apples # MATCHES - thing: - cats: oranges -``` - -### Arrays -a.b[*].c - -```yaml -a: - b: - - c: thing0 # MATCHES - d: what..ever - - c: thing1 # MATCHES - d: blarh - - c: thing2 # MATCHES - f: thingamabob -``` - -## Deep Splat - -'**' will match arbitrary nodes for both maps and arrays: - -a.**.c - -```yaml -a: - b1: - c: thing1 # MATCHES - d: cat cat - b2: - c: thing2 # MATCHES - d: dog dog - b3: - d: - - f: - c: thing3 # MATCHES - d: beep - - f: - g: - c: thing4 # MATCHES - d: boop - - d: mooo -``` - - -## Search by children nodes - -a.(b.d==cat).b.c - -```yaml -a: - - b: - c: thing0 - d: leopard - ba: fast - - b: - c: thing1 # MATCHES - d: cat - ba: meowy - - b: - c: thing2 - d: caterpillar - ba: icky - - b: - c: thing3 # MATCHES - d: cat - ba: also meowy -``` - -### With prefixes - -a.(b.d==cat*).c - -```yaml -a: - - b: - c: thing0 - d: leopard - ba: fast - - b: - c: thing1 # MATCHES - d: cat - ba: meowy - - b: - c: thing2 # MATCHES - d: caterpillar - ba: icky - - b: - c: thing3 # MATCHES - d: cat - ba: also meowy -``` - - -## Special Characters - - -### Keys with dots -When specifying a key that has a dot use key lookup indicator. - -```yaml -b: - foo.bar: 7 -``` - -```bash -yaml r sample.yaml 'b[foo.bar]' -``` - -```bash -yaml w sample.yaml 'b[foo.bar]' 9 -``` - -Any valid yaml key can be specified as part of a key lookup. - -Note that the path is in quotes to avoid the square brackets being interpreted by your shell. - -### Keys (and values) with leading dashes -The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags, if they start if a dash. - -```bash -yq n -j -- --key --value -``` - -Will result in - -``` ---key: --value -``` \ No newline at end of file diff --git a/mkdocs/prefix.md b/mkdocs/prefix.md deleted file mode 100644 index 76832408..00000000 --- a/mkdocs/prefix.md +++ /dev/null @@ -1,75 +0,0 @@ -``` -yq p -``` - -Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path. - -See docs for [path expression](path_expressions.md) for more details. - -## Prefix a document -Given a data1.yaml file of: -```yaml -a: - b: [1, 2] -``` -then -```bash -yq p data1.yaml c.d -``` -will output: -```yaml -c: - d: - a: - b: [1, 2] -``` - -## Updating files in-place -```bash -yq p -i data1.yaml c -``` -will update the data1.yaml file so that the path 'c' prefixes the document. - -## Multiple Documents -### Prefix a single document -Given a data1.yaml file of: -```yaml -something: else ---- -a: simple -b: cat -``` -then -```bash -yq p -d1 data1.yaml c -``` -will output: -```yaml -something: else ---- -c: - a: simple - b: cat -``` - -### Prefix all documents -Given a data1.yaml file of: -```yaml -something: else ---- -a: simple -b: cat -``` -then -```bash -yq p -d'*' data1.yaml c -``` -will output: -```yaml -c: - something: else ---- -c: - a: simple - b: cat -``` diff --git a/mkdocs/read.md b/mkdocs/read.md deleted file mode 100644 index 124e7815..00000000 --- a/mkdocs/read.md +++ /dev/null @@ -1,69 +0,0 @@ -``` -yq r -``` - -TALK PRINTING ABOUT KEYS AND VALUES - -Returns the matching nodes of the path expression for the given yaml file (or STDIN). - -See docs for [path expression](path_expressions.md) for more details. - -## Basic -Given a sample.yaml file of: -```yaml -b: - c: 2 -``` -then -```bash -yq r sample.yaml b.c -``` -will output the value of '2'. - -## From Stdin -Given a sample.yaml file of: -```bash -cat sample.yaml | yq r - b.c -``` -will output the value of '2'. - - -## Multiple Documents -### Reading from a single document -Given a sample.yaml file of: -```yaml -something: else ---- -b: - c: 2 -``` -then -```bash -yq r -d1 sample.yaml b.c -``` -will output the value of '2'. - -### Read from all documents -Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired. - -Given a sample.yaml file of: -```yaml -name: Fred -age: 22 ---- -name: Stella -age: 23 ---- -name: Android -age: 232 -``` -then -```bash -yq r -d'*' sample.yaml name -``` -will output: -``` -- Fred -- Stella -- Android -``` \ No newline at end of file diff --git a/mkdocs/value_parsing.md b/mkdocs/value_parsing.md deleted file mode 100644 index df99fb36..00000000 --- a/mkdocs/value_parsing.md +++ /dev/null @@ -1,161 +0,0 @@ -This describes how values are parsed from the CLI to commands that create/update yaml (e.g. new/write). - -`yq` attempts to parse values intelligently, e.g. when a number is passed it - it will assume it's a number as opposed to a string. `yq` will not alter the representation of what you give. So if you pass '03.0' in, it will assume it's a number and keep the value formatted as it was passed in, that is '03.0'. - -The `--tag` flag can be used to override the tag type to force particular tags. - - -## Default behaviour - -### Integers -*Given* -```bash -yq new key 3 -``` - -results in - -```yaml -key: 3 -``` - -*Given a formatted number* - -```bash -yq new key 03 -``` - -results in - -```yaml -key: 03 -``` -`yq` keeps the number formatted as it was passed in. - -### Float -*Given* -```bash -yq new key "3.1" -``` - -results in - -```yaml -key: 3.1 -``` -Note that quoting the number does not make a difference. - -*Given a formatted decimal number* - -```bash -yq new key 03.0 -``` - -results in - -```yaml -key: 03.0 -``` -`yq` keeps the number formatted as it was passed in - -### Booleans -```bash -yq new key true -``` - -results in - -```yaml -key: true -``` - -### Nulls -```bash -yq new key null -``` - -results in - -```yaml -key: null -``` - -```bash -yq new key '~' -``` - -results in - -```yaml -key: ~ -``` - -```bash -yq new key '' -``` - -results in - -```yaml -key: -``` - -### Strings -```bash -yq new key whatever -``` - -results in - -```yaml -key: whatever -``` - -```bash -yq new key ' whatever ' -``` - -results in - -```yaml -key: ' whatever ' -``` - -## Using the tag field to override - -Previous versions of yq required double quoting to force values to be strings, this no longer works - instead use the --tag flag. - - - -## Casting booleans -```bash -yq new --tag '!!str' key true -``` - -results in - -```yaml -key: 'true' -``` - -## Casting nulls -```bash -yq new --tag '!!str' key null -``` - -results in - -```yaml -key: 'null' -``` - -## Custom types -```bash -yq new --tag '!!farah' key gold -``` - -results in - -```yaml -key: !!farah gold -``` \ No newline at end of file diff --git a/mkdocs/write.md b/mkdocs/write.md deleted file mode 100644 index cc6679d4..00000000 --- a/mkdocs/write.md +++ /dev/null @@ -1,157 +0,0 @@ -``` -yq w -``` - -Updates all the matching nodes of path expression to the supplied value. - -See docs for [path expression](path_expressions.md) for more details. - -## Basic -Given a sample.yaml file of: -```yaml -b: - c: 2 -``` -then -```bash -yq w sample.yaml b.c cat -``` -will output: -```yaml -b: - c: cat -``` - -### Updating files in-place -```bash -yq w -i sample.yaml b.c cat -``` -will update the sample.yaml file so that the value of 'c' is cat. - -## From STDIN -```bash -cat sample.yaml | yq w - b.c blah -``` - -## Adding new fields -Any missing fields in the path will be created on the fly. - -Given a sample.yaml file of: -```yaml -b: - c: 2 -``` -then -```bash -yq w sample.yaml b.d[+] "new thing" -``` -will output: -```yaml -b: - c: cat - d: - - new thing -``` - -## Appending value to an array field -Given a sample.yaml file of: -```yaml -b: - c: 2 - d: - - new thing - - foo thing -``` -then -```bash -yq w sample.yaml "b.d[+]" "bar thing" -``` -will output: -```yaml -b: - c: cat - d: - - new thing - - foo thing - - bar thing -``` - -Note that the path is in quotes to avoid the square brackets being interpreted by your shell. - -## Multiple Documents -### Update a single document -Given a sample.yaml file of: -```yaml -something: else ---- -b: - c: 2 -``` -then -```bash -yq w -d1 sample.yaml b.c 5 -``` -will output: -```yaml -something: else ---- -b: - c: 5 -``` - -### Update all documents -Given a sample.yaml file of: -```yaml -something: else ---- -b: - c: 2 -``` -then -```bash -yq w -d'*' sample.yaml b.c 5 -``` -will output: -```yaml -something: else -b: - c: 5 ---- -b: - c: 5 -``` - -UPDATE THIS -UPDATE THIS -INCLUDE DELETE EXAMPLE - -## Updating multiple values with a script -Given a sample.yaml file of: -```yaml -b: - c: 2 - e: - - name: Billy Bob -``` -and a script update_instructions.yaml of: -```yaml -b.c: 3 -b.e[+].name: Howdy Partner -``` -then - -```bash -yq w -s update_instructions.yaml sample.yaml -``` -will output: -```yaml -b: - c: 3 - e: - - name: Howdy Partner -``` - -And, of course, you can pipe the instructions in using '-': -```bash -cat update_instructions.yaml | yq w -s - sample.yaml -```