Compare commits

...

10 Commits

Author SHA1 Message Date
Kevin Schoon 309277a115
Merge pull request #62 from sboysel/feature/ui-task-message
Display current task message while running or paused
2 years ago
Sam Boysel 8113c0a933 display current task message while running or paused 2 years ago
Kevin Schoon e6aa45152c
Merge pull request #61 from sboysel/feature/exec-on-event
Execute command on state change
2 years ago
Sam Boysel f86c5a6436 adds contrib dir for user contributed scripts 2 years ago
Sam Boysel 81cb8f568f execute onEvent command in Go routine 2 years ago
Sam Boysel 3ba07e9a87 check if onEvent is set, parse argument array 2 years ago
Sam Boysel caded9b68b fix README typos and edit for clarity 2 years ago
Sam Boysel 3d3a2bc152 execute command on state change 2 years ago
Kevin Schoon 6236144041 add man page 2 years ago
Kevin Schoon 1b321198fb fix issue where interface can block
Fixed an issue where the UI can block when certain key combinations are
pressed in different states. An alternative and more robust approach would
likely be to re-write the runner code as a finite state machine, however
these quick fixes work okay.

Additionally cleaned up some spacing in console messages and added a CREATED
state which is the default state of a pomodoro.
2 years ago

@ -10,7 +10,8 @@ LDFLAGS=\
test \
docs \
pomo-build \
readme
readme \
bin/pomo
default: bin/pomo test
@ -28,6 +29,11 @@ test:
install:
go install ./cmd/...
man/pomo.1: man/pomo.1.scd
scdoc < $< > $@
manpages: man/pomo.1
docs: www/data/readme.json
cd www && hugo -d ../docs

@ -49,7 +49,7 @@ pomo start -t my-project "write some codes"
## Configuration
Pomo has a few configuration options which can be read from a JSON file in Pomo's state directory `~/.pomo/config.json`.
Pomo has a few configuration options which can be read from a JSON file in Pomo's config directory `~/.config/pomo/config.json`.
### colors
@ -65,6 +65,33 @@ Example:
}
```
### Execute command on state change
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"],
...
```
where the contents of `my_script.sh` are
```bash
#!/bin/sh
if [ "$POMO_STATE" == "COMPLETE" ] ; then
echo -e '\a'
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.

@ -0,0 +1,3 @@
#!/bin/sh
echo -e '\e'

@ -0,0 +1,5 @@
#!/bin/sh
if [ "$POMO_STATE" == "BREAKING" ] ; then
swaynag -m "It's time to take a break!"
fi

@ -0,0 +1,89 @@
.\" Generated by scdoc 1.11.2
.\" Complete documentation for this program is not available as a GNU info page
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "pomo" "1" "2022-05-30"
.P
.SH NAME
.P
\fBPomo\fR is a simple CLI for using the Pomodoro Technique.\&
.P
.SH SYNOPSIS
.P
\fBpomo\fR [OPTIONS] COMMAND [arg.\&.\&.\&]
.P
.SH DESCRIPTION
.P
\fBpomo\fR helps you track what you did, how long it took you to do it,
and how much effort you expect it to take.\&
.P
The Pomodoro Technique is simple and effective:
.P
.RS 4
\fB\fR Decide on a task you want to accomplish
\fB\fR Break the task into timed intervals (pomodoros), [approx.\& 25 min]
\fB\fR After each pomodoro take a short break [approx.\& 3 - 5 min]
\fB\fR Once all pomodoros are completed take a longer break [approx 15 - 20 min]
\fB\fR Repeat
.P
.RE
.SH SUBCOMMANDS
.P
See --help for the complete command usage
.P
.nf
.RS 4
start, s start a new task
init initialize the sqlite database
config, cf display the current configuration
create, c create a new task without starting
begin, b begin requested pomodoro
list, l list historical tasks
delete, d delete a stored task
status, st output the current status
.fi
.RE
.P
.SH CONFIGURATION
.P
Pomo has a configuration file that is stored in \fB~/.\&config/pomo/config.\&json\fR.\&
.P
.nf
.RS 4
{
"colors": null,
"dateTimeFmt": "2006-01-02 15:04",
"publish": false,
"publishJson": false,
"publishSocketPath": ""
}
.fi
.RE
.P
.SH EXAMPLES
.P
.SS GETTING STARTED
.P
.nf
.RS 4
# ensure your database has been initialized
pomo init
# run a new pomodoro
pomo start -t my-project "write some code"
# once finished view previously completed pomodoros
pomo list
.fi
.RE
.P
.SH SEE ALSO
.P
See the pomo source repository on Github at https://github.\&com/kevinschoon/pomo for complete documentation.\&
.P
.SH AUTHORS
.P
Written by Kevin Schoon <me@kevinschoon.\&com> with help from the open source
community.\&

@ -0,0 +1,74 @@
pomo(1)
# NAME
*Pomo* is a simple CLI for using the Pomodoro Technique.
# SYNOPSIS
*pomo* [OPTIONS] COMMAND [arg...]
# DESCRIPTION
*pomo* helps you track what you did, how long it took you to do it,
and how much effort you expect it to take.
The Pomodoro Technique is simple and effective:
** Decide on a task you want to accomplish
** Break the task into timed intervals (pomodoros), [approx. 25 min]
** After each pomodoro take a short break [approx. 3 - 5 min]
** Once all pomodoros are completed take a longer break [approx 15 - 20 min]
** Repeat
# SUBCOMMANDS
See --help for the complete command usage
```
start, s start a new task
init initialize the sqlite database
config, cf display the current configuration
create, c create a new task without starting
begin, b begin requested pomodoro
list, l list historical tasks
delete, d delete a stored task
status, st output the current status
```
# CONFIGURATION
Pomo has a configuration file that is stored in *~/.config/pomo/config.json*.
```
{
"colors": null,
"dateTimeFmt": "2006-01-02 15:04",
"publish": false,
"publishJson": false,
"publishSocketPath": ""
}
```
# EXAMPLES
## GETTING STARTED
```
# ensure your database has been initialized
pomo init
# run a new pomodoro
pomo start -t my-project "write some code"
# once finished view previously completed pomodoros
pomo list
```
# SEE ALSO
See the pomo source repository on Github at https://github.com/kevinschoon/pomo for complete documentation.
# AUTHORS
Written by Kevin Schoon <me@kevinschoon.com> with help from the open source
community.

@ -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"`

