Skip to content

Commit c4af37e

Browse files
committed
Custom path parsing
1 parent 01845ea commit c4af37e

File tree

6 files changed

+116
-11
lines changed

6 files changed

+116
-11
lines changed

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ yaml sample.yaml b.c
2727
```
2828
will output the value of '2'.
2929

30+
### Handling '.' in the yaml key
31+
Given a sample.yaml file of:
32+
```yaml
33+
b.x:
34+
c: 2
35+
```
36+
then
37+
```bash
38+
yaml sample.yaml \"b.x\".c
39+
```
40+
will output the value of '2'.
41+
3042
### Arrays
3143
You can give an index to access a specific element:
3244
e.g.: given a sample file of
@@ -40,7 +52,7 @@ b:
4052
```
4153
then
4254
```
43-
yaml sample.yaml b.e.1.name
55+
yaml sample.yaml b.e[1].name
4456
```
4557
will output 'sam'
4658

@@ -59,7 +71,3 @@ will output:
5971
b:
6072
c: cat
6173
```
62-
63-
64-
## TODO
65-
* Handling '.' in path names

data_navigator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
// "fmt"
45
"log"
56
"strconv"
67
)

data_navigator_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ func assertResult(t *testing.T, expectedValue interface{}, actualValue interface
6565
}
6666
}
6767

68-
func assertResultWithContext(t *testing.T, expectedValue interface{}, actualValue interface{}, testDescription string) {
68+
func assertResultWithContext(t *testing.T, expectedValue interface{}, actualValue interface{}, context interface{}) {
6969
if expectedValue != actualValue {
70-
t.Error(testDescription, ": expected <", expectedValue, "> but got <", actualValue, ">")
70+
t.Error(context)
71+
t.Error(": expected <", expectedValue, "> but got <", actualValue, ">")
7172
}
7273
}

path_parser.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
func parsePath(path string) []string {
4+
return parsePathAccum([]string{}, path)
5+
}
6+
7+
func parsePathAccum(paths []string, remaining string) []string {
8+
head, tail := nextYamlPath(remaining)
9+
if tail == "" {
10+
return append(paths, head)
11+
}
12+
return parsePathAccum(append(paths, head), tail)
13+
}
14+
15+
func nextYamlPath(path string) (pathElement string, remaining string) {
16+
switch path[0] {
17+
case '[':
18+
// e.g [0].blah.cat -> we need to return "0" and "blah.cat"
19+
return search(path[1:len(path)], []uint8{']'}, true)
20+
case '"':
21+
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
22+
return search(path[1:len(path)], []uint8{'"'}, true)
23+
default:
24+
// e.g "a.blah.cat" -> return "a" and "blah.cat"
25+
return search(path[0:len(path)], []uint8{'.', '['}, false)
26+
}
27+
}
28+
29+
func search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) {
30+
for i := 0; i < len(path); i++ {
31+
var char = path[i]
32+
if contains(matchingChars, char) {
33+
var remainingStart = i + 1
34+
if skipNext {
35+
remainingStart = remainingStart + 1
36+
} else if !skipNext && char != '.' {
37+
remainingStart = i
38+
}
39+
if remainingStart > len(path) {
40+
remainingStart = len(path)
41+
}
42+
return path[0:i], path[remainingStart:len(path)]
43+
}
44+
}
45+
return path, ""
46+
}
47+
48+
func contains(matchingChars []uint8, candidate uint8) bool {
49+
for _, a := range matchingChars {
50+
if a == candidate {
51+
return true
52+
}
53+
}
54+
return false
55+
}

path_parser_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
var parsePathsTests = []struct {
8+
path string
9+
expectedPaths []string
10+
}{
11+
{"a.b", []string{"a", "b"}},
12+
{"a.b[0]", []string{"a", "b", "0"}},
13+
}
14+
15+
func testParsePath(t *testing.T) {
16+
for _, tt := range parsePathsTests {
17+
assertResultWithContext(t, tt.expectedPaths, parsePath(tt.path), tt)
18+
}
19+
}
20+
21+
var nextYamlPathTests = []struct {
22+
path string
23+
expectedElement string
24+
expectedRemaining string
25+
}{
26+
{"a.b", "a", "b"},
27+
{"a", "a", ""},
28+
{"a.b.c", "a", "b.c"},
29+
{"\"a.b\".c", "a.b", "c"},
30+
{"a.\"b.c\".d", "a", "\"b.c\".d"},
31+
{"[1].a.d", "1", "a.d"},
32+
{"a[0].c", "a", "[0].c"},
33+
{"[0]", "0", ""},
34+
}
35+
36+
func TestNextYamlPath(t *testing.T) {
37+
for _, tt := range nextYamlPathTests {
38+
var element, remaining = nextYamlPath(tt.path)
39+
assertResultWithContext(t, tt.expectedElement, element, tt)
40+
assertResultWithContext(t, tt.expectedRemaining, remaining, tt)
41+
}
42+
}

yaml.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ func readProperty(c *cli.Context) {
5151
os.Exit(0)
5252
}
5353

54-
var path = c.Args()[1]
55-
var paths = strings.Split(path, ".")
54+
var paths = parsePath(c.Args()[1])
5655

5756
printYaml(readMap(parsedData, paths[0], paths[1:len(paths)]), c.Bool("trim"))
5857
}
@@ -71,8 +70,7 @@ func writeProperty(c *cli.Context) {
7170
forceString = true
7271
}
7372

74-
var path = c.Args()[1]
75-
var paths = strings.Split(path, ".")
73+
var paths = parsePath(c.Args()[1])
7674

7775
write(parsedData, paths[0], paths[1:len(paths)], getValue(c.Args()[2], forceString))
7876

0 commit comments

Comments
 (0)