diff --git a/main.go b/main.go index 9310591f..feb59f6c 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ import ( "github.com/senorprogrammer/wtf/jenkins" "github.com/senorprogrammer/wtf/jira" "github.com/senorprogrammer/wtf/logger" + "github.com/senorprogrammer/wtf/mercurial" "github.com/senorprogrammer/wtf/newrelic" "github.com/senorprogrammer/wtf/opsgenie" "github.com/senorprogrammer/wtf/power" @@ -206,6 +207,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { widgets = append(widgets, jira.NewWidget(app, pages)) case "logger": widgets = append(widgets, logger.NewWidget(app)) + case "mercurial": + widgets = append(widgets, mercurial.NewWidget(app, pages)) case "newrelic": widgets = append(widgets, newrelic.NewWidget(app)) case "opsgenie": diff --git a/mercurial/display.go b/mercurial/display.go new file mode 100644 index 00000000..2321bccb --- /dev/null +++ b/mercurial/display.go @@ -0,0 +1,83 @@ +package mercurial + +import ( + "fmt" + "strings" + "unicode/utf8" + + "github.com/senorprogrammer/wtf/wtf" +) + +func (widget *Widget) display() { + repoData := widget.currentData() + if repoData == nil { + widget.View.SetText(" Mercurial repo data is unavailable ") + return + } + + title := fmt.Sprintf("%s - [green]%s[white]", widget.Name, repoData.Repository) + widget.View.SetTitle(widget.ContextualTitle(title)) + + str := wtf.SigilStr(len(widget.Data), widget.Idx, widget.View) + "\n" + str = str + " [red]Branch:Bookmark[white]\n" + str = str + fmt.Sprintf(" %s:%s\n", repoData.Branch, repoData.Bookmark) + str = str + "\n" + str = str + widget.formatChanges(repoData.ChangedFiles) + str = str + "\n" + str = str + widget.formatCommits(repoData.Commits) + + widget.View.SetText(str) +} + +func (widget *Widget) formatChanges(data []string) string { + str := "" + str = str + " [red]Changed Files[white]\n" + + if len(data) == 1 { + str = str + " [grey]none[white]\n" + } else { + for _, line := range data { + str = str + widget.formatChange(line) + } + } + + return str +} + +func (widget *Widget) formatChange(line string) string { + if len(line) == 0 { + return "" + } + + line = strings.TrimSpace(line) + firstChar, _ := utf8.DecodeRuneInString(line) + + // Revisit this and kill the ugly duplication + switch firstChar { + case 'A': + line = strings.Replace(line, "A", "[green]A[white]", 1) + case 'D': + line = strings.Replace(line, "D", "[red]D[white]", 1) + case 'M': + line = strings.Replace(line, "M", "[yellow]M[white]", 1) + case 'R': + line = strings.Replace(line, "R", "[purple]R[white]", 1) + } + + return fmt.Sprintf(" %s\n", strings.Replace(line, "\"", "", -1)) +} + +func (widget *Widget) formatCommits(data []string) string { + str := "" + str = str + " [red]Recent Commits[white]\n" + + for _, line := range data { + str = str + widget.formatCommit(line) + } + + return str +} + +func (widget *Widget) formatCommit(line string) string { + return fmt.Sprintf(" %s\n", strings.Replace(line, "\"", "", -1)) +} diff --git a/mercurial/hg_repo.go b/mercurial/hg_repo.go new file mode 100644 index 00000000..daca9dc6 --- /dev/null +++ b/mercurial/hg_repo.go @@ -0,0 +1,96 @@ +package mercurial + +import ( + "fmt" + "io/ioutil" + "os/exec" + "path" + "strings" + + "github.com/senorprogrammer/wtf/wtf" +) + +type MercurialRepo struct { + Branch string + Bookmark string + ChangedFiles []string + Commits []string + Repository string + Path string +} + +func NewMercurialRepo(repoPath string) *MercurialRepo { + repo := MercurialRepo{Path: repoPath} + + repo.Branch = strings.TrimSpace(repo.branch()) + repo.Bookmark = strings.TrimSpace(repo.bookmark()) + repo.ChangedFiles = repo.changedFiles() + repo.Commits = repo.commits() + repo.Repository = strings.TrimSpace(repo.Path) + + return &repo +} + +/* -------------------- Unexported Functions -------------------- */ + +func (repo *MercurialRepo) branch() string { + arg := []string{"branch", repo.repoPath()} + + cmd := exec.Command("hg", arg...) + str := wtf.ExecuteCommand(cmd) + + return str +} + +func (repo *MercurialRepo) bookmark() string { + bookmark, err := ioutil.ReadFile(path.Join(repo.Path, ".hg", "bookmarks.current")) + if err != nil { + return "" + } + return string(bookmark) +} + +func (repo *MercurialRepo) changedFiles() []string { + arg := []string{"status", repo.repoPath()} + + cmd := exec.Command("hg", arg...) + str := wtf.ExecuteCommand(cmd) + + data := strings.Split(str, "\n") + + return data +} + +func (repo *MercurialRepo) commits() []string { + numStr := fmt.Sprintf("-l %d", wtf.Config.UInt("wtf.mods.mercurial.commitCount", 10)) + + commitFormat := wtf.Config.UString("wtf.mods.mercurial.commitFormat", "[forestgreen]{rev}:{phase} [white]{desc|firstline|strip} [grey]{author|person} {date|age}[white]") + commitStr := fmt.Sprintf("--template=\"%s\n\"", commitFormat) + + arg := []string{"log", repo.repoPath(), numStr, commitStr} + + cmd := exec.Command("hg", arg...) + str := wtf.ExecuteCommand(cmd) + + data := strings.Split(str, "\n") + + return data +} + +func (repo *MercurialRepo) pull() string { + arg := []string{"pull", repo.repoPath()} + cmd := exec.Command("hg", arg...) + str := wtf.ExecuteCommand(cmd) + return str +} + +func (repo *MercurialRepo) checkout(branch string) string { + arg := []string{"checkout", repo.repoPath(), branch} + cmd := exec.Command("hg", arg...) + str := wtf.ExecuteCommand(cmd) + return str +} + +func (repo *MercurialRepo) repoPath() string { + return fmt.Sprintf("--repository=%s", repo.Path) +} diff --git a/mercurial/widget.go b/mercurial/widget.go new file mode 100644 index 00000000..083396f4 --- /dev/null +++ b/mercurial/widget.go @@ -0,0 +1,196 @@ +package mercurial + +import ( + "github.com/gdamore/tcell" + "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/wtf" +) + +const HelpText = ` + Keyboard commands for Mercurial: + + /: Show/hide this help window + c: Checkout to branch + h: Previous mercurial repository + l: Next mercurial repository + p: Pull current mercurial repository + + arrow left: Previous mercurial repository + arrow right: Next mercurial repository +` + +const offscreen = -1000 +const modalWidth = 80 +const modalHeight = 7 + +type Widget struct { + wtf.HelpfulWidget + wtf.MultiSourceWidget + wtf.TextWidget + + app *tview.Application + Data []*MercurialRepo + pages *tview.Pages +} + +func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { + widget := Widget{ + HelpfulWidget: wtf.NewHelpfulWidget(app, pages, HelpText), + MultiSourceWidget: wtf.NewMultiSourceWidget("mercurial", "repository", "repositories"), + TextWidget: wtf.NewTextWidget(app, "Mercurial", "mercurial", true), + + app: app, + pages: pages, + } + + widget.LoadSources() + widget.SetDisplayFunction(widget.display) + + widget.HelpfulWidget.SetView(widget.View) + widget.View.SetInputCapture(widget.keyboardIntercept) + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Checkout() { + form := widget.modalForm("Branch to checkout:", "") + + checkoutFctn := func() { + text := form.GetFormItem(0).(*tview.InputField).GetText() + repoToCheckout := widget.Data[widget.Idx] + repoToCheckout.checkout(text) + widget.pages.RemovePage("modal") + widget.app.SetFocus(widget.View) + widget.display() + widget.Refresh() + } + + widget.addButtons(form, checkoutFctn) + widget.modalFocus(form) +} + +func (widget *Widget) Pull() { + repoToPull := widget.Data[widget.Idx] + repoToPull.pull() + widget.Refresh() + +} + +func (widget *Widget) Refresh() { + repoPaths := wtf.ToStrs(wtf.Config.UList("wtf.mods.mercurial.repositories")) + + widget.UpdateRefreshedAt() + widget.Data = widget.mercurialRepos(repoPaths) + widget.display() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) addCheckoutButton(form *tview.Form, fctn func()) { + form.AddButton("Checkout", fctn) +} + +func (widget *Widget) addButtons(form *tview.Form, checkoutFctn func()) { + widget.addCheckoutButton(form, checkoutFctn) + widget.addCancelButton(form) +} + +func (widget *Widget) addCancelButton(form *tview.Form) { + cancelFn := func() { + widget.pages.RemovePage("modal") + widget.app.SetFocus(widget.View) + widget.display() + } + + form.AddButton("Cancel", cancelFn) + form.SetCancelFunc(cancelFn) +} + +func (widget *Widget) modalFocus(form *tview.Form) { + frame := widget.modalFrame(form) + widget.pages.AddPage("modal", frame, false, true) + widget.app.SetFocus(frame) +} + +func (widget *Widget) modalForm(lbl, text string) *tview.Form { + form := tview.NewForm(). + SetButtonsAlign(tview.AlignCenter). + SetButtonTextColor(tview.Styles.PrimaryTextColor) + + form.AddInputField(lbl, text, 60, nil, nil) + + return form +} + +func (widget *Widget) modalFrame(form *tview.Form) *tview.Frame { + frame := tview.NewFrame(form).SetBorders(0, 0, 0, 0, 0, 0) + frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) + frame.SetBorder(true) + frame.SetBorders(1, 1, 0, 0, 1, 1) + + drawFunc := func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { + w, h := screen.Size() + frame.SetRect((w/2)-(width/2), (h/2)-(height/2), width, height) + return x, y, width, height + } + + frame.SetDrawFunc(drawFunc) + + return frame +} + +func (widget *Widget) currentData() *MercurialRepo{ + if len(widget.Data) == 0 { + return nil + } + + if widget.Idx < 0 || widget.Idx >= len(widget.Data) { + return nil + } + + return widget.Data[widget.Idx] +} + +func (widget *Widget) mercurialRepos(repoPaths []string) []*MercurialRepo{ + repos := []*MercurialRepo{} + + for _, repoPath := range repoPaths { + repo := NewMercurialRepo(repoPath) + repos = append(repos, repo) + } + + return repos +} + +func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + switch string(event.Rune()) { + case "/": + widget.ShowHelp() + return nil + case "h": + widget.Prev() + return nil + case "l": + widget.Next() + return nil + case "p": + widget.Pull() + return nil + case "c": + widget.Checkout() + return nil + } + + switch event.Key() { + case tcell.KeyLeft: + widget.Prev() + return nil + case tcell.KeyRight: + widget.Next() + return nil + default: + return event + } +}