Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions mob.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/remotemobprogramming/mob/v5/help"
"github.com/remotemobprogramming/mob/v5/open"
"github.com/remotemobprogramming/mob/v5/say"
"github.com/remotemobprogramming/mob/v5/timer/localtimer"
"github.com/remotemobprogramming/mob/v5/workdir"
)

Expand Down Expand Up @@ -434,6 +435,24 @@ func determineBranches(currentBranch Branch, localBranches []string, configurati
return
}

func enrichConfigurationWithBranchQualifier(configuration config.Configuration) config.Configuration {
if !isGit() {
return configuration
}

if configuration.WipBranchQualifier == "" {
currentBranch := gitCurrentBranch()
currentBaseBranch, _ := determineBranches(currentBranch, gitBranches(), configuration)

if currentBranch.IsWipBranch(configuration) {
wipBranchWithoutWipPrefix := currentBranch.removeWipPrefix(configuration).Name
configuration.WipBranchQualifier = removePrefix(removePrefix(wipBranchWithoutWipPrefix, currentBaseBranch.Name), configuration.WipBranchQualifierSeparator)
}
}

return configuration
}

func injectCommandWithMessage(command string, message string) string {
placeHolders := strings.Count(command, "%s")
if placeHolders > 1 {
Expand All @@ -446,32 +465,13 @@ func injectCommandWithMessage(command string, message string) string {
return fmt.Sprintf(command, message)
}

func executeCommandsInBackgroundProcess(commands ...string) (err error) {
cmds := make([]string, 0)
for _, c := range commands {
if len(c) > 0 {
cmds = append(cmds, c)
}
}
say.Debug(fmt.Sprintf("Operating System %s", runtime.GOOS))
switch runtime.GOOS {
case "windows":
_, err = startCommand("powershell", "-command", fmt.Sprintf("start-process powershell -NoNewWindow -ArgumentList '-command \"%s\"'", strings.Join(cmds, ";")))
case "darwin", "linux":
_, err = startCommand("sh", "-c", fmt.Sprintf("(%s) &", strings.Join(cmds, ";")))
default:
say.Warning(fmt.Sprintf("Cannot execute background commands on your os: %s", runtime.GOOS))
}
return err
}

func currentTime() string {
return time.Now().Format("15:04")
}

func moo(configuration config.Configuration) {
voiceMessage := "moo"
err := executeCommandsInBackgroundProcess(getVoiceCommand(voiceMessage, configuration.VoiceCommand))
err := localtimer.ExecuteCommandsInBackgroundProcess(localtimer.VoiceCommand(voiceMessage, configuration.VoiceCommand))

if err != nil {
say.Warning(fmt.Sprintf("can't run voice command on your system (%s)", runtime.GOOS))
Expand Down
176 changes: 7 additions & 169 deletions timer.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"runtime"
"strconv"
"time"

config "github.com/remotemobprogramming/mob/v5/configuration"
"github.com/remotemobprogramming/mob/v5/exit"
"github.com/remotemobprogramming/mob/v5/httpclient"
"github.com/remotemobprogramming/mob/v5/say"
timerpkg "github.com/remotemobprogramming/mob/v5/timer"
_ "github.com/remotemobprogramming/mob/v5/timer/localtimer"
_ "github.com/remotemobprogramming/mob/v5/timer/webtimer"
)

func StartTimer(timerInMinutes string, configuration config.Configuration) {
Expand All @@ -21,71 +15,8 @@ func StartTimer(timerInMinutes string, configuration config.Configuration) {
}

func startTimer(timerInMinutes string, configuration config.Configuration) error {
err, timeoutInMinutes := toMinutes(timerInMinutes)
if err != nil {
return err
}

timeoutInSeconds := timeoutInMinutes * 60
timeOfTimeout := time.Now().Add(time.Minute * time.Duration(timeoutInMinutes)).Format("15:04")
say.Debug(fmt.Sprintf("Starting timer at %s for %d minutes = %d seconds (parsed from user input %s)", timeOfTimeout, timeoutInMinutes, timeoutInSeconds, timerInMinutes))

room := getMobTimerRoom(configuration)
startRemoteTimer := room != ""
startLocalTimer := configuration.TimerLocal

if !startRemoteTimer && !startLocalTimer {
say.Error("No timer configured, not starting timer")
exit.Exit(1)
}

if startRemoteTimer {
timerUser := getUserForMobTimer(configuration.TimerUser)
err := httpPutTimer(timeoutInMinutes, room, timerUser, configuration.TimerUrl, configuration.TimerInsecure)
if err != nil {
say.Error("remote timer couldn't be started")
say.Error(err.Error())
exit.Exit(1)
}
}

if startLocalTimer {
err := executeCommandsInBackgroundProcess(getSleepCommand(timeoutInSeconds), getVoiceCommand(configuration.VoiceMessage, configuration.VoiceCommand), getNotifyCommand(configuration.NotifyMessage, configuration.NotifyCommand), "echo \"mobTimer\"")

if err != nil {
say.Error(fmt.Sprintf("timer couldn't be started on your system (%s)", runtime.GOOS))
say.Error(err.Error())
exit.Exit(1)
}
}

say.Info("It's now " + currentTime() + ". " + fmt.Sprintf("%d min timer ends at approx. %s", timeoutInMinutes, timeOfTimeout) + ". Happy collaborating! :)")
return nil
}

func getMobTimerRoom(configuration config.Configuration) string {
if !isGit() {
say.Debug("timer not in git repository, using MOB_TIMER_ROOM for room name")
return configuration.TimerRoom
}

currentWipBranchQualifier := configuration.WipBranchQualifier
if currentWipBranchQualifier == "" {
currentBranch := gitCurrentBranch()
currentBaseBranch, _ := determineBranches(currentBranch, gitBranches(), configuration)

if currentBranch.IsWipBranch(configuration) {
wipBranchWithoutWipPrefix := currentBranch.removeWipPrefix(configuration).Name
currentWipBranchQualifier = removePrefix(removePrefix(wipBranchWithoutWipPrefix, currentBaseBranch.Name), configuration.WipBranchQualifierSeparator)
}
}

if configuration.TimerRoomUseWipBranchQualifier && currentWipBranchQualifier != "" {
say.Info("Using wip branch qualifier for room name")
return currentWipBranchQualifier
}

return configuration.TimerRoom
configuration = enrichConfigurationWithBranchQualifier(configuration)
return timerpkg.RunTimer(timerInMinutes, configuration)
}

func StartBreakTimer(timerInMinutes string, configuration config.Configuration) {
Expand All @@ -95,99 +26,6 @@ func StartBreakTimer(timerInMinutes string, configuration config.Configuration)
}

func startBreakTimer(timerInMinutes string, configuration config.Configuration) error {
err, timeoutInMinutes := toMinutes(timerInMinutes)
if err != nil {
return err
}

timeoutInSeconds := timeoutInMinutes * 60
timeOfTimeout := time.Now().Add(time.Minute * time.Duration(timeoutInMinutes)).Format("15:04")
say.Debug(fmt.Sprintf("Starting break timer at %s for %d minutes = %d seconds (parsed from user input %s)", timeOfTimeout, timeoutInMinutes, timeoutInSeconds, timerInMinutes))

room := getMobTimerRoom(configuration)
startRemoteTimer := room != ""
startLocalTimer := configuration.TimerLocal

if !startRemoteTimer && !startLocalTimer {
say.Error("No break timer configured, not starting break timer")
exit.Exit(1)
}

if startRemoteTimer {
timerUser := getUserForMobTimer(configuration.TimerUser)
err := httpPutBreakTimer(timeoutInMinutes, room, timerUser, configuration.TimerUrl, configuration.TimerInsecure)

if err != nil {
say.Error("remote break timer couldn't be started")
say.Error(err.Error())
exit.Exit(1)
}
}

if startLocalTimer {
err := executeCommandsInBackgroundProcess(getSleepCommand(timeoutInSeconds), getVoiceCommand("mob start", configuration.VoiceCommand), getNotifyCommand("mob start", configuration.NotifyCommand), "echo \"mobTimer\"")

if err != nil {
say.Error(fmt.Sprintf("break timer couldn't be started on your system (%s)", runtime.GOOS))
say.Error(err.Error())
exit.Exit(1)
}
}

say.Info("It's now " + currentTime() + ". " + fmt.Sprintf("%d min break timer ends at approx. %s", timeoutInMinutes, timeOfTimeout) + ". So take a break now! :)")
return nil
}

func getUserForMobTimer(userOverride string) string {
if userOverride == "" {
return gitUserName()
}
return userOverride
}

func toMinutes(timerInMinutes string) (error, int) {
timeoutInMinutes, err := strconv.Atoi(timerInMinutes)
if err != nil || timeoutInMinutes < 1 {
say.Error(fmt.Sprintf("The parameter must be an integer number greater then zero"))
return errors.New("The parameter must be an integer number greater then zero"), 0
}
return nil, timeoutInMinutes
}

func httpPutTimer(timeoutInMinutes int, room string, user string, timerService string, disableSSLVerification bool) error {
putBody, _ := json.Marshal(map[string]interface{}{
"timer": timeoutInMinutes,
"user": user,
})
client := httpclient.CreateHttpClient(disableSSLVerification)
_, err := client.SendRequest(putBody, "PUT", timerService+room)
return err
}

func httpPutBreakTimer(timeoutInMinutes int, room string, user string, timerService string, disableSSLVerification bool) error {
putBody, _ := json.Marshal(map[string]interface{}{
"breaktimer": timeoutInMinutes,
"user": user,
})
client := httpclient.CreateHttpClient(disableSSLVerification)
_, err := client.SendRequest(putBody, "PUT", timerService+room)
return err
}

func getSleepCommand(timeoutInSeconds int) string {
return fmt.Sprintf("sleep %d", timeoutInSeconds)
}

func getVoiceCommand(message string, voiceCommand string) string {
if len(voiceCommand) == 0 {
return ""
}
return injectCommandWithMessage(voiceCommand, message)
}

func getNotifyCommand(message string, notifyCommand string) string {
if len(notifyCommand) == 0 {
return ""
}
return injectCommandWithMessage(notifyCommand, message)
configuration = enrichConfigurationWithBranchQualifier(configuration)
return timerpkg.RunBreakTimer(timerInMinutes, configuration)
}
122 changes: 122 additions & 0 deletions timer/localtimer/localtimer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package localtimer

import (
"fmt"
"os/exec"
"runtime"
"strings"

config "github.com/remotemobprogramming/mob/v5/configuration"
"github.com/remotemobprogramming/mob/v5/exit"
"github.com/remotemobprogramming/mob/v5/say"
"github.com/remotemobprogramming/mob/v5/timer"
"github.com/remotemobprogramming/mob/v5/workdir"
)

func init() {
timer.Register(func(configuration config.Configuration) timer.Timer {
if !configuration.TimerLocal {
return nil
}
return NewProcessLocalTimer(configuration)
})
}

// ProcessLocalTimer is a Timer implementation that uses background OS processes.
type ProcessLocalTimer struct {
configuration config.Configuration
}

func NewProcessLocalTimer(configuration config.Configuration) ProcessLocalTimer {
return ProcessLocalTimer{configuration: configuration}
}

func (t ProcessLocalTimer) StartTimer(minutes int) error {
timeoutInSeconds := minutes * 60
if err := ExecuteCommandsInBackgroundProcess(
sleepCommand(timeoutInSeconds),
VoiceCommand(t.configuration.VoiceMessage, t.configuration.VoiceCommand),
notifyCommand(t.configuration.NotifyMessage, t.configuration.NotifyCommand),
"echo \"mobTimer\"",
); err != nil {
return fmt.Errorf("timer couldn't be started on your system (%s): %w", runtime.GOOS, err)
}
return nil
}

func (t ProcessLocalTimer) StartBreakTimer(minutes int) error {
timeoutInSeconds := minutes * 60
if err := ExecuteCommandsInBackgroundProcess(
sleepCommand(timeoutInSeconds),
VoiceCommand("mob start", t.configuration.VoiceCommand),
notifyCommand("mob start", t.configuration.NotifyCommand),
"echo \"mobTimer\"",
); err != nil {
return fmt.Errorf("break timer couldn't be started on your system (%s): %w", runtime.GOOS, err)
}
return nil
}

func sleepCommand(timeoutInSeconds int) string {
return fmt.Sprintf("sleep %d", timeoutInSeconds)
}

// VoiceCommand builds the shell command string for the voice notification.
// Exported because it is also used by the moo feature in the main package.
func VoiceCommand(message string, voiceCommand string) string {
if len(voiceCommand) == 0 {
return ""
}
return injectCommandWithMessage(voiceCommand, message)
}

func notifyCommand(message string, notifyCommand string) string {
if len(notifyCommand) == 0 {
return ""
}
return injectCommandWithMessage(notifyCommand, message)
}

func injectCommandWithMessage(command string, message string) string {
placeHolders := strings.Count(command, "%s")
if placeHolders > 1 {
say.Error(fmt.Sprintf("Too many placeholders (%d) in format command string: %s", placeHolders, command))
exit.Exit(1)
}
if placeHolders == 0 {
return fmt.Sprintf("%s %s", command, message)
}
return fmt.Sprintf(command, message)
}

// ExecuteCommandsInBackgroundProcess runs the given shell commands in a background OS process.
// Exported because it is also used by the moo feature in the main package.
func ExecuteCommandsInBackgroundProcess(commands ...string) error {
cmds := make([]string, 0)
for _, c := range commands {
if len(c) > 0 {
cmds = append(cmds, c)
}
}
say.Debug(fmt.Sprintf("Operating System %s", runtime.GOOS))
var err error
switch runtime.GOOS {
case "windows":
err = runInBackground("powershell", "-command", fmt.Sprintf("start-process powershell -NoNewWindow -ArgumentList '-command \"%s\"'", strings.Join(cmds, ";")))
case "darwin", "linux":
err = runInBackground("sh", "-c", fmt.Sprintf("(%s) &", strings.Join(cmds, ";")))
default:
say.Warning(fmt.Sprintf("Cannot execute background commands on your os: %s", runtime.GOOS))
}
return err
}

func runInBackground(name string, args ...string) error {
command := exec.Command(name, args...)
if len(workdir.Path) > 0 {
command.Dir = workdir.Path
}
commandString := strings.Join(command.Args, " ")
say.Debug("Starting command " + commandString)
return command.Start()
}
Loading
Loading