github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/shared/commentable.go (about)

     1  package shared
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  
    10  	"github.com/AlecAivazis/survey/v2"
    11  	"github.com/cli/cli/api"
    12  	"github.com/cli/cli/internal/config"
    13  	"github.com/cli/cli/internal/ghrepo"
    14  	"github.com/cli/cli/pkg/cmdutil"
    15  	"github.com/cli/cli/pkg/iostreams"
    16  	"github.com/cli/cli/pkg/surveyext"
    17  	"github.com/cli/cli/utils"
    18  	"github.com/spf13/cobra"
    19  )
    20  
    21  type InputType int
    22  
    23  const (
    24  	InputTypeEditor InputType = iota
    25  	InputTypeInline
    26  	InputTypeWeb
    27  )
    28  
    29  type Commentable interface {
    30  	Link() string
    31  	Identifier() string
    32  }
    33  
    34  type CommentableOptions struct {
    35  	IO                    *iostreams.IOStreams
    36  	HttpClient            func() (*http.Client, error)
    37  	RetrieveCommentable   func() (Commentable, ghrepo.Interface, error)
    38  	EditSurvey            func() (string, error)
    39  	InteractiveEditSurvey func() (string, error)
    40  	ConfirmSubmitSurvey   func() (bool, error)
    41  	OpenInBrowser         func(string) error
    42  	Interactive           bool
    43  	InputType             InputType
    44  	Body                  string
    45  }
    46  
    47  func CommentablePreRun(cmd *cobra.Command, opts *CommentableOptions) error {
    48  	inputFlags := 0
    49  	if cmd.Flags().Changed("body") {
    50  		opts.InputType = InputTypeInline
    51  		inputFlags++
    52  	}
    53  	if cmd.Flags().Changed("body-file") {
    54  		opts.InputType = InputTypeInline
    55  		inputFlags++
    56  	}
    57  	if web, _ := cmd.Flags().GetBool("web"); web {
    58  		opts.InputType = InputTypeWeb
    59  		inputFlags++
    60  	}
    61  	if editor, _ := cmd.Flags().GetBool("editor"); editor {
    62  		opts.InputType = InputTypeEditor
    63  		inputFlags++
    64  	}
    65  
    66  	if inputFlags == 0 {
    67  		if !opts.IO.CanPrompt() {
    68  			return &cmdutil.FlagError{Err: errors.New("`--body`, `--body-file` or `--web` required when not running interactively")}
    69  		}
    70  		opts.Interactive = true
    71  	} else if inputFlags == 1 {
    72  		if !opts.IO.CanPrompt() && opts.InputType == InputTypeEditor {
    73  			return &cmdutil.FlagError{Err: errors.New("`--body`, `--body-file` or `--web` required when not running interactively")}
    74  		}
    75  	} else if inputFlags > 1 {
    76  		return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--body`, `--body-file`, `--editor`, or `--web`")}
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  func CommentableRun(opts *CommentableOptions) error {
    83  	commentable, repo, err := opts.RetrieveCommentable()
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	switch opts.InputType {
    89  	case InputTypeWeb:
    90  		openURL := commentable.Link() + "#issuecomment-new"
    91  		if opts.IO.IsStdoutTTY() {
    92  			fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
    93  		}
    94  		return opts.OpenInBrowser(openURL)
    95  	case InputTypeEditor:
    96  		var body string
    97  		if opts.Interactive {
    98  			body, err = opts.InteractiveEditSurvey()
    99  		} else {
   100  			body, err = opts.EditSurvey()
   101  		}
   102  		if err != nil {
   103  			return err
   104  		}
   105  		opts.Body = body
   106  	}
   107  
   108  	if opts.Interactive {
   109  		cont, err := opts.ConfirmSubmitSurvey()
   110  		if err != nil {
   111  			return err
   112  		}
   113  		if !cont {
   114  			return errors.New("Discarding...")
   115  		}
   116  	}
   117  
   118  	httpClient, err := opts.HttpClient()
   119  	if err != nil {
   120  		return err
   121  	}
   122  	apiClient := api.NewClientFromHTTP(httpClient)
   123  	params := api.CommentCreateInput{Body: opts.Body, SubjectId: commentable.Identifier()}
   124  	url, err := api.CommentCreate(apiClient, repo.RepoHost(), params)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	fmt.Fprintln(opts.IO.Out, url)
   129  	return nil
   130  }
   131  
   132  func CommentableConfirmSubmitSurvey() (bool, error) {
   133  	var confirm bool
   134  	submit := &survey.Confirm{
   135  		Message: "Submit?",
   136  		Default: true,
   137  	}
   138  	err := survey.AskOne(submit, &confirm)
   139  	return confirm, err
   140  }
   141  
   142  func CommentableInteractiveEditSurvey(cf func() (config.Config, error), io *iostreams.IOStreams) func() (string, error) {
   143  	return func() (string, error) {
   144  		editorCommand, err := cmdutil.DetermineEditor(cf)
   145  		if err != nil {
   146  			return "", err
   147  		}
   148  		if editorCommand == "" {
   149  			editorCommand = surveyext.DefaultEditorName()
   150  		}
   151  		cs := io.ColorScheme()
   152  		fmt.Fprintf(io.Out, "- %s to draft your comment in %s... ", cs.Bold("Press Enter"), cs.Bold(editorCommand))
   153  		_ = waitForEnter(io.In)
   154  		return surveyext.Edit(editorCommand, "*.md", "", io.In, io.Out, io.ErrOut, nil)
   155  	}
   156  }
   157  
   158  func CommentableEditSurvey(cf func() (config.Config, error), io *iostreams.IOStreams) func() (string, error) {
   159  	return func() (string, error) {
   160  		editorCommand, err := cmdutil.DetermineEditor(cf)
   161  		if err != nil {
   162  			return "", err
   163  		}
   164  		return surveyext.Edit(editorCommand, "*.md", "", io.In, io.Out, io.ErrOut, nil)
   165  	}
   166  }
   167  
   168  func waitForEnter(r io.Reader) error {
   169  	scanner := bufio.NewScanner(r)
   170  	scanner.Scan()
   171  	return scanner.Err()
   172  }