Skip to content

Commit

Permalink
Merge pull request #23 from yazgazan/streams
Browse files Browse the repository at this point in the history
Adding support for JSON streams
  • Loading branch information
yazgazan authored Oct 2, 2019
2 parents 1d108a9 + 13c9fdb commit deac9ea
Show file tree
Hide file tree
Showing 20 changed files with 1,424 additions and 428 deletions.
52 changes: 42 additions & 10 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,22 @@ Usage:
jaydiff [OPTIONS] FILE_1 FILE_2
Application Options:
-i, --ignore= paths to ignore (glob)
--indent= indent string (default: "\t")
-t, --show-types show types
--json json-style output
--ignore-excess ignore excess keys and arrey elements
--ignore-values ignore scalar's values (only type is compared)
-r, --report output report format
--slice-myers use myers algorithm for slices
-v, --version print release version
-i, --ignore= paths to ignore (glob)
--indent= indent string (default: "\t")
-t, --show-types show types
--json json-style output
--ignore-excess ignore excess keys and array elements
--ignore-values ignore scalar's values (only type is compared)
-r, --report output report format
--slice-myers use myers algorithm for slices
--stream treat FILE_1 and FILE_2 as JSON streams
--stream-lines read JSON stream line by line (expecting 1 JSON value per line)
--stream-ignore-excess ignore excess values in JSON stream
--stream-validate compare FILE_2 JSON stream against FILE_1 single value
-v, --version print release version
Help Options:
-h, --help Show this help message
-h, --help Show this help message
```

### Examples
Expand Down Expand Up @@ -159,6 +163,34 @@ $ jaydiff --report --show-types --ignore-excess --ignore-values old.json new.jso
- .f: float64 42
```

JSON streams:

```diff
$ jaydiff --stream --json old.json new.json

[
{"foo":"bar"},
[
2,
3,
4,
{
+ "v": "some"
}
],
+ {"some":"thing"}
]
```

Validating JSON stream types:

```diff
$ jaydiff --ignore-excess --ignore-values --stream-validate --report --show-types base.json stream.json

- [1].bar: float64 4.2
+ [1].bar: string !
```

## Ideas

- JayPatch
Expand Down
28 changes: 21 additions & 7 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ type config struct {
Files files `positional-args:"yes" required:"yes"`
Ignore ignorePatterns `long:"ignore" short:"i" description:"paths to ignore (glob)"`
output
IgnoreExcess bool `long:"ignore-excess" description:"ignore excess keys and arrey elements"`
IgnoreValues bool `long:"ignore-values" description:"ignore scalar's values (only type is compared)"`
OutputReport bool `long:"report" short:"r" description:"output report format"`
UseSliceMyers bool `long:"slice-myers" description:"use myers algorithm for slices"`
Version func() `long:"version" short:"v" description:"print release version"`
IgnoreExcess bool `long:"ignore-excess" description:"ignore excess keys and array elements"`
IgnoreValues bool `long:"ignore-values" description:"ignore scalar's values (only type is compared)"`
OutputReport bool `long:"report" short:"r" description:"output report format"`
UseSliceMyers bool `long:"slice-myers" description:"use myers algorithm for slices"`

Stream bool `long:"stream" description:"treat FILE_1 and FILE_2 as JSON streams"`
StreamLines bool `long:"stream-lines" description:"read JSON stream line by line (expecting 1 JSON value per line)"`
StreamIgnoreExcess bool `long:"stream-ignore-excess" description:"ignore excess values in JSON stream"`
StreamValidate bool `long:"stream-validate" description:"compare FILE_2 JSON stream against FILE_1 single value"`

Version func() `long:"version" short:"v" description:"print release version"`
}

type output struct {
Expand Down Expand Up @@ -53,16 +59,24 @@ func readConfig() config {
fmt.Fprintf(os.Stderr, "Incompatible options --json and --show-types\n")
os.Exit(statusUsage)
}

c.InferFlags()

return c
}

func (c *config) InferFlags() {
if c.JSON {
c.JSONValues = true
}
if c.JSON && c.OutputReport {
c.JSON = false
}
if c.StreamLines || c.StreamValidate {
c.Stream = true
}

c.output.Colorized = terminal.IsTerminal(int(os.Stdout.Fd()))

return c
}

