github.com/jingweno/gh@v2.1.1-0.20221007190738-04a7985fa9a1+incompatible/github/editor.go (about)

     1  package github
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/jingweno/gh/cmd"
    15  	"github.com/jingweno/gh/git"
    16  )
    17  
    18  func NewEditor(topic, message string) (editor *Editor, err error) {
    19  	messageFile, err := getMessageFile(topic)
    20  	if err != nil {
    21  		return
    22  	}
    23  
    24  	program, err := git.Editor()
    25  	if err != nil {
    26  		return
    27  	}
    28  
    29  	editor = &Editor{
    30  		Program: program,
    31  		File:    messageFile,
    32  		Message: message,
    33  		doEdit:  doTextEditorEdit,
    34  	}
    35  
    36  	return
    37  }
    38  
    39  type Editor struct {
    40  	Program string
    41  	File    string
    42  	Message string
    43  	doEdit  func(program, file string) error
    44  }
    45  
    46  func (e *Editor) Edit() (content []byte, err error) {
    47  	if e.Message != "" {
    48  		err = ioutil.WriteFile(e.File, []byte(e.Message), 0644)
    49  		if err != nil {
    50  			return
    51  		}
    52  	}
    53  	defer os.Remove(e.File)
    54  
    55  	err = e.doEdit(e.Program, e.File)
    56  	if err != nil {
    57  		err = fmt.Errorf("error using text editor for editing message")
    58  		return
    59  	}
    60  
    61  	content, err = ioutil.ReadFile(e.File)
    62  
    63  	return
    64  }
    65  
    66  func (e *Editor) EditTitleAndBody() (title, body string, err error) {
    67  	content, err := e.Edit()
    68  	if err != nil {
    69  		return
    70  	}
    71  
    72  	content = bytes.TrimSpace(content)
    73  	reader := bufio.NewReader(bytes.NewReader(content))
    74  	title, body, err = readTitleAndBody(reader)
    75  
    76  	return
    77  }
    78  
    79  func doTextEditorEdit(program, file string) error {
    80  	editCmd := cmd.New(program)
    81  	r := regexp.MustCompile("[mg]?vi[m]$")
    82  	if r.MatchString(program) {
    83  		editCmd.WithArg("-c")
    84  		editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
    85  	}
    86  	editCmd.WithArg(file)
    87  
    88  	return editCmd.Exec()
    89  }
    90  
    91  func readTitleAndBody(reader *bufio.Reader) (title, body string, err error) {
    92  	r := regexp.MustCompile("\\S")
    93  	var titleParts, bodyParts []string
    94  
    95  	line, err := readLine(reader)
    96  	for err == nil {
    97  		if strings.HasPrefix(line, "#") {
    98  			break
    99  		}
   100  
   101  		if len(bodyParts) == 0 && r.MatchString(line) {
   102  			titleParts = append(titleParts, line)
   103  		} else {
   104  			bodyParts = append(bodyParts, line)
   105  		}
   106  
   107  		line, err = readLine(reader)
   108  	}
   109  
   110  	if err == io.EOF {
   111  		err = nil
   112  	}
   113  
   114  	title = strings.Join(titleParts, " ")
   115  	title = strings.TrimSpace(title)
   116  
   117  	body = strings.Join(bodyParts, "\n")
   118  	body = strings.TrimSpace(body)
   119  
   120  	return
   121  }
   122  
   123  func readLine(r *bufio.Reader) (string, error) {
   124  	var (
   125  		isPrefix = true
   126  		err      error
   127  		line, ln []byte
   128  	)
   129  
   130  	for isPrefix && err == nil {
   131  		line, isPrefix, err = r.ReadLine()
   132  		ln = append(ln, line...)
   133  	}
   134  
   135  	return string(ln), err
   136  }
   137  
   138  func getMessageFile(about string) (string, error) {
   139  	gitDir, err := git.Dir()
   140  	if err != nil {
   141  		return "", err
   142  	}
   143  
   144  	return filepath.Join(gitDir, fmt.Sprintf("%s_EDITMSG", about)), nil
   145  }