From 3d3a2bc152388ee8cefbf88b754822549e0ab63a Mon Sep 17 00:00:00 2001 From: Sam Boysel Date: Mon, 30 May 2022 23:20:58 -0700 Subject: [PATCH 1/5] execute command on state change --- README.md | 21 +++++++++++++++++++++ pkg/internal/config.go | 1 + pkg/internal/runner.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/README.md b/README.md index 5aa4e3e..f5a216e 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,27 @@ Example: } ``` +### Execute command on state change + +Pomo will execute the command specified in the array argument `onEvent` when the +state changes. The new state will be exported as an environment variable +`POMO_STATE` for this command. For example, to trigger a terminal bell when a +session complete, add the following to `config.json` +``` +... +"onEvent": ["/bin/sh", "/path/to/script/my_script.sh"] +... +``` +where the contents of `my_script.sh` are +``` +#!/bin/sh + +if [ "$POMO_STATE" == "COMPLETE" ] ; then + echo -e '\a' +fi +``` +Possible state values are `RUNNING`, `PAUSED`, `BREAKING`, or `COMPLETE`. + ## Integrations By default pomo will setup a Unix socket and serve it's status there. diff --git a/pkg/internal/config.go b/pkg/internal/config.go index d4ecf3c..85033f8 100644 --- a/pkg/internal/config.go +++ b/pkg/internal/config.go @@ -23,6 +23,7 @@ type Config struct { DBPath string `json:"dbPath"` SocketPath string `json:"socketPath"` IconPath string `json:"iconPath"` + OnEvent []string `json:"onEvent"` // Publish pushes updates to the configured // SocketPath rather than listening for requests Publish bool `json:"publish"` diff --git a/pkg/internal/runner.go b/pkg/internal/runner.go index 58a0039..9a83fef 100644 --- a/pkg/internal/runner.go +++ b/pkg/internal/runner.go @@ -2,6 +2,9 @@ package pomo import ( "database/sql" + "fmt" + "os" + "os/exec" "sync" "time" ) @@ -21,6 +24,7 @@ type TaskRunner struct { notifier Notifier duration time.Duration mu sync.Mutex + onEvent []string } func NewMockedTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) { @@ -55,6 +59,7 @@ func NewTaskRunner(task *Task, config *Config) (*TaskRunner, error) { toggle: make(chan bool), notifier: NewXnotifier(config.IconPath), duration: task.Duration, + onEvent: config.OnEvent, } return tr, nil } @@ -75,6 +80,20 @@ func (t *TaskRunner) SetState(state State) { t.state = state } +// execute script command specified by `onEvent` on state change +func (t *TaskRunner) OnEvent() error { + app, args := t.onEvent[0], t.onEvent[1:len(t.onEvent)] + cmd := exec.Command(app, args...) + cmd.Env = append(os.Environ(), + fmt.Sprintf("POMO_STATE=%s", t.state), + ) + err := cmd.Run() + if err != nil { + return err + } + return nil +} + func (t *TaskRunner) run() error { for t.count < t.nPomodoros { // Create a new pomodoro where we @@ -85,6 +104,8 @@ func (t *TaskRunner) run() error { pomodoro.Start = time.Now() // Set state to RUNNIN t.SetState(RUNNING) + // Execute onEvent command + t.OnEvent() // Create a new timer timer := time.NewTimer(t.duration) // Record our started time @@ -104,6 +125,8 @@ func (t *TaskRunner) run() error { remaining := t.TimeRemaining() // Change state to PAUSED t.SetState(PAUSED) + // Execute onEvent command + t.OnEvent() // Wait for the user to press [p] <-t.pause // Resume the timer with previous @@ -114,6 +137,8 @@ func (t *TaskRunner) run() error { t.duration = remaining // Restore state to RUNNING t.SetState(RUNNING) + // Execute onEvent command + t.OnEvent() goto loop } pomodoro.End = time.Now() @@ -128,6 +153,8 @@ func (t *TaskRunner) run() error { break } t.SetState(BREAKING) + // Execute onEvent command + t.OnEvent() t.notifier.Notify("Pomo", "It is time to take a break!") // Reset the duration incase it // was paused. @@ -138,6 +165,8 @@ func (t *TaskRunner) run() error { } t.notifier.Notify("Pomo", "Pomo session has completed!") t.SetState(COMPLETE) + // Execute onEvent command + t.OnEvent() return nil } From caded9b68b1b7c987b19888fd16615765fe6b699 Mon Sep 17 00:00:00 2001 From: Sam Boysel Date: Mon, 30 May 2022 23:36:33 -0700 Subject: [PATCH 2/5] fix README typos and edit for clarity --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f5a216e..f0e0a66 100644 --- a/README.md +++ b/README.md @@ -67,24 +67,29 @@ Example: ### Execute command on state change -Pomo will execute the command specified in the array argument `onEvent` when the -state changes. The new state will be exported as an environment variable -`POMO_STATE` for this command. For example, to trigger a terminal bell when a -session complete, add the following to `config.json` -``` +Pomo will execute an arbitrary command specified in the array argument `onEvent` +when the state changes. The first element of this array should be the +executable to run while the remaining elements are space delimited arguments. +The new state will be exported as an environment variable `POMO_STATE` for this +command. Possible state values are `RUNNING`, `PAUSED`, `BREAKING`, or +`COMPLETE`. + + +For example, to trigger a terminal bell when a session completes, add the +following to `config.json`: +```json ... -"onEvent": ["/bin/sh", "/path/to/script/my_script.sh"] +"onEvent": ["/bin/sh", "/path/to/script/my_script.sh"], ... ``` where the contents of `my_script.sh` are -``` +```bash #!/bin/sh if [ "$POMO_STATE" == "COMPLETE" ] ; then echo -e '\a' fi ``` -Possible state values are `RUNNING`, `PAUSED`, `BREAKING`, or `COMPLETE`. ## Integrations From 3ba07e9a873427e0c8f08559ca37273845534f8d Mon Sep 17 00:00:00 2001 From: Sam Boysel Date: Tue, 31 May 2022 13:35:15 -0700 Subject: [PATCH 3/5] check if onEvent is set, parse argument array --- README.md | 1 - pkg/internal/runner.go | 30 +++++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f0e0a66..3a1f32b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ The new state will be exported as an environment variable `POMO_STATE` for this command. Possible state values are `RUNNING`, `PAUSED`, `BREAKING`, or `COMPLETE`. - For example, to trigger a terminal bell when a session completes, add the following to `config.json`: ```json diff --git a/pkg/internal/runner.go b/pkg/internal/runner.go index 9a83fef..ea9fe17 100644 --- a/pkg/internal/runner.go +++ b/pkg/internal/runner.go @@ -78,15 +78,29 @@ func (t *TaskRunner) TimePauseDuration() time.Duration { func (t *TaskRunner) SetState(state State) { t.state = state + // execute onEvent command if variable is set + if t.onEvent != nil { + t.runOnEvent() + } } // execute script command specified by `onEvent` on state change -func (t *TaskRunner) OnEvent() error { - app, args := t.onEvent[0], t.onEvent[1:len(t.onEvent)] - cmd := exec.Command(app, args...) +func (t *TaskRunner) runOnEvent() error { + var cmd *exec.Cmd + // parse command arguments + numArgs := len(t.onEvent) + app := t.onEvent[0] + if numArgs > 1 { + args := t.onEvent[1:(numArgs + 1)] + cmd = exec.Command(app, args...) + } else { + cmd = exec.Command(app) + } + // set state in command environment cmd.Env = append(os.Environ(), fmt.Sprintf("POMO_STATE=%s", t.state), ) + // run command err := cmd.Run() if err != nil { return err @@ -104,8 +118,6 @@ func (t *TaskRunner) run() error { pomodoro.Start = time.Now() // Set state to RUNNIN t.SetState(RUNNING) - // Execute onEvent command - t.OnEvent() // Create a new timer timer := time.NewTimer(t.duration) // Record our started time @@ -125,8 +137,6 @@ func (t *TaskRunner) run() error { remaining := t.TimeRemaining() // Change state to PAUSED t.SetState(PAUSED) - // Execute onEvent command - t.OnEvent() // Wait for the user to press [p] <-t.pause // Resume the timer with previous @@ -137,8 +147,6 @@ func (t *TaskRunner) run() error { t.duration = remaining // Restore state to RUNNING t.SetState(RUNNING) - // Execute onEvent command - t.OnEvent() goto loop } pomodoro.End = time.Now() @@ -153,8 +161,6 @@ func (t *TaskRunner) run() error { break } t.SetState(BREAKING) - // Execute onEvent command - t.OnEvent() t.notifier.Notify("Pomo", "It is time to take a break!") // Reset the duration incase it // was paused. @@ -165,8 +171,6 @@ func (t *TaskRunner) run() error { } t.notifier.Notify("Pomo", "Pomo session has completed!") t.SetState(COMPLETE) - // Execute onEvent command - t.OnEvent() return nil } From 81cb8f568f7a66cf9640d892bf4fc16e598678b0 Mon Sep 17 00:00:00 2001 From: Sam Boysel Date: Tue, 31 May 2022 18:06:43 -0700 Subject: [PATCH 4/5] execute onEvent command in Go routine --- pkg/internal/runner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/internal/runner.go b/pkg/internal/runner.go index ea9fe17..46a802c 100644 --- a/pkg/internal/runner.go +++ b/pkg/internal/runner.go @@ -80,7 +80,7 @@ func (t *TaskRunner) SetState(state State) { t.state = state // execute onEvent command if variable is set if t.onEvent != nil { - t.runOnEvent() + go t.runOnEvent() } } From f86c5a643671acbff51592fbb593933d7de02447 Mon Sep 17 00:00:00 2001 From: Sam Boysel Date: Tue, 31 May 2022 18:10:10 -0700 Subject: [PATCH 5/5] adds contrib dir for user contributed scripts --- README.md | 2 ++ contrib/.gitkeep | 0 contrib/bell | 3 +++ contrib/pomonag | 5 +++++ 4 files changed, 10 insertions(+) create mode 100644 contrib/.gitkeep create mode 100755 contrib/bell create mode 100755 contrib/pomonag diff --git a/README.md b/README.md index 3a1f32b..e1ea9fc 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ if [ "$POMO_STATE" == "COMPLETE" ] ; then fi ``` +See the `contrib` directory for user contributed scripts for use with `onEvent`. + ## Integrations By default pomo will setup a Unix socket and serve it's status there. diff --git a/contrib/.gitkeep b/contrib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/contrib/bell b/contrib/bell new file mode 100755 index 0000000..b4c6e8f --- /dev/null +++ b/contrib/bell @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e '\e' diff --git a/contrib/pomonag b/contrib/pomonag new file mode 100755 index 0000000..4259c6e --- /dev/null +++ b/contrib/pomonag @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ "$POMO_STATE" == "BREAKING" ] ; then + swaynag -m "It's time to take a break!" +fi