func (c config) Opts() []diff.ConfigOpt {
Expand Down
57 changes: 41 additions & 16 deletions diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type diffFn func(c config, lhs, rhs interface{}, visited *visited) (Differ, erro

// Diff generates a tree representing differences and similarities between two objects.
//
// Diff supports maps, slices and scalars (comparables types such as int, string, etc ...).
// Diff supports maps, slices, Stream and scalars (comparables types such as int, string, etc ...).
// When an unsupported type is encountered, an ErrUnsupported error is returned.
func Diff(lhs, rhs interface{}, opts ...ConfigOpt) (Differ, error) {
c := defaultConfig()
Expand All @@ -56,26 +56,38 @@ func diff(c config, lhs, rhs interface{}, visited *visited) (Differ, error) {
return types{lhs, rhs}, ErrCyclic
}

if valueIsScalar(lhsVal) && valueIsScalar(rhsVal) {
return scalar{lhs, rhs}, nil
return diffValues(c, lhsVal, rhsVal, visited)
}

func diffValues(c config, lhs, rhs reflect.Value, visited *visited) (Differ, error) {
if valueIsStream(lhs) && valueIsStream(rhs) {
return newStream(c, lhs.Interface(), rhs.Interface(), visited)
}

if valueIsScalar(lhs) && valueIsScalar(rhs) {
return scalar{lhs.Interface(), rhs.Interface()}, nil
}
if lhsVal.Kind() != rhsVal.Kind() {
return types{lhs, rhs}, nil
if lhs.Kind() != rhs.Kind() {
return types{lhs.Interface(), rhs.Interface()}, nil
}

switch lhsVal.Kind() {
switch lhs.Kind() {
case reflect.Slice, reflect.Array:
return c.sliceFn(c, lhs, rhs, visited)
return c.sliceFn(c, lhs.Interface(), rhs.Interface(), visited)
case reflect.Map:
return newMap(c, lhs, rhs, visited)
return newMap(c, lhs.Interface(), rhs.Interface(), visited)
case reflect.Struct:
return newStruct(c, lhs, rhs, visited)
return newStruct(c, lhs.Interface(), rhs.Interface(), visited)
}

return types{lhs, rhs}, &ErrUnsupported{lhsVal.Type(), rhsVal.Type()}
return types{lhs.Interface(), rhs.Interface()}, &ErrUnsupported{lhs.Type(), rhs.Type()}
}

func indirectValueOf(i interface{}) (reflect.Value, interface{}) {
if _, ok := i.(Stream); ok {
return reflect.ValueOf(i), i
}

v := reflect.Indirect(reflect.ValueOf(i))
if !v.IsValid() || !v.CanInterface() {
return reflect.ValueOf(i), i
Expand All @@ -84,6 +96,16 @@ func indirectValueOf(i interface{}) (reflect.Value, interface{}) {
return v, v.Interface()
}

func valueIsStream(v reflect.Value) bool {
if !v.IsValid() || !v.CanInterface() {
return false
}

_, ok := v.Interface().(Stream)

return ok
}

func valueIsScalar(v reflect.Value) bool {
switch v.Kind() {
default:
Expand Down Expand Up @@ -122,9 +144,7 @@ func IsExcess(d Differ) bool {
switch d.(type) {
default:
return false
case mapExcess:
return true
case sliceExcess:
case mapExcess, sliceExcess, streamExcess:
return true
}
}
Expand All @@ -134,9 +154,7 @@ func IsMissing(d Differ) bool {
switch d.(type) {
default:
return false
case mapMissing:
return true
case sliceMissing:
case mapMissing, sliceMissing, streamMissing:
return true
}
}
Expand Down Expand Up @@ -176,6 +194,13 @@ func IsSlice(d Differ) bool {
return ok
}

// IsStream returns true if d is a diff between towo slices
func IsStream(d Differ) bool {
_, ok := d.(stream)

return ok
}

type lhsGetter interface {
LHS() interface{}
}
Expand Down
Loading

0 comments on commit deac9ea

Please # to comment.