github.com/echohead/hub@v2.2.1+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/github/hub/cmd"
    15  	"github.com/github/hub/git"
    16  )
    17  
    18  func NewEditor(filePrefix, topic, message string) (editor *Editor, err error) {
    19  	messageFile, err := getMessageFile(filePrefix)
    20  	if err != nil {
    21  		return
    22  	}
    23  
    24  	program, err := git.Editor()
    25  	if err != nil {
    26  		return
    27  	}
    28  
    29  	cs := git.CommentChar()
    30  
    31  	editor = &Editor{
    32  		Program:    program,
    33  		Topic:      topic,
    34  		File:       messageFile,
    35  		Message:    message,
    36  		CS:         cs,
    37  		openEditor: openTextEditor,
    38  	}
    39  
    40  	return
    41  }
    42  
    43  type Editor struct {
    44  	Program    string
    45  	Topic      string
    46  	File       string
    47  	Message    string
    48  	CS         string
    49  	openEditor func(program, file string) error
    50  }
    51  
    52  func (e *Editor) DeleteFile() error {
    53  	return os.Remove(e.File)
    54  }
    55  
    56  func (e *Editor) EditTitleAndBody() (title, body string, err error) {
    57  	content, err := e.openAndEdit()
    58  	if err != nil {
    59  		return
    60  	}
    61  
    62  	content = bytes.TrimSpace(content)
    63  	reader := bytes.NewReader(content)
    64  	title, body, err = readTitleAndBody(reader, e.CS)
    65  
    66  	if err != nil || title == "" {
    67  		defer e.DeleteFile()
    68  	}
    69  
    70  	return
    71  }
    72  
    73  func (e *Editor) openAndEdit() (content []byte, err error) {
    74  	err = e.writeContent()
    75  	if err != nil {
    76  		return
    77  	}
    78  
    79  	err = e.openEditor(e.Program, e.File)
    80  	if err != nil {
    81  		err = fmt.Errorf("error using text editor for %s message", e.Topic)
    82  		defer e.DeleteFile()
    83  		return
    84  	}
    85  
    86  	content, err = e.readContent()
    87  
    88  	return
    89  }
    90  
    91  func (e *Editor) writeContent() (err error) {
    92  	// only write message if file doesn't exist
    93  	if !e.isFileExist() && e.Message != "" {
    94  		err = ioutil.WriteFile(e.File, []byte(e.Message), 0644)
    95  		if err != nil {
    96  			return
    97  		}
    98  	}
    99  
   100  	return
   101  }
   102  
   103  func (e *Editor) isFileExist() bool {
   104  	_, err := os.Stat(e.File)
   105  	return err == nil || !os.IsNotExist(err)
   106  }
   107  
   108  func (e *Editor) readContent() (content []byte, err error) {
   109  	return ioutil.ReadFile(e.File)
   110  }
   111  
   112  func openTextEditor(program, file string) error {
   113  	editCmd := cmd.New(program)
   114  	r := regexp.MustCompile("[mg]?vi[m]$")
   115  	if r.MatchString(program) {
   116  		editCmd.WithArg("-c")
   117  		editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
   118  	}
   119  	editCmd.WithArg(file)
   120  
   121  	return editCmd.Spawn()
   122  }
   123  
   124  func readTitleAndBody(reader io.Reader, cs string) (title, body string, err error) {
   125  	var titleParts, bodyParts []string
   126  
   127  	r := regexp.MustCompile("\\S")
   128  	scanner := bufio.NewScanner(reader)
   129  	for scanner.Scan() {
   130  		line := scanner.Text()
   131  		if strings.HasPrefix(line, cs) {
   132  			continue
   133  		}
   134  
   135  		if len(bodyParts) == 0 && r.MatchString(line) {
   136  			titleParts = append(titleParts, line)
   137  		} else {
   138  			bodyParts = append(bodyParts, line)
   139  		}
   140  	}
   141  
   142  	if err = scanner.Err(); err != nil {
   143  		return
   144  	}
   145  
   146  	title = strings.Join(titleParts, " ")
   147  	title = strings.TrimSpace(title)
   148  
   149  	body = strings.Join(bodyParts, "\n")
   150  	body = strings.TrimSpace(body)
   151  
   152  	return
   153  }
   154  
   155  func getMessageFile(about string) (string, error) {
   156  	gitDir, err := git.Dir()
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  
   161  	return filepath.Join(gitDir, fmt.Sprintf("%s_EDITMSG", about)), nil
   162  }