input.go

  1// Taken from the git-appraise project
  2
  3package input
  4
  5import (
  6	"bufio"
  7	"bytes"
  8	"fmt"
  9	"github.com/MichaelMure/git-bug/repository"
 10	"io/ioutil"
 11	"os"
 12	"os/exec"
 13)
 14
 15// LaunchEditor launches the default editor configured for the given repo. This
 16// method blocks until the editor command has returned.
 17//
 18// The specified filename should be a temporary file and provided as a relative path
 19// from the repo (e.g. "FILENAME" will be converted to ".git/FILENAME"). This file
 20// will be deleted after the editor is closed and its contents have been read.
 21//
 22// This method returns the text that was read from the temporary file, or
 23// an error if any step in the process failed.
 24func LaunchEditor(repo repository.Repo, fileName string) (string, error) {
 25	editor, err := repo.GetCoreEditor()
 26	if err != nil {
 27		return "", fmt.Errorf("Unable to detect default git editor: %v\n", err)
 28	}
 29
 30	path := fmt.Sprintf("%s/.git/%s", repo.GetPath(), fileName)
 31
 32	cmd, err := startInlineCommand(editor, path)
 33	if err != nil {
 34		// Running the editor directly did not work. This might mean that
 35		// the editor string is not a path to an executable, but rather
 36		// a shell command (e.g. "emacsclient --tty"). As such, we'll try
 37		// to run the command through bash, and if that fails, try with sh
 38		args := []string{"-c", fmt.Sprintf("%s %q", editor, path)}
 39		cmd, err = startInlineCommand("bash", args...)
 40		if err != nil {
 41			cmd, err = startInlineCommand("sh", args...)
 42		}
 43	}
 44	if err != nil {
 45		return "", fmt.Errorf("Unable to start editor: %v\n", err)
 46	}
 47
 48	if err := cmd.Wait(); err != nil {
 49		return "", fmt.Errorf("Editing finished with error: %v\n", err)
 50	}
 51
 52	output, err := ioutil.ReadFile(path)
 53	if err != nil {
 54		os.Remove(path)
 55		return "", fmt.Errorf("Error reading edited file: %v\n", err)
 56	}
 57	os.Remove(path)
 58	return string(output), err
 59}
 60
 61// FromFile loads and returns the contents of a given file. If - is passed
 62// through, much like git, it will read from stdin. This can be piped data,
 63// unless there is a tty in which case the user will be prompted to enter a
 64// message.
 65func FromFile(fileName string) (string, error) {
 66	if fileName == "-" {
 67		stat, err := os.Stdin.Stat()
 68		if err != nil {
 69			return "", fmt.Errorf("Error reading from stdin: %v\n", err)
 70		}
 71		if (stat.Mode() & os.ModeCharDevice) == 0 {
 72			// There is no tty. This will allow us to read piped data instead.
 73			output, err := ioutil.ReadAll(os.Stdin)
 74			if err != nil {
 75				return "", fmt.Errorf("Error reading from stdin: %v\n", err)
 76			}
 77			return string(output), err
 78		}
 79
 80		fmt.Printf("(reading comment from standard input)\n")
 81		var output bytes.Buffer
 82		s := bufio.NewScanner(os.Stdin)
 83		for s.Scan() {
 84			output.Write(s.Bytes())
 85			output.WriteRune('\n')
 86		}
 87		return output.String(), nil
 88	}
 89
 90	output, err := ioutil.ReadFile(fileName)
 91	if err != nil {
 92		return "", fmt.Errorf("Error reading file: %v\n", err)
 93	}
 94	return string(output), err
 95}
 96
 97func startInlineCommand(command string, args ...string) (*exec.Cmd, error) {
 98	cmd := exec.Command(command, args...)
 99	cmd.Stdin = os.Stdin
100	cmd.Stdout = os.Stdout
101	cmd.Stderr = os.Stderr
102	err := cmd.Start()
103	return cmd, err
104}