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

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/qri-io/ioes"
    11  	"github.com/qri-io/qri/base/params"
    12  	"github.com/qri-io/qri/dsref"
    13  	"github.com/qri-io/qri/errors"
    14  	"github.com/qri-io/qri/lib"
    15  	"github.com/qri-io/qri/repo"
    16  	"github.com/spf13/cobra"
    17  )
    18  
    19  // NewLogCommand creates a new `qri log` cobra command
    20  func NewLogCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
    21  	o := &LogOptions{IOStreams: ioStreams}
    22  	cmd := &cobra.Command{
    23  		Use:     "log [DATASET]",
    24  		Aliases: []string{"history"},
    25  		Short:   "show log of dataset commits",
    26  		Long: "`qri log`" + ` lists dataset commits over time. Each entry in the log is a 
    27  snapshot of a dataset taken at the moment it was saved that keeps exact details 
    28  about how that dataset looked at at that point in time. 
    29  
    30  We call these snapshots versions. Each version has an author (the peer that 
    31  created the version) and a message explaining what changed. Log prints these 
    32  details in order of occurrence, starting with the most recent known version, 
    33  working backwards in time.
    34  
    35  The log command can get the list of versions for a local dataset or a dataset
    36  on the network at a remote.
    37  `,
    38  		Example: `  # Show log for the local dataset b5/precip:
    39    $ qri log b5/precip
    40  
    41    # Show log for a dataset on the Qri Cloud registry called ramfox/league_stats
    42    $ qri log ramfox/league_stats
    43  	
    44    # Show log for a dataset chriswhong/nyc_parking_tickets on a remote named "nycdatacollection"
    45    $ qri log chriswhong/nyc_parking_tickets --source nycdatacollection`,
    46  		Annotations: map[string]string{
    47  			"group": "dataset",
    48  		},
    49  		Args: cobra.MaximumNArgs(1),
    50  		RunE: func(cmd *cobra.Command, args []string) error {
    51  			if err := o.Complete(f, args); err != nil {
    52  				return err
    53  			}
    54  			return o.Run()
    55  		},
    56  	}
    57  
    58  	// cmd.Flags().StringVarP(&o.Format, "format", "f", "", "set output format [json]")
    59  	cmd.Flags().IntVar(&o.Offset, "offset", 0, "skip this number of records from the results, default 0")
    60  	cmd.Flags().IntVar(&o.Limit, "limit", 25, "size of results, default 25")
    61  	cmd.Flags().StringVarP(&o.Source, "source", "", "", "name of source to fetch from, disables local actions. `registry` will search the default qri registry")
    62  	cmd.Flags().BoolVarP(&o.Local, "local", "l", false, "only fetch local logs, disables network actions")
    63  	cmd.Flags().BoolVarP(&o.Pull, "pull", "p", false, "fetch the latest logs from the network")
    64  
    65  	return cmd
    66  }
    67  
    68  // LogOptions encapsulates state for the log command
    69  type LogOptions struct {
    70  	ioes.IOStreams
    71  
    72  	Offset int
    73  	Limit  int
    74  	Refs   *RefSelect
    75  	Local  bool
    76  	Pull   bool
    77  
    78  	// remote fetching specific flags
    79  	Source     string
    80  	Unfetch    bool
    81  	NoRegistry bool
    82  	NoPin      bool
    83  
    84  	Instance *lib.Instance
    85  }
    86  
    87  // Complete adds any missing configuration that can only be added just before calling Run
    88  func (o *LogOptions) Complete(f Factory, args []string) (err error) {
    89  	if o.Instance, err = f.Instance(); err != nil {
    90  		return err
    91  	}
    92  
    93  	if o.Local && (o.Source != "" || o.Pull) {
    94  		return errors.New(err, "cannot use 'local' flag with either the 'source' or 'pull' flags")
    95  	}
    96  
    97  	if o.Refs, err = GetCurrentRefSelect(f, args, AnyNumberOfReferences); err != nil {
    98  		if err == repo.ErrEmptyRef {
    99  			return errors.New(err, "please provide a dataset reference")
   100  		}
   101  	}
   102  
   103  	return err
   104  }
   105  
   106  // Run executes the log command
   107  func (o *LogOptions) Run() error {
   108  
   109  	ctx := context.TODO()
   110  	p := &lib.ActivityParams{
   111  		Ref:  o.Refs.Ref(),
   112  		Pull: o.Pull,
   113  		List: params.List{
   114  			Offset: o.Offset,
   115  			Limit:  o.Limit,
   116  		},
   117  	}
   118  
   119  	res, err := o.Instance.WithSource(o.Source).Dataset().Activity(ctx, p)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	makeItemsAndPrint(res, o.Out, o.Offset)
   125  	return nil
   126  }
   127  
   128  func makeItemsAndPrint(refs []dsref.VersionInfo, out io.Writer, offset int) {
   129  	items := make([]fmt.Stringer, len(refs))
   130  	for i, r := range refs {
   131  		items[i] = dslogItemStringer(r)
   132  	}
   133  
   134  	printItems(out, items, offset)
   135  }
   136  
   137  // NewLogbookCommand creates a `qri logbook` cobra command
   138  func NewLogbookCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
   139  	o := &LogbookOptions{IOStreams: ioStreams}
   140  	cmd := &cobra.Command{
   141  		Use:   "logbook [DATASET]",
   142  		Short: "show a detailed list of changes on a dataset name",
   143  		Example: `  # Show log for the dataset bob/precip:
   144    $ qri logbook bob/precip`,
   145  		Long: `Logbooks are records of changes to a dataset. The logbook is more detailed
   146  than a dataset history, recording the steps taken to construct a dataset history
   147  without including dataset data. Logbooks can be synced with other users.
   148  
   149  The logbook command shows entries for a dataset, from newest to oldest.`,
   150  		Annotations: map[string]string{
   151  			"group": "dataset",
   152  		},
   153  		RunE: func(cmd *cobra.Command, args []string) error {
   154  			if err := o.Complete(f, args); err != nil {
   155  				return err
   156  			}
   157  			if o.Raw {
   158  				return o.RawLogs()
   159  			} else if o.Summary {
   160  				return o.LogbookSummary()
   161  			}
   162  			return o.LogEntries()
   163  		},
   164  	}
   165  
   166  	cmd.Flags().IntVar(&o.Offset, "offset", 0, "skip this number of records from the results, default 0")
   167  	cmd.Flags().IntVar(&o.Limit, "limit", 25, "size of results, default 25")
   168  	cmd.Flags().BoolVar(&o.Raw, "raw", false, "full logbook in raw JSON format. overrides all other flags")
   169  	cmd.Flags().BoolVar(&o.Summary, "summary", false, "print one oplog per line in the format 'MODEL ID OPCOUNT NAME'. overrides all other flags")
   170  
   171  	return cmd
   172  }
   173  
   174  // LogbookOptions encapsulates state for the log command
   175  type LogbookOptions struct {
   176  	ioes.IOStreams
   177  
   178  	Offset       int
   179  	Limit        int
   180  	Refs         *RefSelect
   181  	Raw, Summary bool
   182  
   183  	Instance *lib.Instance
   184  }
   185  
   186  // Complete adds any missing configuration that can only be added just before calling Run
   187  func (o *LogbookOptions) Complete(f Factory, args []string) (err error) {
   188  	if o.Instance, err = f.Instance(); err != nil {
   189  		return err
   190  	}
   191  
   192  	if o.Raw && o.Summary {
   193  		return fmt.Errorf("cannot use summary & raw flags at once")
   194  	}
   195  
   196  	if o.Raw || o.Summary {
   197  		if len(args) != 0 {
   198  			return fmt.Errorf("can't use dataset reference. the raw flag shows the entire logbook")
   199  		}
   200  	} else {
   201  		if o.Refs, err = GetCurrentRefSelect(f, args, 1); err != nil {
   202  			return err
   203  		}
   204  	}
   205  
   206  	return err
   207  }
   208  
   209  // LogEntries gets entries from the logbook
   210  func (o *LogbookOptions) LogEntries() error {
   211  
   212  	p := &lib.RefListParams{
   213  		Ref:    o.Refs.Ref(),
   214  		Offset: o.Offset,
   215  		Limit:  o.Limit,
   216  	}
   217  
   218  	ctx := context.TODO()
   219  	res, err := o.Instance.Log().Log(ctx, p)
   220  	if err != nil {
   221  		if err == repo.ErrEmptyRef {
   222  			return errors.New(err, "please provide a dataset reference")
   223  		}
   224  		return err
   225  	}
   226  
   227  	// print items in reverse
   228  	items := make([]fmt.Stringer, len(res))
   229  	j := len(items)
   230  	for _, r := range res {
   231  		j--
   232  		items[j] = logEntryStringer(r)
   233  	}
   234  
   235  	printItems(o.Out, items, o.Offset)
   236  	return nil
   237  }
   238  
   239  // RawLogs executes the rawlogs variant of the logbook command
   240  func (o *LogbookOptions) RawLogs() error {
   241  	ctx := context.TODO()
   242  	res, err := o.Instance.Log().RawLogbook(ctx, &lib.RawLogbookParams{})
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	data, err := json.Marshal(res)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	printToPager(o.Out, bytes.NewBuffer(data))
   253  	return nil
   254  }
   255  
   256  // LogbookSummary prints a logbook overview
   257  func (o *LogbookOptions) LogbookSummary() error {
   258  	ctx := context.TODO()
   259  	res, err := o.Instance.Log().LogbookSummary(ctx, &struct{}{})
   260  	if err != nil {
   261  		return err
   262  	}
   263  
   264  	printToPager(o.Out, bytes.NewBufferString(*res))
   265  	return nil
   266  }