diff --git a/build.sh b/build.sh index 3c88440..40c7730 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -VERSION="0.1.0" +VERSION="0.1.1" PROTECTED_MODE="no" export GO15VENDOREXPERIMENT=1 diff --git a/cmd/jsoned/main.go b/cmd/jsoned/main.go index 6671695..ab829e3 100644 --- a/cmd/jsoned/main.go +++ b/cmd/jsoned/main.go @@ -15,7 +15,7 @@ var ( version = "0.0.1" tag = "jsoned - JSON Stream Editor " + version usage = ` -usage: jsoned [-v value] [-s] [-i infile] [-o outfile] keypath +usage: jsoned [-v value] [-s] [-D] [-i infile] [-o outfile] keypath examples: jsoned keypath read value from stdin or: jsoned -i infile keypath read value from infile @@ -24,6 +24,7 @@ examples: jsoned keypath read value from stdin options: -v value Edit JSON key path value + -D Delete the value at the specified key path -i infile Use input file instead of stdin -o outfile Use output file instead of stdout -r Use raw values, otherwise types are auto-detected @@ -38,6 +39,7 @@ type args struct { outfile *string value *string raw bool + del bool keypath string } @@ -82,6 +84,8 @@ func parseArgs() args { } case "-r": a.raw = true + case "-D": + a.del = true case "-h", "--help", "-?": help() } @@ -107,7 +111,12 @@ func main() { if err != nil { goto fail } - if a.value != nil { + if a.del { + outb, err = sjson.DeleteBytes(input, a.keypath) + if err != nil { + goto fail + } + } else if a.value != nil { raw := a.raw val := *a.value if !raw { diff --git a/resources/vendor.sh b/resources/vendor.sh deleted file mode 100755 index 0a603dd..0000000 --- a/resources/vendor.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -set -e - -cd $(dirname "${BASH_SOURCE[0]}")/.. - -if [ "$1" == "" ]; then - echo missing argument -else - mkdir -p vendor/$1 - cp -rf ${GOPATH}/src/$1/ vendor/$1/ - if [ -d vendor/$1/.git ]; then - wd=`pwd` - cd vendor/$1 - git clean -fd - cd "$wd" - fi - rm -rf vendor/$1/.git \ - vendor/$1/.bzr \ - vendor/$1/.hg \ - vendor/$1/.svn -fi diff --git a/vendor/github.com/tidwall/sjson/README.md b/vendor/github.com/tidwall/sjson/README.md index 4505971..249556a 100644 --- a/vendor/github.com/tidwall/sjson/README.md +++ b/vendor/github.com/tidwall/sjson/README.md @@ -9,8 +9,8 @@

set a json value quickly

-SJSON is a Go package the provides a **very fast** and simple way to set a value in a json document. The reason for this library it to provide efficient json updating for the [SummitDB](https://github.com/tidwall/summitdb) project. -For quickly retrieving json values check out the [GJSON](https://github.com/tidwall/gjson). +SJSON is a Go package that provides a very fast and simple way to set a value in a json document. The purpose for this library is to provide efficient json updating for the [SummitDB](https://github.com/tidwall/summitdb) project. +For quickly retrieving json values check out [GJSON](https://github.com/tidwall/gjson). Getting Started =============== @@ -30,7 +30,7 @@ Set a value ----------- Set sets the value for the specified path. A path is in dot syntax, such as "name.last" or "age". -This function expects that the json is well-formed and validates. +This function expects that the json is well-formed and validated. Invalid json will not panic, but it may return back unexpected results. Invalid paths may return an error. @@ -184,6 +184,33 @@ println(value) // {"friends":["Andy","Carol",null,null,"Sara"] ``` +Delete a value: +```go +value, _ := sjson.Delete(`{"name":{"first":"Sara","last":"Anderson"}}`, "name.first") +println(value) + +// Output: +// {"name":{"last":"Anderson"}} +``` + +Delete an array value: +```go +value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.1") +println(value) + +// Output: +// {"friends":["Andy"]} +``` + +Delete the last array value: +```go +value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.-1") +println(value) + +// Output: +// {"friends":["Andy"]} +``` + ## Contact Josh Baker [@tidwall](http://twitter.com/tidwall) diff --git a/vendor/github.com/tidwall/sjson/sjson.go b/vendor/github.com/tidwall/sjson/sjson.go index 2feebf5..2aad9fd 100644 --- a/vendor/github.com/tidwall/sjson/sjson.go +++ b/vendor/github.com/tidwall/sjson/sjson.go @@ -163,13 +163,71 @@ func trim(s string) string { return s } -func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string, stringify bool) ([]byte, error) { +// deleteTailItem deletes the previous key or comma. +func deleteTailItem(buf []byte) ([]byte, bool) { +loop: + for i := len(buf) - 1; i >= 0; i-- { + // look for either a ',',':','[' + switch buf[i] { + case '[': + return buf, true + case ',': + return buf[:i], false + case ':': + // delete tail string + i-- + for ; i >= 0; i-- { + if buf[i] == '"' { + i-- + for ; i >= 0; i-- { + if buf[i] == '"' { + i-- + if i >= 0 && i == '\\' { + i-- + continue + } + for ; i >= 0; i-- { + // look for either a ',','{' + switch buf[i] { + case '{': + return buf[:i+1], true + case ',': + return buf[:i], false + } + } + } + } + break + } + } + break loop + } + } + return buf, false +} + +var errNoChange = &errorType{"no change"} + +func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string, stringify, del bool) ([]byte, error) { var err error - res := gjson.Get(jstr, paths[0].part) + var res gjson.Result + var found bool + if del { + if paths[0].part == "-1" && !paths[0].force { + res = gjson.Get(jstr, "#") + if res.Int() > 0 { + res = gjson.Get(jstr, strconv.FormatInt(int64(res.Int()-1), 10)) + found = true + } + } + } + if !found { + res = gjson.Get(jstr, paths[0].part) + } if res.Index > 0 { if len(paths) > 1 { buf = append(buf, jstr[:res.Index]...) - buf, err = appendRawPaths(buf, res.Raw, paths[1:], raw, stringify) + buf, err = appendRawPaths(buf, res.Raw, paths[1:], raw, stringify, del) if err != nil { return nil, err } @@ -177,14 +235,34 @@ func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string, str return buf, nil } buf = append(buf, jstr[:res.Index]...) - if stringify { - buf = appendStringify(buf, raw) + var exidx int // addional forward stripping + if del { + var delNextComma bool + buf, delNextComma = deleteTailItem(buf) + if delNextComma { + for i, j := res.Index+len(res.Raw), 0; i < len(jstr); i, j = i+1, j+1 { + if jstr[i] <= ' ' { + continue + } + if jstr[i] == ',' { + exidx = j + 1 + } + break + } + } } else { - buf = append(buf, raw...) + if stringify { + buf = appendStringify(buf, raw) + } else { + buf = append(buf, raw...) + } } - buf = append(buf, jstr[res.Index+len(res.Raw):]...) + buf = append(buf, jstr[res.Index+len(res.Raw)+exidx:]...) return buf, nil } + if del { + return nil, errNoChange + } n, numeric := atoui(paths[0]) isempty := true for i := 0; i < len(jstr); i++ { @@ -271,7 +349,7 @@ func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string, str } } -func set(jstr, path, raw string, stringify bool) ([]byte, error) { +func set(jstr, path, raw string, stringify, del bool) ([]byte, error) { // parse the path, make sure that it does not contain invalid characters // such as '#', '?', '*' if path == "" { @@ -290,7 +368,7 @@ func set(jstr, path, raw string, stringify bool) ([]byte, error) { paths = append(paths, r) } - njson, err := appendRawPaths(nil, jstr, paths, raw, stringify) + njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del) if err != nil { return nil, err } @@ -326,11 +404,26 @@ func Set(json, path string, value interface{}) (string, error) { return string(res), err } +type dtype struct{} + +// Delete deletes a value from json for the specified path. +func Delete(json, path string) (string, error) { + return Set(json, path, dtype{}) +} + +// DeleteBytes deletes a value from json for the specified path. +func DeleteBytes(json []byte, path string) ([]byte, error) { + return SetBytes(json, path, dtype{}) +} + // SetRaw sets a raw json value for the specified path. The works the same as // Set except that the value is set as a raw block of json. This allows for setting // premarshalled json objects. func SetRaw(json, path, value string) (string, error) { - res, err := set(json, path, value, false) + res, err := set(json, path, value, false, false) + if err == errNoChange { + return json, nil + } return string(res), err } @@ -339,7 +432,11 @@ func SetRaw(json, path, value string) (string, error) { func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) { jstr := *(*string)(unsafe.Pointer(&json)) vstr := *(*string)(unsafe.Pointer(&value)) - return set(jstr, path, vstr, false) + res, err := set(jstr, path, vstr, false, false) + if err == errNoChange { + return json, nil + } + return res, nil } // SetBytes sets a json value for the specified path. @@ -355,38 +452,43 @@ func SetBytes(json []byte, path string, value interface{}) ([]byte, error) { return nil, err } raw := *(*string)(unsafe.Pointer(&b)) - res, err = set(jstr, path, raw, false) + res, err = set(jstr, path, raw, false, false) + case dtype: + res, err = set(jstr, path, "", false, true) case string: - res, err = set(jstr, path, v, true) + res, err = set(jstr, path, v, true, false) case []byte: raw := *(*string)(unsafe.Pointer(&v)) - res, err = set(jstr, path, raw, true) + res, err = set(jstr, path, raw, true, false) case bool: if v { - res, err = set(jstr, path, "true", false) + res, err = set(jstr, path, "true", false, false) } else { - res, err = set(jstr, path, "false", false) + res, err = set(jstr, path, "false", false, false) } case int8: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false) + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false, false) case int16: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false) + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false, false) case int32: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false) + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false, false) case int64: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false) + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), false, false) case uint8: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false) + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false, false) case uint16: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false) + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false, false) case uint32: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false) + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false, false) case uint64: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false) + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), false, false) case float32: - res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), false) + res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), false, false) case float64: - res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), false) + res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), false, false) + } + if err == errNoChange { + return json, nil } return res, err } diff --git a/vendor/github.com/tidwall/sjson/sjson_test.go b/vendor/github.com/tidwall/sjson/sjson_test.go index 68d8f89..4c7119f 100644 --- a/vendor/github.com/tidwall/sjson/sjson_test.go +++ b/vendor/github.com/tidwall/sjson/sjson_test.go @@ -45,6 +45,7 @@ const ( setInt = 3 setFloat = 4 setString = 5 + setDelete = 6 ) func testRaw(t *testing.T, kind int, expect, json, path string, value interface{}) { @@ -55,6 +56,8 @@ func testRaw(t *testing.T, kind int, expect, json, path string, value interface{ json2, err = Set(json, path, value) case setRaw: json2, err = SetRaw(json, path, value.(string)) + case setDelete: + json2, err = Delete(json, path) } if err != nil { t.Fatal(err) @@ -68,6 +71,8 @@ func testRaw(t *testing.T, kind int, expect, json, path string, value interface{ json3, err = SetBytes([]byte(json), path, value) case setRaw: json3, err = SetRawBytes([]byte(json), path, []byte(value.(string))) + case setDelete: + json3, err = DeleteBytes([]byte(json), path) } if err != nil { t.Fatal(err) @@ -125,6 +130,18 @@ func TestBasic(t *testing.T) { testRaw(t, setFloat, `[1234.5]`, ``, `0`, float64(1234.5)) testRaw(t, setString, `["1234.5"]`, ``, `0`, "1234.5") testRaw(t, setBool, `[true]`, ``, `0`, true) + testRaw(t, setBool, `[null]`, ``, `0`, nil) +} + +func TestDelete(t *testing.T) { + testRaw(t, setDelete, `[456]`, `[123,456]`, `0`, nil) + testRaw(t, setDelete, `[123,789]`, `[123,456,789]`, `1`, nil) + testRaw(t, setDelete, `[123,456]`, `[123,456,789]`, `-1`, nil) + testRaw(t, setDelete, `{"a":[123,456]}`, `{"a":[123,456,789]}`, `a.-1`, nil) + testRaw(t, setDelete, `{"and":"another"}`, `{"this":"that","and":"another"}`, `this`, nil) + testRaw(t, setDelete, `{"this":"that"}`, `{"this":"that","and":"another"}`, `and`, nil) + testRaw(t, setDelete, `{}`, `{"and":"another"}`, `and`, nil) + testRaw(t, setDelete, `{"1":"2"}`, `{"1":"2"}`, `3`, nil) } // TestRandomData is a fuzzing test that throws random data at SetRaw