github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/diff.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "encoding/json" 6 "path/filepath" 7 "strings" 8 9 "github.com/qri-io/ioes" 10 "github.com/qri-io/qri/base/component" 11 "github.com/qri-io/qri/lib" 12 "github.com/spf13/cobra" 13 ) 14 15 // NewDiffCommand creates a new `qri diff` cobra command for comparing changes between datasets 16 func NewDiffCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { 17 o := DiffOptions{IOStreams: ioStreams} 18 cmd := &cobra.Command{ 19 Use: "diff ([COMPONENT] [DATASET [DATASET]])|(PATH PATH)", 20 Short: "compare differences between two data sources", 21 Long: `'qri diff' is a new & experimental feature, please report bugs here: 22 https://github.com/qri-io/deepdiff 23 24 Diff compares two data sources & generates a description of the difference 25 between them. The output of diff describes the steps required to make the 26 element on the left (the first argument) equal the element on the right (the 27 second argument). The steps themselves are the "diff". 28 29 Unlike the classic unix diff utility (which operates on text), 30 qri diff works on structured data. qri diffs are measured in elements 31 (think cells in a spreadsheet), each change is either an insert (added 32 elements), delete (removed elements), or update (changed values). 33 34 Each change has a path that locates it within the document`, 35 Example: ` # Diff between a latest version & the next one back: 36 $ qri diff me/annual_pop 37 38 # Diff current "qri use" selection: 39 $ qri diff 40 41 # Diff dataset body against its last version: 42 $ qri diff body me/annual_pop 43 44 # Diff two dataset meta components: 45 $ qri diff meta me/population_2016 me/population_2017 46 47 # Diff two local json files: 48 $ qri diff a.json b.json 49 50 # Diff a json & csv file: 51 $ qri diff some_table.csv b.json`, 52 Annotations: map[string]string{ 53 "group": "dataset", 54 }, 55 RunE: func(cmd *cobra.Command, args []string) error { 56 if err := o.Complete(f, args); err != nil { 57 return err 58 } 59 return o.Run() 60 }, 61 } 62 63 cmd.Flags().StringVarP(&o.Format, "format", "f", "pretty", "output format. one of [json,pretty]") 64 cmd.Flags().BoolVar(&o.Summary, "summary", false, "just output the summary") 65 66 return cmd 67 } 68 69 // DiffOptions encapsulates options for the diff command 70 type DiffOptions struct { 71 ioes.IOStreams 72 73 Refs *RefSelect 74 Selector string 75 Format string 76 Summary bool 77 78 inst *lib.Instance 79 } 80 81 // Complete adds any missing configuration that can only be added just before calling Run 82 func (o *DiffOptions) Complete(f Factory, args []string) (err error) { 83 if len(args) > 0 && component.IsKnownFilename(args[0], nil) { 84 // Treat a command like `qri diff structure.json` as `qri diff structure`. This mostly 85 // makes sense in the context of FSI. 86 // TODO(dustmop): Consider if we should support this outside of FSI. That is, if a user 87 // has "structure.json" in their current directory (which is not a working directory) and 88 // tries to diff it, that file should be compared to the structure component of the 89 // dataset ref. Currently doesn't happen because we don't support diffing a dataset in 90 // the repository against a local file on disk, but perhaps we should. 91 basename := filepath.Base(args[0]) 92 o.Selector = strings.TrimSuffix(basename, filepath.Ext(basename)) 93 args = args[1:] 94 } 95 if len(args) > 0 && component.IsDatasetField.MatchString(args[0]) { 96 o.Selector = args[0] 97 args = args[1:] 98 } 99 if o.inst, err = f.Instance(); err != nil { 100 return 101 } 102 103 o.Refs, err = GetCurrentRefSelect(f, args, 2) 104 return 105 } 106 107 // Run executes the diff command 108 func (o *DiffOptions) Run() (err error) { 109 p := &lib.DiffParams{ 110 Selector: o.Selector, 111 } 112 113 if len(o.Refs.RefList()) == 1 { 114 // > qri diff me/example_ds 115 // 116 // left = me/example_ds@previous right = me/example_ds@head 117 p.LeftSide = o.Refs.Ref() 118 p.UseLeftPrevVersion = true 119 } else if len(o.Refs.RefList()) == 2 { 120 // > qri diff me/example_ds me/another_ds 121 // 122 // left = me/example_ds@head right = me/another_ds@head 123 //OR 124 // left = path/to/first.json right = path/to/second.json 125 p.LeftSide = o.Refs.RefList()[0] 126 p.RightSide = o.Refs.RefList()[1] 127 } 128 129 ctx := context.TODO() 130 res, err := o.inst.Diff().Diff(ctx, p) 131 if err != nil { 132 return err 133 } 134 135 if o.Format == "json" { 136 json.NewEncoder(o.Out).Encode(res) 137 return 138 } 139 140 return printDiff(o.Out, res, o.Summary) 141 }