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  }