aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/config8
-rwxr-xr-xcore/help37
-rw-r--r--core/lib18
-rw-r--r--core/path5
-rwxr-xr-xcore/pause13
-rwxr-xr-xcore/reset5
-rwxr-xr-xcore/start39
-rwxr-xr-xcore/state21
l---------core/stop1
-rwxr-xr-xcore/toggle7
-rw-r--r--core/update55
-rwxr-xr-xdppt35
-rw-r--r--license21
13 files changed, 265 insertions, 0 deletions
diff --git a/core/config b/core/config
new file mode 100644
index 0000000..34c96b8
--- /dev/null
+++ b/core/config
@@ -0,0 +1,8 @@
+#!/bin/sh
+# initialize default configuration values
+export POMODORO_STATE_PATH="${POMODORO_STATE_PATH:-$XDG_CACHE_HOME/$progname}" # ~/.cache/dppt
+export POMODORO_NORMAL_DURATION="${POMODORO_NORMAL_DURATION:-$(( 25 * 60 ))}" # 25 minutes
+export POMODORO_BREAK_SHORT_DURATION="${POMODORO_BREAK_SHORT_DURATION:-$(( 5 * 60 ))}" # 5 minutes
+export POMODORO_BREAK_LONG_DURATION="${POMODORO_BREAK_LONG_DURATION:-$(( 15 * 60 ))}" # 15 minutes
+export POMODORO_BREAK_SHORT_INTERVAL="${POMODORO_BREAK_INTERVAL:-2}" # every other lap
+export POMODORO_BREAK_LONG_INTERVAL="${POMODORO_BREAK_INTERVAL:-6}" # every 3rd break
diff --git a/core/help b/core/help
new file mode 100755
index 0000000..98abd1e
--- /dev/null
+++ b/core/help
@@ -0,0 +1,37 @@
+#!/bin/sh
+[ "$1" = "info" ] && echo "show this very message" && exit 2
+
+cat << EOF
+$progname -- daemonless posix pomodoro timer
+
+usage:
+ $progname [options] [action] [action options]
+
+options:
+ -h, --help show help
+
+actions:
+EOF
+
+IFS=':'
+find $path -type l,f -perm /u=x,g=x,o=x 2>/dev/null | while read -r action ; do
+ action_name="$(basename "$action")"
+ if [ -L "$action" ] ; then
+ printf '%s\t%s\n' "$action_name" "same as $(basename "$(readlink -f "$action")")"
+ else
+ action_info="$("$action" info)"
+ [ $? -eq 2 ] && printf '%s\t%s\n' "$action_name" "$action_info"
+ fi
+done | awk '
+ BEGIN { FS = "\t" }
+ {
+ if (printed[$1]) next
+ printf("\t%-13s %s\n", $1, $2)
+ printed[$1] = 1
+ }
+'
+
+cat << EOF
+
+if no action is provided, the \`state\` action is chosen by default
+EOF
diff --git a/core/lib b/core/lib
new file mode 100644
index 0000000..9892663
--- /dev/null
+++ b/core/lib
@@ -0,0 +1,18 @@
+#!/bin/sh
+# utility variables and functions
+XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" # XDG basedir specification
+
+# this variable grows automatically as long as this file is included in each
+# subcommand
+export subcmd="$subcmd${subcmd:+ }$(basename "$0")"
+
+# print an error to stdout and exit
+err() {
+ echo "error: $*" >&2
+ echo "run \`$subcmd --help\` for options" >&2
+ exit 1
+}
+
+# debug:
+# bc() { tee /dev/stderr | /usr/bin/bc ; }
+
diff --git a/core/path b/core/path
new file mode 100644
index 0000000..23f7f5c
--- /dev/null
+++ b/core/path
@@ -0,0 +1,5 @@
+#!/bin/sh
+# initialize default search path for core + plugins
+path="$core_path"
+path="$XDG_DATA_HOME/$progname:$path"
+export path
diff --git a/core/pause b/core/pause
new file mode 100755
index 0000000..a55bd30
--- /dev/null
+++ b/core/pause
@@ -0,0 +1,13 @@
+#!/bin/sh
+[ "$1" = "info" ] && echo "pause the timer" && exit 2
+
+. "$core_path/lib"
+. "$core_path/update"
+
+[ "$state" != 'running' ] && err "timer is not running"
+
+time="$("$time - $now" | bc)"
+state='paused'
+
+save_state
+
diff --git a/core/reset b/core/reset
new file mode 100755
index 0000000..5492dce
--- /dev/null
+++ b/core/reset
@@ -0,0 +1,5 @@
+#!/bin/sh
+[ "$1" = "info" ] && echo "reset the timer and lap counter" && exit 2
+
+rm -rf "$POMODORO_STATE_PATH"
+
diff --git a/core/start b/core/start
new file mode 100755
index 0000000..ae471ad
--- /dev/null
+++ b/core/start
@@ -0,0 +1,39 @@
+#!/bin/sh
+[ "$1" = "info" ] && echo "start or resume the timer" && exit 2
+
+usage() {
+ cat << EOF
+usage:
+ $subcmd [options]
+
+options:
+ -h, --help display this help text
+ -s, --skip skip to next lap, even if the timer is currently running
+
+EOF
+}
+
+. "$core_path/lib"
+. "$core_path/update"
+
+allow_skip=0
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ -h|--help) usage && exit 0 ;;
+ -s|--skip) shift ; allow_skip=1 ;;
+ --) shift ; break ;;
+ *) err "unknown parameter: $1" ;;
+ esac
+done
+
+if [ "$state" = 'running' ] ; then
+ [ $allow_skip -eq 0 ] && err "timer is already running, use -s to skip lap"
+ time="0.0"
+ lap=$(( $lap + 1 ))
+ . "$core_path/update"
+fi
+
+time="$(echo "$now + $time" | bc)"
+state='running'
+
+save_state
diff --git a/core/state b/core/state
new file mode 100755
index 0000000..fcf327d
--- /dev/null
+++ b/core/state
@@ -0,0 +1,21 @@
+#!/bin/sh
+[ "$1" = "info" ] && echo "show timer" && exit 2
+. "$core_path/lib"
+
+usage() {
+ cat << EOF
+$subcmd -- show timer
+
+usage:
+ $subcmd [options]
+
+options:
+ -h, --help show help
+
+actions:
+EOF
+}
+
+. "$core_path/update"
+echo "[$state] $lap: ${remaining}s"
+
diff --git a/core/stop b/core/stop
new file mode 120000
index 0000000..398b326
--- /dev/null
+++ b/core/stop
@@ -0,0 +1 @@
+reset \ No newline at end of file
diff --git a/core/toggle b/core/toggle
new file mode 100755
index 0000000..bfb96d2
--- /dev/null
+++ b/core/toggle
@@ -0,0 +1,7 @@
+#!/bin/sh
+[ "$1" = "info" ] && echo "toggle the timer between running and paused" && exit 2
+
+. "$core_path/update"
+
+[ "$state" = 'running' ] && exec "$prog" pause || exec "$prog" start
+
diff --git a/core/update b/core/update
new file mode 100644
index 0000000..04fd29c
--- /dev/null
+++ b/core/update
@@ -0,0 +1,55 @@
+#!/bin/sh
+export now="$(date +%s.%N)"
+
+update_time=0
+
+mkdir -p "$POMODORO_STATE_PATH"
+load_or_init() {
+ property="$1"
+ default_value="$2"
+ property_path="$POMODORO_STATE_PATH/$property"
+ if [ -f "$property_path" ] ; then
+ eval $property='"$(cat "$property_path")"'
+ else
+ eval $property='$default_value'
+ fi
+}
+[ -z "$lap" ] && load_or_init lap 0
+[ -z "$state" ] && load_or_init state 'reset'
+[ -z "$time" ] && load_or_init time 0.0
+[ "$time" = "0.0" ] && update_time=1
+
+# calculate remaining time on timer
+if [ -z "$remaining" ] ; then
+ remaining="$time"
+ [ "$state" = 'running' ] && remaining="$(echo "( $time - $now ) / 1" | bc)"
+fi
+
+# go to next lap if this timer expired
+if [ "$state" = 'running' ] && [ "${remaining#-}" != "${remaining}" ] ; then
+ lap=$(( $lap + 1 ))
+ state='paused'
+ update_time=1
+fi
+
+# update remaining time if in reset
+[ "$state" = 'reset' ] && update_time=1
+
+if [ $update_time -eq 1 ] ; then
+ time="$POMODORO_NORMAL_DURATION"
+ [ $(( $lap % $POMODORO_BREAK_SHORT_INTERVAL )) -eq $(( $POMODORO_BREAK_SHORT_INTERVAL - 1 )) ] && time=$POMODORO_BREAK_SHORT_DURATION
+ [ $(( $lap % $POMODORO_BREAK_LONG_INTERVAL )) -eq $(( $POMODORO_BREAK_LONG_INTERVAL - 1 )) ] && time=$POMODORO_BREAK_LONG_DURATION
+ remaining="$time"
+fi
+
+# save state
+save_state() {
+ mkdir -p "$POMODORO_STATE_PATH"
+ echo "$lap" > "$POMODORO_STATE_PATH/lap"
+ echo "$state" > "$POMODORO_STATE_PATH/state"
+ echo "$time" > "$POMODORO_STATE_PATH/time"
+}
+
+# export state variables
+export lap state time
+
diff --git a/dppt b/dppt
new file mode 100755
index 0000000..72c3ad0
--- /dev/null
+++ b/dppt
@@ -0,0 +1,35 @@
+#!/bin/sh
+export prog="$0"
+export progname="$(basename "$0")"
+export core_path="$(dirname "$(readlink -f "$0")")/core"
+
+. "$core_path/lib"
+. "$core_path/path"
+. "$core_path/config"
+
+# parse arguments (does not support joined arguments)
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ -h|--help) shift ; exec "$prog" help ;;
+ --) shift ; break ;; # stop parsing options
+ *) break ;;
+ esac
+done
+
+# action is first argument after options
+action="$1"
+# default action is to display state
+[ -z "$action" ] && exec "$prog" state
+shift
+
+IFS=':' # make sure for loop splits $path on colon
+for dir in $path ; do
+ # action must be in $path and be executable
+ ! [ -x "$dir/$action" ] && continue
+ # execute first action that matches above criteria (in order of path)
+ exec "$dir/$action" "$@"
+done
+
+# this point is only reached if no $action was `exec`-ed
+err "unknown action \"$action\""
+
diff --git a/license b/license
new file mode 100644
index 0000000..6faf3cf
--- /dev/null
+++ b/license
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 lonkaars
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.