@ -2,6 +2,10 @@ package pomo
import (
"database/sql"
"fmt"
"os"
"os/exec"
"sync"
"time"
)
@ -19,6 +23,8 @@ type TaskRunner struct {
toggle chan bool
notifier Notifier
duration time.Duration
mu sync.Mutex
onEvent []string
}
func NewMockedTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) {
@ -28,7 +34,7 @@ func NewMockedTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunn
nPomodoros: task.NPomodoros,
origDuration: task.Duration,
store: store,
state: State(0),
state: CREATED,
pause: make(chan bool),
toggle: make(chan bool),
notifier: notifier,
@ -53,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
}
@ -71,6 +78,34 @@ 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 {
go t.runOnEvent()
}
}
// execute script command specified by `onEvent` on state change
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
}
return nil
}
func (t *TaskRunner) run() error {
@ -90,7 +125,6 @@ func (t *TaskRunner) run() error {
loop:
select {
case <-timer.C:
t.SetState(BREAKING)
t.stopped = time.Now()
t.count++
case <-t.toggle:
@ -126,7 +160,7 @@ func (t *TaskRunner) run() error {
if t.count == t.nPomodoros {
break
}
t.SetState(BREAKING)
t.notifier.Notify("Pomo", "It is time to take a break!")
// Reset the duration incase it
// was paused.
@ -141,15 +175,25 @@ func (t *TaskRunner) run() error {
}
func (t *TaskRunner) Toggle() {
t.toggle <- true
t.mu.Lock()
defer t.mu.Unlock()
if t.state == BREAKING {
t.toggle <- true
}
}
func (t *TaskRunner) Pause() {
t.pause <- true
t.mu.Lock()
defer t.mu.Unlock()
if t.state == PAUSED || t.state == RUNNING {
t.pause <- true
}
}
func (t *TaskRunner) Status() *Status {
return &Status{
TaskID: t.taskID,
TaskMessage: t.taskMessage,
State: t.state,
Count: t.count,
NPomodoros: t.nPomodoros,

@ -12,6 +12,8 @@ type State int
func (s State) String() string {
switch s {
case CREATED:
return "CREATED"
case RUNNING:
return "RUNNING"
case BREAKING:
@ -25,7 +27,8 @@ func (s State) String() string {
}
const (
RUNNING State = iota + 1
CREATED State = iota
RUNNING
BREAKING
COMPLETE
PAUSED
@ -102,6 +105,8 @@ func (p Pomodoro) Duration() time.Duration {
// Status is used to communicate the state
// of a running Pomodoro session
type Status struct {
TaskID int `json:"task_id"`
TaskMessage string `json:"task_message"`
State State `json:"state"`
Remaining time.Duration `json:"remaining"`
Pauseduration time.Duration `json:"pauseduration"`

@ -14,6 +14,8 @@ func setContent(wheel *Wheel, status *Status, par *widgets.Paragraph) {
par.Text = fmt.Sprintf(
`[%d/%d] Pomodoros completed
Current Task: %s
%s %s remaining
@ -21,6 +23,7 @@ func setContent(wheel *Wheel, status *Status, par *widgets.Paragraph) {
`,
status.Count,
status.NPomodoros,
status.TaskMessage,
wheel,
status.Remaining,
)
@ -29,26 +32,29 @@ func setContent(wheel *Wheel, status *Status, par *widgets.Paragraph) {
par.Text = fmt.Sprintf(
`It is time to take a break!
Once you are ready, press [Enter]
to begin the next Pomodoro
%s %s pause duration
%s %s break duration
[q] - quit [p] - pause
[q] - quit
`,
wheel,
status.Pauseduration,
)
case PAUSED:
par.Text = `Pomo is suspended.
Press [p] to continue.
[q] - quit [p] - unpause
`
par.Text = fmt.Sprintf(`Pomo is suspended.
Current Task: %s
Press [p] to continue.
[q] - quit [p] - unpause
`,
status.TaskMessage,
)
case COMPLETE:
par.Text = `This session has concluded.
@ -83,16 +89,20 @@ func StartUI(runner *TaskRunner) {
resize := func() {
termWidth, termHeight := ui.TerminalDimensions()
// for RUNNING and PAUSED states
x1 := (termWidth - 50) / 2
x2 := x1 + 50
y1 := (termHeight - 8) / 2
y2 := y1 + 8
y1 := (termHeight - 10) / 2
y2 := y1 + 10
switch runner.state {
case BREAKING:
y1 = (termHeight - 12) / 2
y2 = y1 + 12
y1 = (termHeight - 11) / 2
y2 = y1 + 11
case COMPLETE:
y1 = (termHeight - 8) / 2
y2 = y1 + 8
}
par.SetRect(x1, y1, x2, y2)

Loading…
Cancel
Save