github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/dag.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "encoding/hex" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "path/filepath" 10 "strings" 11 12 "github.com/dustin/go-humanize" 13 "github.com/ghodss/yaml" 14 "github.com/qri-io/dag" 15 "github.com/qri-io/ioes" 16 "github.com/qri-io/qri/base/component" 17 "github.com/qri-io/qri/lib" 18 "github.com/spf13/cobra" 19 ) 20 21 // NewDAGCommand creates a new `qri dag` command that generates a manifest for a given 22 // dataset reference. Referenced dataset must be stored in local CAFS 23 func NewDAGCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { 24 o := &DAGOptions{IOStreams: ioStreams} 25 cmd := &cobra.Command{ 26 Use: "dag", 27 Hidden: true, 28 Short: "directed acyclic graph interaction", 29 } 30 31 manifest := &cobra.Command{ 32 Use: "manifest", 33 Short: "dataset manifest interaction", 34 } 35 36 get := &cobra.Command{ 37 Use: "get DATASET [DATASET...]", 38 Short: "get manifests for one or more dataset references", 39 Args: cobra.MinimumNArgs(1), 40 RunE: func(cmd *cobra.Command, args []string) error { 41 if err := o.Complete(f, args, false); err != nil { 42 return err 43 } 44 return o.Get() 45 }, 46 } 47 48 missing := &cobra.Command{ 49 Use: "missing --file MANIFEST_PATH", 50 Short: "list blocks not present in this repo for a given manifest", 51 Args: cobra.NoArgs, 52 RunE: func(cmd *cobra.Command, args []string) error { 53 if err := o.Complete(f, args, false); err != nil { 54 return err 55 } 56 return o.Missing() 57 }, 58 } 59 60 get.Flags().StringVar(&o.Format, "format", "json", "set output format [json, yaml, cbor]") 61 get.Flags().BoolVar(&o.Pretty, "pretty", false, "print output without indentation, only applies to json format") 62 get.Flags().BoolVar(&o.Hex, "hex", false, "hex-encode output") 63 64 missing.Flags().StringVar(&o.Format, "format", "json", "set output format [json, yaml, cbor]") 65 missing.Flags().BoolVar(&o.Pretty, "pretty", false, "print output without indentation, only applies to json format") 66 missing.Flags().BoolVar(&o.Hex, "hex", false, "hex-encode output") 67 missing.Flags().StringVar(&o.File, "file", "", "manifest file") 68 missing.MarkFlagRequired("file") 69 70 manifest.AddCommand(get, missing) 71 72 info := &cobra.Command{ 73 Use: "info [LABEL] DATASET [DATASET...]", 74 Short: "dataset dag info interaction", 75 RunE: func(cmd *cobra.Command, args []string) error { 76 if err := o.Complete(f, args, true); err != nil { 77 return err 78 } 79 return o.Info() 80 }, 81 } 82 83 info.Flags().StringVar(&o.InfoFormat, "format", "", "set output format [json, yaml, cbor]") 84 info.Flags().BoolVar(&o.Pretty, "pretty", false, "print output without indentation, only applies to json format") 85 info.Flags().BoolVar(&o.Hex, "hex", false, "hex-encode output") 86 87 cmd.AddCommand(manifest, info) 88 return cmd 89 } 90 91 // DAGOptions encapsulates state for the dag command 92 type DAGOptions struct { 93 ioes.IOStreams 94 95 Refs []string 96 Format string 97 InfoFormat string 98 Pretty bool 99 Hex bool 100 File string 101 Label string 102 103 inst *lib.Instance 104 } 105 106 // Complete adds any missing configuration that can only be added just before calling Run 107 func (o *DAGOptions) Complete(f Factory, args []string, parseLabel bool) (err error) { 108 if parseLabel && len(args) > 0 { 109 if component.IsDatasetField.MatchString(args[0]) { 110 o.Label = fullFieldToAbbr(args[0]) 111 args = args[1:] 112 } 113 } 114 o.Refs = args 115 o.inst, err = f.Instance() 116 return 117 } 118 119 // Get executes the manifest get command 120 func (o *DAGOptions) Get() (err error) { 121 ctx := context.TODO() 122 mf := &dag.Manifest{} 123 for _, ref := range o.Refs { 124 if mf, err = o.inst.Dataset().Manifest(ctx, &lib.ManifestParams{Ref: ref}); err != nil { 125 return err 126 } 127 128 var buffer []byte 129 switch strings.ToLower(o.Format) { 130 case "json": 131 if !o.Pretty { 132 buffer, err = json.Marshal(mf) 133 } else { 134 buffer, err = json.MarshalIndent(mf, "", " ") 135 } 136 case "yaml": 137 buffer, err = yaml.Marshal(mf) 138 case "cbor": 139 buffer, err = mf.MarshalCBOR() 140 } 141 if err != nil { 142 return fmt.Errorf("err encoding manifest: %s", err) 143 } 144 if o.Hex { 145 buffer = []byte(hex.EncodeToString(buffer)) 146 } 147 _, err = o.Out.Write(buffer) 148 } 149 150 return err 151 } 152 153 // Missing executes the manifest missing command 154 func (o *DAGOptions) Missing() error { 155 ctx := context.TODO() 156 in := &dag.Manifest{} 157 data, err := ioutil.ReadFile(o.File) 158 if err != nil { 159 return err 160 } 161 162 switch strings.ToLower(filepath.Ext(o.File)) { 163 case ".yaml": 164 err = yaml.Unmarshal(data, in) 165 case ".json": 166 err = json.Unmarshal(data, in) 167 case ".cbor": 168 // TODO - detect hex input? 169 // data, err = hex.DecodeString(string(data)) 170 // if err != nil { 171 // return err 172 // } 173 in, err = dag.UnmarshalCBORManifest(data) 174 } 175 176 if err != nil { 177 return err 178 } 179 180 mf, err := o.inst.Dataset().ManifestMissing(ctx, &lib.ManifestMissingParams{Manifest: in}) 181 if err != nil { 182 return err 183 } 184 185 var buffer []byte 186 switch strings.ToLower(o.InfoFormat) { 187 case "json": 188 if !o.Pretty { 189 buffer, err = json.Marshal(mf) 190 } else { 191 buffer, err = json.MarshalIndent(mf, "", " ") 192 } 193 case "yaml": 194 buffer, err = yaml.Marshal(mf) 195 case "cbor": 196 buffer, err = mf.MarshalCBOR() 197 } 198 if err != nil { 199 return fmt.Errorf("error encoding manifest: %s", err) 200 } 201 if o.Hex { 202 buffer = []byte(hex.EncodeToString(buffer)) 203 } 204 _, err = o.Out.Write(buffer) 205 206 return err 207 } 208 209 // Info executes the dag info command 210 func (o *DAGOptions) Info() (err error) { 211 ctx := context.TODO() 212 info := &dag.Info{} 213 if len(o.Refs) == 0 { 214 return fmt.Errorf("dataset reference required") 215 } 216 217 for _, ref := range o.Refs { 218 s := &lib.DAGInfoParams{Ref: ref, Label: o.Label} 219 info, err = o.inst.Dataset().DAGInfo(ctx, s) 220 if err != nil { 221 return err 222 } 223 224 var buffer []byte 225 switch strings.ToLower(o.InfoFormat) { 226 case "json": 227 if !o.Pretty { 228 buffer, err = json.Marshal(info) 229 } else { 230 buffer, err = json.MarshalIndent(info, "", " ") 231 } 232 case "yaml": 233 buffer, err = yaml.Marshal(info) 234 // case "cbor": 235 // buffer, err = info.MarshalCBOR() 236 default: 237 totalSize := uint64(0) 238 if len(info.Sizes) != 0 { 239 totalSize = info.Sizes[0] 240 } 241 out := "" 242 if o.Label != "" { 243 out += fmt.Sprintf("\nSubDAG at: %s", abbrFieldToFull(o.Label)) 244 } 245 out += fmt.Sprintf("\nDAG for: %s\n", ref) 246 if totalSize != 0 { 247 out += fmt.Sprintf("Total Size: %s\n", humanize.Bytes(totalSize)) 248 } 249 if info.Manifest != nil { 250 out += fmt.Sprintf("Block Count: %d\n", len(info.Manifest.Nodes)) 251 } 252 if info.Labels != nil { 253 out += fmt.Sprint("Labels:\n") 254 } 255 for label, index := range info.Labels { 256 fullField := abbrFieldToFull(label) 257 out += fmt.Sprintf("\t%s:", fullField) 258 spacesLen := 16 - len(fullField) 259 for i := 0; i <= spacesLen; i++ { 260 out += fmt.Sprintf(" ") 261 } 262 out += fmt.Sprintf("%s\n", humanize.Bytes(info.Sizes[index])) 263 } 264 buffer = []byte(out) 265 266 } 267 if err != nil { 268 return fmt.Errorf("err encoding daginfo: %s", err) 269 } 270 if o.Hex { 271 buffer = []byte(hex.EncodeToString(buffer)) 272 } 273 _, err = o.Out.Write(buffer) 274 } 275 276 return err 277 } 278 279 func fullFieldToAbbr(field string) string { 280 switch field { 281 case "commit": 282 return "cm" 283 case "structure": 284 return "st" 285 case "body": 286 return "bd" 287 case "meta": 288 return "md" 289 case "viz": 290 return "vz" 291 case "transform": 292 return "tf" 293 case "rendered": 294 return "rd" 295 default: 296 return field 297 } 298 } 299 300 func abbrFieldToFull(abbr string) string { 301 switch abbr { 302 case "cm": 303 return "commit" 304 case "st": 305 return "structure" 306 case "bd": 307 return "body" 308 case "md": 309 return "meta" 310 case "vz": 311 return "viz" 312 case "tf": 313 return "transform" 314 case "rd": 315 return "rendered" 316 default: 317 return abbr 318 } 319 }