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

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"sync"
    11  
    12  	"github.com/qri-io/ioes"
    13  	"github.com/qri-io/qri/auth/key"
    14  	"github.com/qri-io/qri/config"
    15  	"github.com/qri-io/qri/lib"
    16  	qhttp "github.com/qri-io/qri/lib/http"
    17  	"github.com/qri-io/qri/p2p"
    18  	"github.com/qri-io/qri/remote"
    19  	"github.com/qri-io/qri/remote/access"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  // Constructors encapsulates constructors required by the qri command
    24  type Constructors struct {
    25  	CryptoGenerator key.CryptoGenerator
    26  	InitIPFS        func(repoPath, configPath string) error
    27  }
    28  
    29  // NewQriCommand represents the base command when called without any subcommands
    30  func NewQriCommand(ctx context.Context, repoPath string, ctors Constructors, ioStreams ioes.IOStreams, libOpts ...lib.Option) (*cobra.Command, func() <-chan error) {
    31  	opt := NewQriOptions(ctx, repoPath, ctors, ioStreams, libOpts)
    32  
    33  	cmd := &cobra.Command{
    34  		Use:   "qri",
    35  		Short: "qri GDVCS CLI",
    36  		Long: `qri ("query") is a set of tools for building & sharing datasets: https://qri.io
    37  Feedback, questions, bug reports, and contributions are welcome! 
    38  https://github.com/qri-io/qri/issues`,
    39  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
    40  		},
    41  		BashCompletionFunction: bashCompletionFunc,
    42  	}
    43  
    44  	cmd.SetUsageTemplate(rootUsageTemplate)
    45  	cmd.PersistentFlags().BoolVarP(&opt.Migrate, "migrate", "", false, "automatically run migrations if necessary")
    46  	cmd.PersistentFlags().BoolVarP(&opt.NoPrompt, "no-prompt", "", false, "disable all interactive prompts")
    47  	cmd.PersistentFlags().BoolVarP(&opt.NoColor, "no-color", "", false, "disable colorized output")
    48  	cmd.PersistentFlags().StringVar(&opt.repoPath, "repo", repoPath, "filepath to load qri data from")
    49  	cmd.PersistentFlags().BoolVarP(&opt.LogAll, "log-all", "", false, "log all activity")
    50  
    51  	cmd.AddCommand(
    52  		NewAccessCommand(opt, ioStreams),
    53  		NewAnalyzeTransformCommand(opt, ioStreams),
    54  		NewApplyCommand(opt, ioStreams),
    55  		NewAutocompleteCommand(opt, ioStreams),
    56  		NewConfigCommand(opt, ioStreams),
    57  		NewConnectCommand(opt, ioStreams),
    58  		NewDAGCommand(opt, ioStreams),
    59  		NewDiffCommand(opt, ioStreams),
    60  		NewGetCommand(opt, ioStreams),
    61  		NewListCommand(opt, ioStreams),
    62  		NewLogCommand(opt, ioStreams),
    63  		NewLogbookCommand(opt, ioStreams),
    64  		NewPushCommand(opt, ioStreams),
    65  		NewPullCommand(opt, ioStreams),
    66  		NewPeersCommand(opt, ioStreams),
    67  		NewPreviewCommand(opt, ioStreams),
    68  		NewRegistryCommand(opt, ioStreams),
    69  		NewRemoveCommand(opt, ioStreams),
    70  		NewRenameCommand(opt, ioStreams),
    71  		NewRenderCommand(opt, ioStreams),
    72  		NewSaveCommand(opt, ioStreams),
    73  		NewSearchCommand(opt, ioStreams),
    74  		NewSetupCommand(opt, ioStreams),
    75  		NewValidateCommand(opt, ioStreams),
    76  		NewVersionCommand(opt, ioStreams),
    77  		NewWhatChangedCommand(opt, ioStreams),
    78  	)
    79  
    80  	for _, sub := range cmd.Commands() {
    81  		sub.SetUsageTemplate(defaultUsageTemplate)
    82  	}
    83  
    84  	return cmd, opt.Shutdown
    85  }
    86  
    87  // QriOptions holds the Root Command State
    88  type QriOptions struct {
    89  	ioes.IOStreams
    90  
    91  	// TODO (b5) - this context should be refactored away, preferring to pass
    92  	// this stored context object down through function calls
    93  	ctx       context.Context
    94  	releasers sync.WaitGroup
    95  	doneCh    chan struct{}
    96  
    97  	// path to the qri data directory
    98  	repoPath string
    99  	// generator is source of generating cryptographic info
   100  	ctors Constructors
   101  	// automatically run migrations if necessary
   102  	Migrate bool
   103  	// NoPrompt Disables all promt messages
   104  	NoPrompt bool
   105  	// NoColor disables colorized output
   106  	NoColor bool
   107  	// path to configuration object
   108  	ConfigPath string
   109  	// Whether to log all activity by enabling logging for all packages
   110  	LogAll  bool
   111  	libOpts []lib.Option
   112  	// inst is the Instance that holds state needed by qri's methods
   113  	inst *lib.Instance
   114  }
   115  
   116  // NewQriOptions creates an options object
   117  func NewQriOptions(ctx context.Context, repoPath string, ctors Constructors, ioStreams ioes.IOStreams, libOpts []lib.Option) *QriOptions {
   118  	return &QriOptions{
   119  		IOStreams: ioStreams,
   120  		ctx:       ctx,
   121  		doneCh:    make(chan struct{}),
   122  		repoPath:  repoPath,
   123  		libOpts:   libOpts,
   124  		ctors:     ctors,
   125  	}
   126  }
   127  
   128  // Init will initialize the internal state before any command is run (excluding `qri setup`)
   129  func (o *QriOptions) Init() (err error) {
   130  	if o.inst != nil {
   131  		return
   132  	}
   133  	setNoPrompt(o.NoPrompt)
   134  
   135  	repoErr := lib.QriRepoExists(o.repoPath)
   136  	if repoErr != nil {
   137  		return errors.New("no qri repo exists\nhave you run 'qri setup'?")
   138  	}
   139  
   140  	opts := []lib.Option{
   141  		lib.OptIOStreams(o.IOStreams), // transfer iostreams to instance
   142  		lib.OptCheckConfigMigrations(o.migrationApproval, (!o.Migrate && !o.NoPrompt)),
   143  		lib.OptSetLogAll(o.LogAll),
   144  		lib.OptRemoteServerOptions([]remote.OptionsFunc{
   145  			// look for a remote policy
   146  			remote.OptLoadPolicyFileIfExists(filepath.Join(o.repoPath, access.DefaultAccessControlPolicyFilename)),
   147  		}),
   148  	}
   149  
   150  	if o.libOpts != nil {
   151  		opts = append(o.libOpts, o.libOpts...)
   152  	}
   153  
   154  	o.inst, err = lib.NewInstance(o.ctx, o.repoPath, opts...)
   155  	if err != nil {
   156  		return
   157  	}
   158  
   159  	// Handle color and prompt flags which apply to every command
   160  	shouldColorOutput := !o.NoColor
   161  	cfg := o.inst.GetConfig()
   162  	if cfg != nil && cfg.CLI != nil {
   163  		shouldColorOutput = cfg.CLI.ColorizeOutput
   164  	}
   165  	// todo(arqu): have a config var to indicate force override for windows
   166  	if runtime.GOOS == "windows" {
   167  		shouldColorOutput = false
   168  	}
   169  	setNoColor(!shouldColorOutput)
   170  
   171  	// TODO (b5) - this is a hack to make progress bars not show up while running
   172  	// tests. It does have the real-world implication that "shouldColorOutput"
   173  	// being false also disables progress bars, which may be what we want (ahem: TTY
   174  	// detection), but even if so, isn't the right use of this variable name
   175  	if shouldColorOutput {
   176  		// TODO(ramfox): we guard for a nil bus in `PrintProgressBarsOnEvents`
   177  		// but noting here that no requests that go through http rpc will have
   178  		// a working bus, so we won't get any progress bars when working over
   179  		// http rpc until this is adjusted (once we get the events "rpc-ified")
   180  		PrintProgressBarsOnEvents(o.IOStreams.ErrOut, o.inst.Bus())
   181  	}
   182  
   183  	log.Debugf("running cmd %q", os.Args)
   184  
   185  	return
   186  }
   187  
   188  // migrationApproval returns a boolen based on either flag-derived state or user
   189  // input approving the execution of migrations
   190  func (o *QriOptions) migrationApproval() bool {
   191  	if o.Migrate {
   192  		return true
   193  	} else if o.NoPrompt {
   194  		return false
   195  	}
   196  
   197  	msg := `Your repo needs updating before qri can start. 
   198  Run migration now?`
   199  	return confirm(o.Out, o.In, msg, false)
   200  }
   201  
   202  // Instance returns the instance this options is using
   203  func (o *QriOptions) Instance() (*lib.Instance, error) {
   204  	if err := o.Init(); err != nil {
   205  		return nil, err
   206  	}
   207  	return o.inst, nil
   208  }
   209  
   210  // RepoPath returns the path to the qri data directory
   211  func (o *QriOptions) RepoPath() string {
   212  	return o.repoPath
   213  }
   214  
   215  // Config returns from internal state
   216  func (o *QriOptions) Config() (*config.Config, error) {
   217  	if err := o.Init(); err != nil {
   218  		return nil, err
   219  	}
   220  	return o.inst.GetConfig(), nil
   221  }
   222  
   223  // Constructors returns a struct for expensive constructor functions
   224  func (o *QriOptions) Constructors() Constructors {
   225  	return o.ctors
   226  }
   227  
   228  // HTTPClient returns a client for performing RPC over HTTP
   229  func (o *QriOptions) HTTPClient() *qhttp.Client {
   230  	if err := o.Init(); err != nil {
   231  		return nil
   232  	}
   233  	return o.inst.HTTPClient()
   234  }
   235  
   236  // ConnectionNode returns the internal QriNode, if it is available
   237  func (o *QriOptions) ConnectionNode() (*p2p.QriNode, error) {
   238  	if o.inst == nil {
   239  		return nil, fmt.Errorf("repo not available")
   240  	}
   241  	return o.inst.Node(), nil
   242  }
   243  
   244  // Shutdown closes the instance
   245  func (o *QriOptions) Shutdown() <-chan error {
   246  	if o.inst == nil {
   247  		done := make(chan error)
   248  		go func() { done <- nil }()
   249  		return done
   250  	}
   251  	return o.inst.Shutdown()
   252  }