Custom path parsing

This commit is contained in:
Mike Farah 2015-10-03 15:10:29 +10:00
parent 01845ea923
commit c4af37ed68
6 changed files with 116 additions and 11 deletions

View File

@ -27,6 +27,18 @@ yaml sample.yaml b.c
``` ```
will output the value of '2'. will output the value of '2'.
### Handling '.' in the yaml key
Given a sample.yaml file of:
```yaml
b.x:
c: 2
```
then
```bash
yaml sample.yaml \"b.x\".c
```
will output the value of '2'.
### Arrays ### Arrays
You can give an index to access a specific element: You can give an index to access a specific element:
e.g.: given a sample file of e.g.: given a sample file of
@ -40,7 +52,7 @@ b:
``` ```
then then
``` ```
yaml sample.yaml b.e.1.name yaml sample.yaml b.e[1].name
``` ```
will output 'sam' will output 'sam'
@ -59,7 +71,3 @@ will output:
b: b:
c: cat c: cat
``` ```
## TODO
* Handling '.' in path names

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
// "fmt"
"log" "log"
"strconv" "strconv"
) )

View File

@ -65,8 +65,9 @@ func assertResult(t *testing.T, expectedValue interface{}, actualValue interface
} }
} }
func assertResultWithContext(t *testing.T, expectedValue interface{}, actualValue interface{}, testDescription string) { func assertResultWithContext(t *testing.T, expectedValue interface{}, actualValue interface{}, context interface{}) {
if expectedValue != actualValue { if expectedValue != actualValue {
t.Error(testDescription, ": expected <", expectedValue, "> but got <", actualValue, ">") t.Error(context)
t.Error(": expected <", expectedValue, "> but got <", actualValue, ">")
} }
} }

55
path_parser.go Normal file
View File

@ -0,0 +1,55 @@
package main
func parsePath(path string) []string {
return parsePathAccum([]string{}, path)
}
func parsePathAccum(paths []string, remaining string) []string {
head, tail := nextYamlPath(remaining)
if tail == "" {
return append(paths, head)
}
return parsePathAccum(append(paths, head), tail)
}
func nextYamlPath(path string) (pathElement string, remaining string) {
switch path[0] {
case '[':
// e.g [0].blah.cat -> we need to return "0" and "blah.cat"
return search(path[1:len(path)], []uint8{']'}, true)
case '"':
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
return search(path[1:len(path)], []uint8{'"'}, true)
default:
// e.g "a.blah.cat" -> return "a" and "blah.cat"
return search(path[0:len(path)], []uint8{'.', '['}, false)
}
}
func search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) {
for i := 0; i < len(path); i++ {
var char = path[i]
if contains(matchingChars, char) {
var remainingStart = i + 1
if skipNext {
remainingStart = remainingStart + 1
} else if !skipNext && char != '.' {
remainingStart = i
}
if remainingStart > len(path) {
remainingStart = len(path)
}
return path[0:i], path[remainingStart:len(path)]
}
}
return path, ""
}
func contains(matchingChars []uint8, candidate uint8) bool {
for _, a := range matchingChars {
if a == candidate {
return true
}
}
return false
}

42
path_parser_test.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"testing"
)
var parsePathsTests = []struct {
path string
expectedPaths []string
}{
{"a.b", []string{"a", "b"}},
{"a.b[0]", []string{"a", "b", "0"}},
}
func testParsePath(t *testing.T) {
for _, tt := range parsePathsTests {
assertResultWithContext(t, tt.expectedPaths, parsePath(tt.path), tt)
}
}
var nextYamlPathTests = []struct {
path string
expectedElement string
expectedRemaining string
}{
{"a.b", "a", "b"},
{"a", "a", ""},
{"a.b.c", "a", "b.c"},
{"\"a.b\".c", "a.b", "c"},
{"a.\"b.c\".d", "a", "\"b.c\".d"},
{"[1].a.d", "1", "a.d"},
{"a[0].c", "a", "[0].c"},
{"[0]", "0", ""},
}
func TestNextYamlPath(t *testing.T) {
for _, tt := range nextYamlPathTests {
var element, remaining = nextYamlPath(tt.path)
assertResultWithContext(t, tt.expectedElement, element, tt)
assertResultWithContext(t, tt.expectedRemaining, remaining, tt)
}
}

View File

@ -51,8 +51,7 @@ func readProperty(c *cli.Context) {
os.Exit(0) os.Exit(0)
} }
var path = c.Args()[1] var paths = parsePath(c.Args()[1])
var paths = strings.Split(path, ".")
printYaml(readMap(parsedData, paths[0], paths[1:len(paths)]), c.Bool("trim")) printYaml(readMap(parsedData, paths[0], paths[1:len(paths)]), c.Bool("trim"))
} }
@ -71,8 +70,7 @@ func writeProperty(c *cli.Context) {
forceString = true forceString = true
} }
var path = c.Args()[1] var paths = parsePath(c.Args()[1])
var paths = strings.Split(path, ".")
write(parsedData, paths[0], paths[1:len(paths)], getValue(c.Args()[2], forceString)) write(parsedData, paths[0], paths[1:len(paths)], getValue(c.Args()[2], forceString))