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 }