github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/get.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  
    10  	"github.com/ghodss/yaml"
    11  	"github.com/qri-io/ioes"
    12  	"github.com/qri-io/qri/base/component"
    13  	"github.com/qri-io/qri/base/params"
    14  	"github.com/qri-io/qri/lib"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  // NewGetCommand creates a new `qri search` command that searches for datasets
    19  func NewGetCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
    20  	o := &GetOptions{IOStreams: ioStreams}
    21  	cmd := &cobra.Command{
    22  		Use:   "get [COMPONENT] [DATASET]",
    23  		Short: "get components of qri datasets",
    24  		Long: `Get the qri dataset (except for the body). You can also get components of 
    25  the dataset: meta, structure, viz, transform, and commit. To narrow down
    26  further to specific fields in each section, use dot notation. The get 
    27  command prints to the console in yaml format, by default.
    28  
    29  Check out https://qri.io/docs/reference/dataset/ to learn about each section of the 
    30  dataset and its fields.`,
    31  		Example: `  # Print the entire dataset to the console:
    32    $ qri get me/annual_pop
    33  
    34    # Print the meta to the console:
    35    $ qri get meta me/annual_pop
    36  
    37    # Print the dataset body size to the console:
    38    $ qri get structure.length me/annual_pop`,
    39  		Annotations: map[string]string{
    40  			"group": "dataset",
    41  		},
    42  		Args: cobra.MaximumNArgs(2),
    43  		RunE: func(cmd *cobra.Command, args []string) error {
    44  			if err := o.Complete(f, args); err != nil {
    45  				return err
    46  			}
    47  			return o.Run()
    48  		},
    49  	}
    50  
    51  	cmd.Flags().StringVarP(&o.Format, "format", "f", "", "set output format [json, yaml, csv, zip]. If format is set to 'zip' it will save the entire dataset as a zip archive.")
    52  	cmd.Flags().BoolVar(&o.Pretty, "pretty", false, "whether to print output with indentation, only for json format")
    53  	cmd.Flags().IntVar(&o.Limit, "limit", -1, "for body, limit how many entries to get per request")
    54  	cmd.Flags().IntVar(&o.Offset, "offset", -1, "for body, offset amount at which to get entries")
    55  	cmd.Flags().BoolVarP(&o.All, "all", "a", true, "for body, whether to get all entries")
    56  	cmd.Flags().StringVarP(&o.Outfile, "outfile", "o", "", "file to write output to")
    57  
    58  	cmd.Flags().BoolVar(&o.Offline, "offline", false, "prevent network access")
    59  	cmd.Flags().StringVar(&o.Remote, "remote", "", "name to get any remote data from")
    60  
    61  	return cmd
    62  }
    63  
    64  // GetOptions encapsulates state for the get command
    65  type GetOptions struct {
    66  	ioes.IOStreams
    67  
    68  	Refs     *RefSelect
    69  	Selector string
    70  	Format   string
    71  
    72  	Limit  int
    73  	Offset int
    74  	All    bool
    75  
    76  	Pretty  bool
    77  	Outfile string
    78  
    79  	Offline bool
    80  	Remote  string
    81  
    82  	inst *lib.Instance
    83  }
    84  
    85  // Complete adds any missing configuration that can only be added just before calling Run
    86  func (o *GetOptions) Complete(f Factory, args []string) (err error) {
    87  	if o.inst, err = f.Instance(); err != nil {
    88  		return
    89  	}
    90  
    91  	if len(args) > 0 {
    92  		if component.IsDatasetField.MatchString(args[0]) {
    93  			o.Selector = args[0]
    94  			args = args[1:]
    95  		}
    96  	}
    97  	if o.Refs, err = GetCurrentRefSelect(f, args, AnyNumberOfReferences); err != nil {
    98  		return
    99  	}
   100  
   101  	if o.Selector == "body" {
   102  		if o.Limit != -1 && o.Offset == -1 {
   103  			o.Offset = 0
   104  		}
   105  		if o.Offset != -1 || o.Limit != -1 {
   106  			o.All = false
   107  		}
   108  	} else {
   109  		if o.Format == "csv" {
   110  			return fmt.Errorf("can only use --format=csv when getting body")
   111  		}
   112  		if o.Limit != -1 {
   113  			return fmt.Errorf("can only use --limit flag when getting body")
   114  		}
   115  		if o.Offset != -1 {
   116  			return fmt.Errorf("can only use --offset flag when getting body")
   117  		}
   118  		if !o.All {
   119  			return fmt.Errorf("can only use --all flag when getting body")
   120  		}
   121  	}
   122  
   123  	return
   124  }
   125  
   126  // Run executes the get command
   127  func (o *GetOptions) Run() (err error) {
   128  	if o.Offline {
   129  		if o.Remote != "" {
   130  			return fmt.Errorf("cannot use '--offline' and '--remote' flags together")
   131  		}
   132  		o.Remote = "local"
   133  	}
   134  
   135  	// TODO(dustmop): Allow any number of references. Right now we ignore everything after the
   136  	// first. The hard parts are:
   137  	// 1) Correctly handling the pager output, and having headers between each ref
   138  	// 2) Identifying cases that limit Get to only work on 1 dataset. For example, the -o flag
   139  
   140  	ctx := context.TODO()
   141  	p := &lib.GetParams{
   142  		Ref:      o.Refs.Ref(),
   143  		Selector: o.Selector,
   144  		All:      o.All,
   145  		List: params.List{
   146  			Offset: o.Offset,
   147  			Limit:  o.Limit,
   148  		},
   149  	}
   150  	var outBytes []byte
   151  	switch {
   152  	case o.Format == "zip":
   153  		zipResults, err := o.inst.Dataset().GetZip(ctx, p)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		outBytes = zipResults.Bytes
   158  		if o.Outfile == "" {
   159  			o.Outfile = zipResults.GeneratedName
   160  		}
   161  	case o.Format == "csv":
   162  		outBytes, err = o.inst.Dataset().GetCSV(ctx, p)
   163  		if err != nil {
   164  			return err
   165  		}
   166  	default:
   167  		res, err := o.inst.WithSource(o.Remote).Dataset().Get(ctx, p)
   168  		if err != nil {
   169  			return err
   170  		}
   171  		switch {
   172  		case lib.IsSelectorScriptFile(o.Selector):
   173  			outBytes = res.Bytes
   174  		case o.Format == "json" || (o.Selector == "body" && o.Format == ""):
   175  			if o.Pretty {
   176  				outBytes, err = json.MarshalIndent(res.Value, "", "  ")
   177  				if err != nil {
   178  					return err
   179  				}
   180  				break
   181  			}
   182  
   183  			outBytes, err = json.Marshal(res.Value)
   184  			if err != nil {
   185  				return err
   186  			}
   187  		default:
   188  			outBytes, err = yaml.Marshal(res.Value)
   189  			if err != nil {
   190  				return err
   191  			}
   192  		}
   193  	}
   194  
   195  	if o.Outfile != "" {
   196  		err := ioutil.WriteFile(o.Outfile, outBytes, 0644)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		outBytes = []byte(fmt.Sprintf("wrote to file %q", o.Outfile))
   201  	}
   202  	buf := bytes.NewBuffer(outBytes)
   203  	buf.Write([]byte{'\n'})
   204  	printToPager(o.Out, buf)
   205  	return nil
   206  }