Compare commits
10 Commits
1cac8904da
...
309277a115
Author | SHA1 | Date |
---|---|---|
Kevin Schoon | 309277a115 | |
Sam Boysel | 8113c0a933 | |
Kevin Schoon | e6aa45152c | |
Sam Boysel | f86c5a6436 | |
Sam Boysel | 81cb8f568f | |
Sam Boysel | 3ba07e9a87 | |
Sam Boysel | caded9b68b | |
Sam Boysel | 3d3a2bc152 | |
Kevin Schoon | 6236144041 | |
Kevin Schoon | 1b321198fb |
8
Makefile
8
Makefile
|
@ -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
|
||||
|
||||
|
|
29
README.md
29
README.md
|
@ -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.
|
||||
par.Text = fmt.Sprintf(`Pomo is suspended.
|
||||
|
||||
Current Task: %s
|
||||
|
||||
Press [p] to continue.
|
||||
|
||||
|
||||
[q] - quit [p] - unpause
|
||||
`
|
||||
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…
Reference in New Issue