├── .gitignore ├── test ├── data │ ├── timebox15.out │ ├── timebox30.out │ ├── timebox-duration-1.out │ ├── timebox-duration-5.out │ ├── timebox-duration-6.out │ ├── timebox-duration-15.out │ ├── timebox-msg.out │ ├── timebox-conf-multi.out │ ├── timebox-conf-quiet.out │ ├── timebox-duration-16.out │ ├── timebox-conf-sober.out │ ├── timebox-duration-default.out │ └── timebox-conf-qtpi.out ├── setup ├── test └── test_timebox ├── .travis.yml ├── Makefile ├── LICENSE.md ├── CHANGES.md ├── dev └── README.md ├── README.md ├── timebox └── timebox.cmd /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | /coverage/ 3 | -------------------------------------------------------------------------------- /test/data/timebox15.out: -------------------------------------------------------------------------------- 1 | TIME - 15 2 | TIME - 10 3 | TIME - 05 4 | TIME - 00 5 | :-) :-) :-) :-) :-) 6 | -------------------------------------------------------------------------------- /test/data/timebox30.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | TIME - 25 3 | TIME - 20 4 | TIME - 15 5 | TIME - 10 6 | TIME - 05 7 | TIME - 00 8 | :-) :-) :-) :-) :-) 9 | -------------------------------------------------------------------------------- /test/data/timebox-duration-1.out: -------------------------------------------------------------------------------- 1 | TIME - 1 2 | 3 | 4 | TIME - 0 5 | 6 | 7 | 8 | 9 | :-) :-) :-) :-) :-) 10 | -------------------------------------------------------------------------------- /test/data/timebox-duration-5.out: -------------------------------------------------------------------------------- 1 | TIME - 5 2 | 3 | 4 | TIME - 0 5 | 6 | 7 | 8 | 9 | :-) :-) :-) :-) :-) 10 | -------------------------------------------------------------------------------- /test/data/timebox-duration-6.out: -------------------------------------------------------------------------------- 1 | TIME - 6 2 | 3 | 4 | TIME - 5 5 | 6 | 7 | TIME - 0 8 | 9 | 10 | 11 | 12 | :-) :-) :-) :-) :-) 13 | -------------------------------------------------------------------------------- /test/data/timebox-duration-15.out: -------------------------------------------------------------------------------- 1 | TIME - 15 2 | 3 | 4 | TIME - 10 5 | TIME - 05 6 | 7 | 8 | TIME - 00 9 | 10 | 11 | 12 | 13 | :-) :-) :-) :-) :-) 14 | -------------------------------------------------------------------------------- /test/data/timebox-msg.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | TIME - 25 3 | TIME - 20 4 | TIME - 15 5 | TIME - 10 6 | TIME - 05 7 | TIME - 00 8 | :-) :-) :-) :-) :-) 9 | msg1 10 | msg2 11 | msg3: :-) :-) :-) :-) :-) 12 | -------------------------------------------------------------------------------- /test/data/timebox-conf-multi.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | 3 | 4 | TIME - 25 5 | TIME - 20 6 | TIME - 15 7 | TIME - 10 8 | TIME - 05 9 | TIME - 00 10 | 11 | 12 | 13 | 14 | EOT 15 | msg: EOT 16 | -------------------------------------------------------------------------------- /test/data/timebox-conf-quiet.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | 3 | 4 | TIME - 25 5 | TIME - 20 6 | TIME - 15 7 | TIME - 10 8 | TIME - 05 9 | TIME - 00 10 | 11 | 12 | 13 | 14 | :-) :-) :-) :-) :-) 15 | -------------------------------------------------------------------------------- /test/data/timebox-duration-16.out: -------------------------------------------------------------------------------- 1 | TIME - 16 2 | 3 | 4 | TIME - 15 5 | 6 | TIME - 10 7 | TIME - 05 8 | 9 | 10 | TIME - 00 11 | 12 | 13 | 14 | 15 | :-) :-) :-) :-) :-) 16 | -------------------------------------------------------------------------------- /test/data/timebox-conf-sober.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | 3 | 4 | TIME - 25 5 | TIME - 20 6 | TIME - 15 7 | 8 | TIME - 10 9 | TIME - 05 10 | 11 | 12 | TIME - 00 13 | 14 | 15 | 16 | 17 | EOT 18 | msg: EOT 19 | -------------------------------------------------------------------------------- /test/data/timebox-duration-default.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | 3 | 4 | TIME - 25 5 | TIME - 20 6 | TIME - 15 7 | 8 | TIME - 10 9 | TIME - 05 10 | 11 | 12 | TIME - 00 13 | 14 | 15 | 16 | 17 | :-) :-) :-) :-) :-) 18 | -------------------------------------------------------------------------------- /test/data/timebox-conf-qtpi.out: -------------------------------------------------------------------------------- 1 | TIME - 30 2 | 3 | 4 | TIME - 25 5 | TIME - 20 6 | TIME - 15 7 | 8 | TIME - 10 9 | TIME - 05 10 | 11 | 12 | TIME - 00 13 | 14 | 15 | 16 | 17 | :-* :-* :-* :-* :-* 18 | msg: :-* :-* :-* :-* :-* 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | install: 6 | - if [ -f /etc/debian_version ]; then sudo make install_kcov; fi 7 | - if [ -f /etc/debian_version ]; then sudo make install_shells; fi 8 | 9 | script: 10 | - if [ -f /etc/debian_version ]; then make test_all; fi 11 | - if [ -f /etc/debian_version ]; then make coveralls; fi 12 | - if uname | grep Darwin; then make test_unix; fi 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Install test tools. 2 | install_kcov: 3 | apt-get update 4 | apt-get install g++ pkg-config libcurl4-gnutls-dev libelf-dev libdw-dev zlib1g-dev cmake 5 | git clone https://github.com/SimonKagstrom/kcov.git 6 | mkdir kcov/build 7 | cd kcov/build && cmake .. && make && make install 8 | 9 | install_shells: 10 | apt-get update 11 | apt-get install ksh zsh posh yash 12 | 13 | 14 | # Run tests. 15 | test: .FORCE 16 | sh test/test 17 | 18 | test_all: test test_unix test_posix 19 | 20 | test_unix: 21 | bash test/test 22 | ksh test/test 23 | zsh test/test 24 | 25 | test_posix: 26 | dash test/test 27 | posh test/test 28 | yash test/test 29 | 30 | 31 | # Measure code coverage. 32 | coverage: .FORCE 33 | rm -rf coverage 34 | kcov --exclude-path=test/test coverage test/test 35 | 36 | coveralls: 37 | rm -rf coverage 38 | kcov --coveralls-id=$$TRAVIS_JOB_ID --exclude-path=test/test coverage test/test 39 | 40 | 41 | # Cleanup 42 | clean: 43 | rm -rf coverage 44 | 45 | 46 | # Meta 47 | .FORCE: 48 | 49 | 50 | # vim: noet 51 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2013-2020 Susam Pal 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /test/setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Test Setup. 4 | 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2013-2016 Susam Pal 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining 10 | # a copy of this software and associated documentation files (the 11 | # "Software"), to deal in the Software without restriction, including 12 | # without limitation the rights to use, copy, modify, merge, publish, 13 | # distribute, sublicense, and/or sell copies of the Software, and to 14 | # permit persons to whom the Software is furnished to do so, subject to 15 | # the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be 18 | # included in all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | import=true . ./timebox 29 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.5.0 (2020-12-15) 5 | ------------------ 6 | 7 | ### Added 8 | 9 | - Add optional comment argument to record a comment for the time box in 10 | the log file. 11 | 12 | 13 | 0.4.0 (2019-08-25) 14 | ------------------ 15 | 16 | ### Fixed 17 | 18 | - Use `osascript` and not `xmessage` to display messages on macOS even 19 | if `xmessage` is installed. 20 | 21 | 22 | 0.3.0 (2017-03-02) 23 | ------------------ 24 | 25 | ### Added 26 | 27 | - Display time remaining in the title bar of Windows Command Prompt. 28 | - Tweak behaviour with configuration file at ~/.timebox.conf. 29 | - Configuration option 'quiet': Do not beep in the middle of time box. 30 | - Configuration option 'sober': Show "EOT" instead of smileys at the end. 31 | 32 | 33 | 0.2.0 (2016-06-05) 34 | ------------------ 35 | 36 | ### Added 37 | 38 | - Timebox script for Linux and OS/X. 39 | - Beep twice when 5 minutes are left. 40 | 41 | 42 | ### Changed 43 | 44 | - Minimum possible duration of a time box is 1 minute. 45 | - Maximum possible duration of a time box is 999999999 minutes. 46 | - Use integer duration as is; don't round it down to a multiple of 5. 47 | 48 | 49 | 0.1.0 (2013-07-14) 50 | ------------------ 51 | 52 | ### Added 53 | 54 | - Timebox script for Windows. 55 | - Minimum possible duration of a time box is 5 minutes. 56 | - Maximum possible duration of a time box is 90 minutes. 57 | - Duration is rounded down to a multiple of 5 minutes. 58 | - Beep twice at the beginning of a time box. 59 | - Beep once when 15 minutes are left. 60 | - Beep four times at the end of a time box. 61 | - Display a dialog box with smileys at the end of a time box. 62 | -------------------------------------------------------------------------------- /test/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Test Runner. 4 | 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2013-2016 Susam Pal 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining 10 | # a copy of this software and associated documentation files (the 11 | # "Software"), to deal in the Software without restriction, including 12 | # without limitation the rights to use, copy, modify, merge, publish, 13 | # distribute, sublicense, and/or sell copies of the Software, and to 14 | # permit persons to whom the Software is furnished to do so, subject to 15 | # the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be 18 | # included in all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | 29 | # Temporary working directory that may be used by any tests. 30 | TWORK=test/work 31 | 32 | # Temporary file containing the list of all discovered tests. 33 | TLIST=test/list 34 | 35 | # Location of test data files. 36 | TDATA=test/data 37 | 38 | # Start afresh with a clean test list. 39 | rm -f "$TLIST" "$TWORK" 40 | trap "rm -rf \"$TWORK\" \"$TLIST\"" EXIT 41 | 42 | # Discover test scripts. 43 | for test_file in test/test_* 44 | do 45 | # Load the test script. 46 | . "$test_file" 47 | 48 | # Add test functions in the test script to test list. 49 | grep -E "^test_[[:alnum:]]*()" "$test_file" | while read -r test_func 50 | do 51 | test_file=${test_file#test/test_} 52 | test_func=${test_func#test_} 53 | test_func=${test_func%"()"} 54 | printf "%s:%s\n" "$test_file" "$test_func" >> "$TLIST" 55 | done 56 | done 57 | 58 | # It is an error if two test functions have the same name. 59 | duplicate=$(cut -d : -f 2 "$TLIST" | sort | uniq -d | head -n 1) 60 | if [ -n "$duplicate" ] 61 | then 62 | echo Error: Duplicate test function names! 63 | grep ":$duplicate$" "$TLIST" 64 | return 1 65 | fi 66 | 67 | 68 | # Number of tests that passed. 69 | pass=0 70 | 71 | # Number of tests that failed. 72 | fail=0 73 | 74 | [ -f "test/setup" ] && . test/setup 75 | 76 | # Execute each test. 77 | while read -r test_spec 78 | do 79 | test_file="${test_spec%:*}" 80 | test_func="${test_spec#*:}" 81 | printf "%s: %s: " "${test_file}" "${test_func}" 82 | mkdir -p "$TWORK" 83 | if ("test_$test_func") 84 | then 85 | pass=$(( $pass + 1 )) 86 | printf "pass\n" 87 | else 88 | fail=$(( $fail + 1 )) 89 | printf "FAIL\n" 90 | fi 91 | rm -r "$TWORK" 92 | done < "$TLIST" 93 | rm "$TLIST" 94 | 95 | echo PASS: $pass 96 | [ $fail -gt 0 ] && echo FAIL: $fail && exit 1 || exit 0 97 | -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | Timebox Developer Notes 2 | ======================= 3 | 4 | Tests 5 | ----- 6 | 7 | All discussion about tests in this section assume that the tests are 8 | being run on a Debian or Debian based system. On other systems, the 9 | commands in Makefile may need to be modified appropriately. The Linux/OS 10 | X *timebox* script, however, can run on any POSIX conformant Unix or 11 | Linux system with any POSIX conformant shell without any changes to the 12 | script. 13 | 14 | The Windows timebox.cmd script is not discussed in this document at all. 15 | There are no tests for it. 16 | 17 | 18 | ### Setup Test Environment ### 19 | 20 | Change the current directory to the top level directory of this project 21 | and enter the following command as root to install kcov. It is used to 22 | measure code coverage of the tests. 23 | 24 | make install_kcov 25 | 26 | Enter the following command to install the shells required for testing 27 | cross-shell compatibility. 28 | 29 | make install_shells 30 | 31 | 32 | ### Run Tests ### 33 | 34 | Change current directory to the top level directory of this project and 35 | enter the following command to run a quick test with sh. 36 | 37 | make test 38 | 39 | Now enter the following command to run a complete test with sh, bash, 40 | ksh, dash and yash. 41 | 42 | make alltest 43 | 44 | Finally enter the following command to measure code coverage. 45 | 46 | make coverage 47 | 48 | Open coverage/index.html with a web browser to see the code coverage 49 | results. 50 | 51 | 52 | ### About the Tests ### 53 | 54 | All tests for this project are present in the *test* directory relative 55 | to the top level directory of this project. 56 | 57 | This project does not use an external testing tool or library to run the 58 | tests. This project comes with its own very basic and minimal test 59 | runner which can be found at *test/test*. It is a shell script that may 60 | be run with any POSIX conformant shell. 61 | 62 | For example, to run the tests with bash, enter the following command. 63 | 64 | bash test/test 65 | 66 | As another example, to run the tests with ksh, enter the following 67 | command. 68 | 69 | ksh test/test 70 | 71 | See *Makefile* for more details on how the tests are run. 72 | 73 | The test runner script at *test/test* follows very simple rules to 74 | discover and run tests. 75 | 76 | 1. First it looks for a file *test/setup* and executes it in the same 77 | shell that invoked the test runner *test/test*. Any test 78 | initialization code should go here. 79 | 2. Then it looks for files that match the wildcard pattern `test/test_*` 80 | relative to the current directory. These are considered to be test 81 | scripts. Therefore, the test runner must be run from the top level 82 | directory of the project. 83 | 2. Each test script is executed in the same shell that invoked the 84 | test runner *test/test*. Thus, all functions defined in each test 85 | script become available in the shell that runs the test runner. 86 | 3. Each test script is searched for functions with alphanumeric names 87 | that begin with `test_`. These are considered to be test functions. 88 | 4. After all test functions have been discovered, each test function is 89 | executed. 90 | 5. If a test function returns with an exit status of 0, the test is 91 | considered to have passed, otherwise it is considered to 92 | have failed. 93 | 6. Before executing each test function, an empty directory is created 94 | at "$TWORK". The TWORK environment variable is set to test/work. 95 | 7. After executing each test function, the "$TWORK" directory is 96 | removed automatically. Therefore, any temporary working files 97 | required during testing may be written to "$TWORK" by the test 98 | functions. 99 | 100 | It is an error if two test functions have the same name, even if the two 101 | functions are in different test scripts. Each test function defined 102 | within the *test* directory must have a distinct name. 103 | 104 | If all tests pass, the test runner prints the number of tests that 105 | passed and exits with an exit status of 0. If one or more tests fail, 106 | then the test runner prints the number of tests that passed followed by 107 | the number of tests that failed and exits with an exit status of 1. 108 | 109 | See the test runner *test/test* for more details. 110 | 111 | 112 | Release 113 | ------- 114 | 115 | The following tasks need to be performed for every release of a new 116 | version. These tasks should be performed with the project's top-level 117 | directory as the current directory. 118 | 119 | - Update copyright notice in LICENSE.md. 120 | - Update copyright notice in timebox.cmd. 121 | - Update copyright notice in timebox. 122 | - Update copyright notice in tests. 123 | - Update `COPYRIGHT` in timebox.cmd. 124 | - Update `COPYRIGHT` in timebox. 125 | - Update `VERSION` in timebox.cmd. 126 | - Update `VERSION` in timebox. 127 | - Update version in download URLs in README.md at two places. 128 | - Update CHANGES.md. 129 | - Run tests. 130 | 131 | make test 132 | make test_all 133 | make coverage 134 | firefox coverage/index.html & 135 | 136 | - Confirm that code coverage looks good. 137 | - Commit changes. 138 | 139 | git status 140 | git diff 141 | git add -p 142 | git commit 143 | 144 | - Tag the release. 145 | 146 | git tag -a -m "Timebox " 147 | git push 148 | git push --tags 149 | 150 | - Upload timebox.cmd and timebox to GitHub release page. 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Timebox 2 | ======= 3 | 4 | Timebox is a timer script that may be used to practice timeboxing. 5 | 6 | [![Download][SHIELD_WIN]][DOWNLOAD_WIN] 7 | [![Download][SHIELD_UNX]][DOWNLOAD_UNX] 8 | [![Build Status][BUILD_IMG]][BUILD_URL] 9 | [![Coverage Status][COVERAGE_IMG]][COVERAGE_URL] 10 | 11 | The Windows script has been tested on Windows 7 only. However, it should 12 | work fine on other recent versions of Windows too. 13 | 14 | The Unix script has been tested with [bash][], [ksh][] and [zsh][] on 15 | Debian and macOS as well as with [dash][], [posh][] and [yash][] on 16 | Debian. It should work fine on any Linux distribution as well as any 17 | POSIX compliant system with a POSIX compliant shell. 18 | 19 | [SHIELD_WIN]: https://img.shields.io/badge/download-timebox%20for%20Windows-brightgreen.svg 20 | [SHIELD_UNX]: https://img.shields.io/badge/download-timebox%20for%20Unix-brightgreen.svg 21 | [DOWNLOAD_WIN]: https://github.com/susam/timebox/releases/download/0.5.0/timebox.cmd 22 | [DOWNLOAD_UNX]: https://github.com/susam/timebox/releases/download/0.5.0/timebox 23 | 24 | [BUILD_IMG]: https://travis-ci.org/susam/timebox.svg?branch=master 25 | [BUILD_URL]: https://travis-ci.org/susam/timebox 26 | [COVERAGE_IMG]: https://coveralls.io/repos/github/susam/timebox/badge.svg?branch=master 27 | [COVERAGE_URL]: https://coveralls.io/github/susam/timebox?branch=master 28 | 29 | [bash]: https://packages.debian.org/stable/bash 30 | [ksh]: https://packages.debian.org/stable/ksh 31 | [zsh]: https://packages.debian.org/stable/zsh 32 | [dash]: https://packages.debian.org/stable/dash 33 | [posh]: https://packages.debian.org/stable/posh 34 | [yash]: https://packages.debian.org/stable/yash 35 | 36 | 37 | Contents 38 | -------- 39 | 40 | * [Introduction](#introduction) 41 | * [Features](#features) 42 | * [Get Started](#get-started) 43 | * [Beeps](#beeps) 44 | * [Logs](#logs) 45 | * [Configuration](#configuration) 46 | * [License](#license) 47 | * [Support](#support) 48 | 49 | 50 | Introduction 51 | ------------ 52 | 53 | Timeboxing is a time management technique that is believed to boost 54 | productivity by limiting the time during which a task is supposed to be 55 | completed. A time box is a fixed period of time alloted for a task or 56 | activity. The period of time to spend on the task is decided first. One 57 | time box may last anywhere between 15 minutes to 45 minutes. The 58 | duration of a time box may depend on the task or activity. 59 | Alternatively, the task may be scoped in a manner that it can be 60 | completed in a fixed size time box. Then a timer is started with the 61 | decided time interval. Once the timer notifies that the time interval 62 | has expired, any activity or work on the task is stopped, and a short 63 | break is taken before beginning another time box. 64 | 65 | This project offers scripts for Windows as well as Unix that may be used 66 | to run a time box for a specified duration. 67 | 68 | 69 | Features 70 | -------- 71 | 72 | - Runs in Windows Command Prompt or Linux/Unix/macOS shell. 73 | - Boring user experience. 74 | - No frills. 75 | - No ~~bells and~~ whistles. Actually, there are bells (`printf "\a"`). 76 | See [Beeps](#beeps) for details. 77 | - Logs completed time boxes to a file named `timebox.log` in the user's 78 | home directory. 79 | 80 | 81 | Get Started 82 | ----------- 83 | 84 | Timebox is a single-file executable script. 85 | 86 | Download [`timebox.cmd`][DOWNLOAD_WIN] for Windows, 87 | or [`timebox`][DOWNLOAD_UNX] for Linux, Unix, or macOS. 88 | 89 | Copy it to a directory specified in the `PATH` environment variable. On 90 | Linux/Unix/macOS, make the script executable: `chmod u+x timebox`. 91 | 92 | To run a 30 minute time box, run the script without any arguments: 93 | 94 | timebox 95 | 96 | The script accepts an integer argument that specifies the duration of 97 | the time box in minutes. For example, the following command also runs a 98 | 30 minute time box: 99 | 100 | timebox 30 101 | 102 | The following command runs a 15 minute time box: 103 | 104 | timebox 15 105 | 106 | The time at which a time box ends and its duration is written to 107 | `%userprofile%\timebox.log` on Windows and `~/timebox.log` on 108 | Linux/Unix/macOS at the end of a time box. 109 | 110 | The following command runs a 30 minute box with a text comment that is 111 | recorded in the log file when the time box ends. 112 | 113 | timebox 30 write essay 114 | 115 | To learn more about the usage of the script, run the following command. 116 | 117 | timebox --help 118 | 119 | After the script starts, it displays a countdown starting with the 120 | specified duration to 0. The countdown is displayed at the beginning of 121 | a time box and whenever the number of minutes remaining in the time box 122 | is a multiple of 5. On Windows, the time remaining is also displayed in 123 | the title bar of the Command Prompt window; the title bar is updated 124 | every minute. 125 | 126 | Here is how a typical session looks on the terminal: 127 | 128 | $ timebox 129 | 15:38:24 - 30 130 | 15:43:24 - 25 131 | 15:48:24 - 20 132 | 15:53:24 - 15 133 | 15:58:24 - 10 134 | 16:03:24 - 05 135 | 16:08:24 - 00 136 | :-) :-) :-) :-) :-) 137 | $ 138 | 139 | Apart from writing to standard output as shown above, the script also 140 | beeps a few times. The beeping behaviour is explained in the next 141 | section. 142 | 143 | 144 | Beeps 145 | ----- 146 | 147 | Two beeps are played at the beginning of a time box. If the duration of 148 | the time box is longer then 15 minutes, one beep is played when 15 149 | minutes are remaining in the time box. If the duration of the time box 150 | is longer than 5 minutes, two beeps are played when 5 minutes are 151 | remaining in the time box. Four beeps are played at the end of a time 152 | box. A dialog box with smileys is displayed for ten seconds at the end 153 | of a time box. 154 | 155 | 156 | Configuration 157 | ------------- 158 | 159 | The behaviour of the script can be tweaked a little bit with a 160 | configuration file at `~/.timeboxrc`, i.e., `%userprofile%\.timeboxrc` 161 | on Windows and `$HOME/.timeboxrc` on Linux/Unix/macOS. 162 | 163 | The script recognizes the following keywords (configuration options) in 164 | the configuration file. 165 | 166 | - `quiet` - Do not beep in the middle of a time box. Without this 167 | option, the script beeps once when 15 minutes are left and twice 168 | more when 5 minutes are left. This can be distracting on macOS where 169 | the beeps cause the icon for the terminal running the script to 170 | bounce in the Dock. With this configuration option, the beeps occur 171 | only at the start and the end of a time box. 172 | 173 | - `sober` - Display the message "EOT" instead of smileys when a time 174 | box ends. 175 | 176 | A configuration keyword may occur anywhere in the configuration file. 177 | The only requirement is that the keyword must appear as a word, i.e., it 178 | must either occur at the beginning of a line or follow a whitespace 179 | character and it must also either occur at the end of a line or followed 180 | by a whitespace character. Therefore, multiple configuration keywords 181 | may occur in the same line or on different lines. Any text in the 182 | configuration file that is not a configuration keyword is ignored. 183 | 184 | 185 | License 186 | ------- 187 | 188 | This is free and open source software. You can use, copy, modify, 189 | merge, publish, distribute, sublicense, and/or sell copies of it, 190 | under the terms of the MIT License. See [LICENSE.md][L] for details. 191 | 192 | This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, 193 | express or implied. See [LICENSE.md][L] for details. 194 | 195 | [L]: LICENSE.md 196 | 197 | 198 | Support 199 | ------- 200 | 201 | To report bugs, suggest improvements, or ask questions, please create a 202 | new issue at . 203 | -------------------------------------------------------------------------------- /test/test_timebox: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Timebox Tests. 4 | 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2013-2020 Susam Pal 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining 10 | # a copy of this software and associated documentation files (the 11 | # "Software"), to deal in the Software without restriction, including 12 | # without limitation the rights to use, copy, modify, merge, publish, 13 | # distribute, sublicense, and/or sell copies of the Software, and to 14 | # permit persons to whom the Software is furnished to do so, subject to 15 | # the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be 18 | # included in all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | 29 | # Test home directory where configuration file is read from and log file 30 | # is written to. 31 | HOME=$TWORK 32 | 33 | 34 | # Newline character. 35 | NL=" 36 | " 37 | 38 | # Bell character. 39 | BELL=$(printf "\a") 40 | 41 | 42 | # Test helper function to display bell characters as "" 43 | show_bell() 44 | { 45 | sed "s/$BELL/\\$NL/g" 46 | } 47 | 48 | 49 | # Test helper function that removes bell characters. 50 | hide_bell() 51 | { 52 | sed "s/$BELL//g" 53 | } 54 | 55 | 56 | # Test helper function that replaces time field with "TIME". 57 | mask_time() 58 | { 59 | sed 's/[0-2][0-9]:[0-5][0-9]:[0-5][0-9]/TIME/' 60 | } 61 | 62 | 63 | # Help output contains usage line. 64 | test_help() 65 | { 66 | for opt in -h --help 67 | do 68 | main $opt | head -n 1 | grep -q "^Usage:" || return 1 69 | done 70 | } 71 | 72 | 73 | # Version output contains version. 74 | test_version() 75 | { 76 | for opt in -v --version 77 | do 78 | main $opt | head -n 1 | grep -q "Timebox $VERSION" || return 1 79 | done 80 | } 81 | 82 | 83 | # Unknown option leads to error. 84 | test_unknown_option() 85 | { 86 | main -x 2>&1 | grep -q '^.*: Unknown option "-x"\.$' || return 1 87 | main --xyz 2>&1 | grep -q '^.*: Unknown option "--xyz"\.$' || return 1 88 | } 89 | 90 | 91 | 92 | # Surplus argument leads to error. 93 | test_comment_argument() 94 | { 95 | unset MSG_COMMANDS 96 | main -m 0 10 "foo bar" "baz qux" > /dev/null 97 | grep -q "^....-..-.. ..:..:.. - 10 - foo bar baz qux$" "$LOG_FILE" 98 | } 99 | 100 | 101 | # Option -m must be followed by an integer. 102 | test_m_option_missing_argument() 103 | { 104 | main -m 2>&1 | grep -q '^.*: "-m" must be followed by an integer\.$' 105 | } 106 | 107 | 108 | # Test dedication. 109 | test_qtpi() 110 | { 111 | main --qtpi | grep -q 'For Sunaina, for all time' 112 | } 113 | 114 | 115 | # When no arguments are specified, timebox defaults to 30 minutes. 116 | test_default_duration() 117 | { 118 | unset MSG_COMMANDS 119 | 120 | main -m 0 | mask_time | hide_bell > "$TWORK/timebox.out" 121 | diff -u "$TWORK/timebox.out" "$TDATA/timebox30.out" 122 | } 123 | 124 | 125 | # Test duration argument. 126 | test_good_duration() 127 | { 128 | unset MSG_COMMANDS 129 | main -m 0 15 | mask_time | hide_bell > "$TWORK/timebox.out" 130 | diff -u "$TWORK/timebox.out" "$TDATA/timebox15.out" 131 | } 132 | 133 | 134 | # Duration argument must be made entirely of digits. 135 | test_bad_duration() 136 | { 137 | main 10foo 2>&1 | grep -q '^.*: Bad duration: "10foo"\.$' || return 1 138 | main 0 2>&1 | grep -q '^.*: Bad duration: "0"\.$' || return 1 139 | } 140 | 141 | 142 | # Duration that exceeds 9 digits is an error. 143 | test_large_duration() 144 | { 145 | main 1000000000 2>&1 | grep -q '^.*: Bad duration: "1000000000"\.$' 146 | } 147 | 148 | 149 | # Duration cannot be 0. 150 | test_zero_duration() 151 | { 152 | main 0 2>&1 | grep -q '^.*: Bad duration: "0"\.$' 153 | } 154 | 155 | 156 | # When timebox ends, a command to display dialog box on desktop is 157 | # invoked. 158 | test_desktop_msg() 159 | { 160 | MSG_COMMANDS='echo msg: "$msg"' 161 | main -m 0 | hide_bell | 162 | grep -q "^msg: :-) :-) :-) :-) :-)$" 163 | } 164 | 165 | 166 | # When timebox ends, the commands specified in MSG_COMMANDS must be 167 | # tried one by one until one succeeds in order to display a message on 168 | # the desktop. 169 | test_desktop_msg_loop() 170 | { 171 | MSG_COMMANDS="$(cat < "$TWORK/timebox.out" 180 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-msg.out" 181 | } 182 | 183 | 184 | # Test that a log is written to log file at the end of a timebox. 185 | test_end_log() 186 | { 187 | unset MSG_COMMANDS 188 | main -m 0 > /dev/null 189 | grep -q "^....-..-.. ..:..:.. - 30$" "$LOG_FILE" 190 | } 191 | 192 | 193 | # Test output and beeps for default duration. 194 | test_duration_default() 195 | { 196 | unset MSG_COMMANDS 197 | main -m 0 | mask_time | show_bell > "$TWORK/timebox.out" 198 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-duration-default.out" 199 | } 200 | 201 | 202 | # Test output and beeps for default duration. 203 | test_duration_16() 204 | { 205 | unset MSG_COMMANDS 206 | main -m 0 16 | mask_time | show_bell > "$TWORK/timebox.out" 207 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-duration-16.out" 208 | } 209 | 210 | 211 | # Test output and beeps for default duration. 212 | test_duration_15() 213 | { 214 | unset MSG_COMMANDS 215 | main -m 0 15 | mask_time | show_bell > "$TWORK/timebox.out" 216 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-duration-15.out" 217 | } 218 | 219 | 220 | # Test output and beeps for default duration. 221 | test_duration_6() 222 | { 223 | unset MSG_COMMANDS 224 | main -m 0 6 | mask_time | show_bell > "$TWORK/timebox.out" 225 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-duration-6.out" 226 | } 227 | 228 | 229 | # Test output and beeps for default duration. 230 | test_duration_5() 231 | { 232 | unset MSG_COMMANDS 233 | main -m 0 5 | mask_time | show_bell > "$TWORK/timebox.out" 234 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-duration-5.out" 235 | } 236 | 237 | 238 | # Test output and beeps for default duration. 239 | test_duration_1() 240 | { 241 | unset MSG_COMMANDS 242 | main -m 0 1 | mask_time | show_bell > "$TWORK/timebox.out" 243 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-duration-1.out" 244 | } 245 | 246 | # Test configuration option: quiet. 247 | test_conf_quiet() 248 | { 249 | unset MSG_COMMANDS 250 | echo quiet > "$TWORK/.timeboxrc" 251 | main -m 0 | mask_time | show_bell > "$TWORK/timebox.out" 252 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-conf-quiet.out" 253 | } 254 | 255 | # Test configuration option: quiet. 256 | test_conf_sober() 257 | { 258 | MSG_COMMANDS='echo msg: "$msg"' 259 | echo sober > $TWORK/.timeboxrc 260 | main -m 0 | mask_time | show_bell > "$TWORK/timebox.out" 261 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-conf-sober.out" 262 | } 263 | 264 | # Test configuration: qtpi. 265 | test_conf_qtpi() 266 | { 267 | MSG_COMMANDS='echo msg: "$msg"' 268 | echo qtpi > $TWORK/.timeboxrc 269 | main -m 0 | mask_time | show_bell > "$TWORK/timebox.out" 270 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-conf-qtpi.out" 271 | } 272 | 273 | # Test multiple configurations. 274 | test_conf_multi() 275 | { 276 | MSG_COMMANDS='echo msg: "$msg"' 277 | 278 | echo quiet sober > $TWORK/.timeboxrc 279 | main -m 0 | mask_time | show_bell > "$TWORK/timebox.out" 280 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-conf-multi.out" || return 1 281 | 282 | echo quiet > $TWORK/.timeboxrc 283 | echo sober >> $TWORK/.timeboxrc 284 | main -m 0 | mask_time | show_bell > "$TWORK/timebox.out" 285 | diff -u "$TWORK/timebox.out" "$TDATA/timebox-conf-multi.out" || return 1 286 | } 287 | -------------------------------------------------------------------------------- /timebox: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Timebox 4 | 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2013-2020 Susam Pal 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining 10 | # a copy of this software and associated documentation files (the 11 | # "Software"), to deal in the Software without restriction, including 12 | # without limitation the rights to use, copy, modify, merge, publish, 13 | # distribute, sublicense, and/or sell copies of the Software, and to 14 | # permit persons to whom the Software is furnished to do so, subject to 15 | # the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be 18 | # included in all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | 29 | VERSION=0.5.0 30 | AUTHOR="Susam Pal" 31 | COPYRIGHT="Copyright (c) 2013-2020 $AUTHOR" 32 | LICENSE_URL="https://susam.github.io/licenses/mit.html" 33 | SUPPORT_URL="https://github.com/susam/timebox/issues" 34 | NAME=${0##*/} 35 | 36 | DEFAULT_DURATION=30 37 | LOG_FILE=~/timebox.log 38 | CONF_FILE=~/.timeboxrc 39 | 40 | 41 | # Starting point of this script. 42 | # 43 | # Arguments: 44 | # arg...: All arguments this script was invoked with. 45 | main() 46 | { 47 | parse_arguments "$@" 48 | parse_configuration 49 | timer "$duration_arg" "$comment_arg" 50 | } 51 | 52 | 53 | # Parse command line arguments passed to this script. 54 | # 55 | # Arguments: 56 | # arg...: All arguments this script was invoked with. 57 | # 58 | # Errors: 59 | # Exit with an error message if invalid arguments are specified. 60 | parse_arguments() 61 | { 62 | minute=60 63 | while [ $# -gt 0 ] 64 | do 65 | case $1 in 66 | -m) 67 | [ $# -lt 2 ] && 68 | quit \""$1"\" must be followed by an integer. 69 | minute=$2 70 | shift 2 71 | ;; 72 | --qtpi) 73 | qtpi 74 | exit 75 | ;; 76 | -h | --help) 77 | show_help 78 | exit 79 | ;; 80 | -v | --version) 81 | show_version 82 | exit 83 | ;; 84 | -?*) 85 | quit Unknown option \""$1"\". 86 | ;; 87 | *) 88 | break 89 | ;; 90 | esac 91 | done 92 | 93 | while [ $# -gt 0 ] 94 | do 95 | if [ -z "$duration_arg" ] 96 | then 97 | duration_arg="$1" 98 | shift 99 | elif [ -z "$comment_arg" ] 100 | then 101 | comment_arg="$*" 102 | break 103 | fi 104 | done 105 | 106 | [ -z "$duration_arg" ] && duration_arg=$DEFAULT_DURATION 107 | } 108 | 109 | 110 | # Parse configuration file. 111 | parse_configuration() 112 | { 113 | # If configuration file does not exist, there is nothing to parse. 114 | [ -e "$CONF_FILE" ] || return 115 | 116 | # Regular expressions to match start of word. 117 | sow="(^|[[:space:]])" 118 | 119 | # Regular expressions to match end of word. 120 | eow="($|[[:space:]])" 121 | 122 | # quiet: Do not beep in the middle of time box. 123 | grep -q -E "${sow}quiet${eow}" "$CONF_FILE" && quiet=yes 124 | 125 | # qtpi: Special message. 126 | grep -q -E "${sow}qtpi${eow}" "$CONF_FILE" && qtpi=yes 127 | 128 | # sober: EOT message. 129 | grep -q -E "${sow}sober${eow}" "$CONF_FILE" && sober=yes 130 | } 131 | 132 | 133 | # Run a time box timer for the specified number of minutes. 134 | # 135 | # Arguments: 136 | # duration: Number of minutes. 137 | timer() 138 | { 139 | duration=$1 140 | comment=$2 141 | 142 | if ! printf "%s" "$duration" | grep -q "^[1-9][0-9]*$" || 143 | [ $duration -ge 1000000000 ] 144 | then 145 | quit Bad duration: \""$duration"\". 146 | fi 147 | 148 | i=$duration 149 | while [ $i -ge 0 ] 150 | do 151 | minute $i 152 | i=$(( $i - 1 )) 153 | done 154 | 155 | unset duration 156 | unset comment 157 | unset i 158 | } 159 | 160 | 161 | # Generate output based on time remaining. 162 | # 163 | # If the time remaining in the time box is greater than 0, then 164 | # this function sleeps for one minute before returning. 165 | # 166 | # Arguments: 167 | # time_left: Time remaining in the time box. 168 | minute() 169 | { 170 | time_left=$1 171 | 172 | # Print current time and time remaining at the beginning of a 173 | # time box and when the time remaining is a multiple of 5 minutes. 174 | if [ $time_left = $duration ] || [ $(( $time_left % 5 )) = 0 ] 175 | then 176 | print_time $time_left 177 | fi 178 | 179 | # Beep at the beginning of the time box, 15 minutes from the end, 180 | # 5 minutes from the end and at the end of the time box. 181 | if [ $time_left = $duration ] 182 | then 183 | beep 2 184 | elif [ $time_left = 15 ] && [ $duration != 15 ] && [ "$quiet" != "yes" ] 185 | then 186 | beep 1 187 | elif [ $time_left = 5 ] && [ $duration != 5 ] && [ "$quiet" != "yes" ] 188 | then 189 | beep 2 190 | elif [ $time_left = 0 ] 191 | then 192 | beep 4 193 | fi 194 | 195 | # Determine smileys to use at the end of time box. 196 | if [ "$qtpi" = "yes" ] 197 | then 198 | console_msg=":-* :-* :-* :-* :-*" 199 | desktop_msg=":-* :-* :-* :-* :-*" 200 | elif [ "$sober" = "yes" ] 201 | then 202 | console_msg=EOT 203 | desktop_msg=EOT 204 | else 205 | console_msg=":-) :-) :-) :-) :-)" 206 | desktop_msg=":-) :-) :-) :-) :-)" 207 | fi 208 | 209 | if [ $time_left != 0 ] 210 | then 211 | # Sleep for a minute. 212 | sleep $minute 213 | else 214 | # Display smileys and write log at the end of the time box. 215 | echo "$console_msg" 216 | msg "$desktop_msg" 217 | log_msg="$(date "+%Y-%m-%d %H:%M:%S") - $duration" 218 | [ -n "$comment" ] && log_msg="$log_msg - $comment" 219 | echo "$log_msg" >> "$LOG_FILE" 220 | fi 221 | 222 | unset console_msg 223 | unset desktop_msg 224 | unset log_msg 225 | } 226 | 227 | 228 | # Print the current time and the time remaining in the time box. 229 | # 230 | # Arguments: 231 | # time_left: Time remaining in the time box. 232 | print_time() 233 | { 234 | # Pad the time remaining with zeroes on the left side, so that we 235 | # can select a substring of fixed size from the right side in 236 | # order to display the time left values as a fixed width column. 237 | # We do not allow a duration that exceeds nine digits. Therefore, 238 | # padding with eight zeroes is sufficient. 239 | time_left_padded=00000000$1 240 | 241 | # Determine the number of characters in the duration string. We 242 | # will use these many characters to print the time left in order 243 | # to display the time left values as a fixed width column. 244 | dur_len=$(printf "%s" $duration | wc -c) 245 | 246 | # Display time remaining in HH:MM:SS - N format where the string 247 | # length of N equals the string length of specified duration. 248 | echo $(date "+%H:%M:%S") - \ 249 | $(printf "%s" $time_left_padded | tail -c "$dur_len") 250 | 251 | unset time_left_padded 252 | unset dur_len 253 | } 254 | 255 | 256 | # Play one or more beeps. 257 | # 258 | # Arguments: 259 | # count (optional): Number of beeps to play, defaults to 1. 260 | beep() 261 | { 262 | [ $# = 0 ] && count=1 || count=$1 263 | 264 | j=0 265 | while [ $j -lt $count ] 266 | do 267 | printf "\a" 268 | j=$(( $j + 1 )) 269 | done 270 | 271 | unset count 272 | unset j 273 | } 274 | 275 | 276 | # Commands to try to display message in the desktop environment. 277 | # 278 | # Each command is specified in a separate line. Blank lines and lines 279 | # consisting only of whitespace are ignored. Each command is tried in 280 | # the order specified until a command exits with status code 0. 281 | MSG_COMMANDS="$(cat < /dev/null 2>&1 283 | zenity --timeout 10 --info --text "\$msg" || [ \$? -eq 5 ] 284 | xmessage -timeout 10 "\$msg" 285 | eof 286 | )" 287 | 288 | 289 | # Display message in the desktop environment. 290 | # 291 | # Arguments: 292 | # msg: Message to display. 293 | msg() 294 | { 295 | msg=$1 296 | printf "%s\n" "$MSG_COMMANDS" | grep "[[:graph:]]" | 297 | while read -r cmd 298 | do 299 | eval "$cmd" 2> /dev/null && break 300 | done 301 | } 302 | 303 | 304 | # Print dedication. 305 | qtpi() 306 | { 307 | dedication="For Sunaina, for all time" 308 | echo 309 | echo " $dedication" 310 | echo 311 | } 312 | 313 | 314 | # Terminate the script with an error message. 315 | # 316 | # Arguments: 317 | # string...: String to print to standard error stream. 318 | # 319 | # Errors: 320 | # Unconditionally cause the script to terminate with an error message 321 | # and exit code 1. 322 | quit() 323 | { 324 | printf "%s: %s\n" "$NAME" "$*" >&2 325 | exit 1 326 | } 327 | 328 | 329 | # Show help. 330 | show_help() 331 | { 332 | cat <. 349 | eof 350 | } 351 | 352 | 353 | # Show version and copyright. 354 | show_version() 355 | { 356 | cat <. 364 | 365 | This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, 366 | express or implied. See the MIT License for details. 367 | eof 368 | } 369 | 370 | 371 | # Start. 372 | [ -z "$import" ] && main "$@" 373 | -------------------------------------------------------------------------------- /timebox.cmd: -------------------------------------------------------------------------------- 1 | @echo off & goto :main 2 | 3 | rem Timebox 4 | 5 | rem The MIT License (MIT) 6 | rem 7 | rem Copyright (c) 2013-2020 Susam Pal 8 | rem 9 | rem Permission is hereby granted, free of charge, to any person obtaining 10 | rem a copy of this software and associated documentation files (the 11 | rem "Software"), to deal in the Software without restriction, including 12 | rem without limitation the rights to use, copy, modify, merge, publish, 13 | rem distribute, sublicense, and/or sell copies of the Software, and to 14 | rem permit persons to whom the Software is furnished to do so, subject to 15 | rem the following conditions: 16 | rem 17 | rem The above copyright notice and this permission notice shall be 18 | rem included in all copies or substantial portions of the Software. 19 | rem 20 | rem THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | rem EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | rem MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | rem IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | rem CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | rem TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | rem SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | 29 | rem Starting point of this script. 30 | :main 31 | setlocal 32 | 33 | rem Script data. 34 | set VERSION=0.5.0 35 | set AUTHOR=Susam Pal 36 | set COPYRIGHT=Copyright (c) 2013-2020 %AUTHOR% 37 | set LICENSE_URL=https://susam.github.io/licenses/mit.html 38 | set SUPPORT_URL=https://github.com/susam/timebox/issues 39 | set NAME=%~n0 40 | 41 | rem Timer data. 42 | set DEFAULT_DURATION=30 43 | set LOG_FILE=%userprofile%\timebox.log 44 | set CONF_FILE=%userprofile%\.timeboxrc 45 | 46 | rem Parse arguments and start timer. 47 | set minute=60 48 | set duration_arg= 49 | set comment_arg= 50 | call :parse_arguments %* 51 | call :parse_configuration 52 | call :timer %duration_arg% "%comment_arg%" 53 | 54 | endlocal 55 | goto :eof 56 | 57 | 58 | rem Parse command line arguments passed to this script. 59 | rem 60 | rem Arguments: 61 | rem arg...: All arguments this script was invoked with. 62 | :parse_arguments 63 | set arg=%~1 64 | rem Handle command line arguments 65 | if "%arg%" == "-w" ( 66 | call :where_am_i 67 | goto :eof 68 | ) else if "%arg%" == "--where" ( 69 | call :where_am_i 70 | goto :eof 71 | ) else if "%arg%" == "-m" ( 72 | if "%~2" == "" ( 73 | call :err "%arg%" must be followed by an integer. 74 | goto :eof 75 | ) 76 | set minute=%~2 77 | shift 78 | shift 79 | goto :parse_arguments 80 | ) else if "%arg%" == "--qtpi" ( 81 | call :qtpi 82 | goto :eof 83 | ) else if "%arg%" == "-h" ( 84 | call :show_help 85 | goto :eof 86 | ) else if "%arg%" == "--help" ( 87 | call :show_help 88 | goto :eof 89 | ) else if "%arg%" == "/?" ( 90 | call :show_help 91 | ) else if "%arg%" == "-v" ( 92 | call :show_version 93 | goto :eof 94 | ) else if "%arg%" == "--version" ( 95 | call :show_version 96 | goto :eof 97 | ) else if "%arg:~0,1%" == "-" ( 98 | call :err Unknown option "%arg%". 99 | goto :eof 100 | ) 101 | if not "%arg%" == "" ( 102 | if "%duration_arg%" == "" ( 103 | set duration_arg=%arg% 104 | shift 105 | goto :parse_arguments 106 | ) 107 | ) 108 | :consume_comment 109 | if not "%~1" == "" ( 110 | if "%comment_arg%" == "" ( 111 | set comment_arg=%~1 112 | ) else ( 113 | set comment_arg=%comment_arg% %~1 114 | ) 115 | shift 116 | goto :consume_comment 117 | ) 118 | if "%duration_arg%" == "" ( 119 | set duration_arg=%DEFAULT_DURATION% 120 | ) 121 | set arg= 122 | goto :eof 123 | 124 | 125 | rem Parse configuration file. 126 | :parse_configuration 127 | rem If configuration file does not exist, there is nothing to parse. 128 | if not exist %CONF_FILE% goto :eof 129 | 130 | rem quiet: Do not beep in the middle of timebox. 131 | findstr "\" %CONF_FILE% > nul 132 | if not errorlevel 1 set quiet=yes 133 | 134 | rem qtpi: Special message. 135 | findstr "\" %CONF_FILE% > nul 136 | if not errorlevel 1 set qtpi=yes 137 | 138 | rem sober: "EOT" instead of smileys at the end of timebox. 139 | findstr "\" %CONF_FILE% > nul 140 | if not errorlevel 1 set sober=yes 141 | 142 | goto :eof 143 | 144 | rem Run a time box timer for the specified number of minutes. 145 | rem 146 | rem Arguments: 147 | rem duration: Number of minutes. 148 | :timer 149 | setlocal 150 | set /a duration = %~1 151 | if errorlevel 1 ( 152 | call :err Bad duration: "%~1". 153 | goto :eof 154 | ) 155 | if %duration% equ 0 ( 156 | call :err Bad duration: "%~1". 157 | goto :eof 158 | ) else if %duration% geq 1000000000 ( 159 | call :err Bad duration: "%~1". 160 | goto :eof 161 | ) 162 | set comment=%~2 163 | for /l %%i in (%duration%, -1, 0) do call :minute %%i 164 | endlocal 165 | goto :eof 166 | 167 | 168 | rem If the time remaining in the time box is greater than zero, then 169 | rem this subroutine sleeps for one minute before returning. 170 | rem 171 | rem Arguments: 172 | rem time_left: Time remaining in the time box. 173 | :minute 174 | setlocal 175 | set time_left=%~1 176 | 177 | rem Display time remaining in the terminal window. 178 | title %time_left% 179 | 180 | rem Print current time and time remaining at the beginning of a 181 | rem time box and when the time remaining is a multiple of 5 minutes. 182 | set /a mod = %time_left% %% 5 183 | if %time_left% == %duration% ( 184 | call :print_time %time_left% 185 | ) else if %mod% == 0 ( 186 | call :print_time %time_left% 187 | ) 188 | 189 | rem Beep at the beginning of the time box, 15 minutes from the end, 190 | rem 5 minutes from the end and at the end of the time box. 191 | if %time_left% == %duration% ( 192 | call :beep 2 193 | ) else if %time_left% == 15 ( 194 | if not %duration% == 15 ( 195 | if not "%quiet%" == "yes" ( 196 | call :beep 1 197 | ) 198 | ) 199 | ) else if %time_left% == 5 ( 200 | if not %duration% == 5 ( 201 | if not "%quiet%" == "yes" ( 202 | call :beep 2 203 | ) 204 | ) 205 | ) else if %time_left% == 0 ( 206 | call :beep 4 207 | ) 208 | 209 | rem Determine smileys to use at the end of time box. 210 | if "%qtpi%" == "yes" ( 211 | set console_msg=:-* :-* :-* :-* :-* 212 | set desktop_msg=":-* :-* :-* :-* :-*" 213 | ) else if "%sober%" == "yes" ( 214 | set console_msg=EOT 215 | set desktop_msg=EOT 216 | ) else ( 217 | set console_msg=:-^^^) :-^^^) :-^^^) :-^^^) :-^^^) 218 | set desktop_msg=":-) :-) :-) :-) :-)" 219 | ) 220 | 221 | set log_msg=%date% %time:~0,-3% - %duration% 222 | if not "%comment%" == "" ( 223 | set log_msg=%log_msg% - %comment% 224 | ) 225 | 226 | if not %time_left% == 0 ( 227 | rem Sleep for a minute. 228 | call :sleep %minute% 229 | ) else ( 230 | rem Display smileys and write log at the end of the time box. 231 | echo %console_msg% 232 | msg %username% /w /time:10 %desktop_msg% 233 | >> "%LOG_FILE%" echo %log_msg% 234 | ) 235 | 236 | endlocal 237 | goto :eof 238 | 239 | 240 | rem Print the current time and the time remaining in the time box. 241 | rem 242 | rem Arguments: 243 | rem time_left: Time remaining in the time box. 244 | :print_time 245 | setlocal enabledelayedexpansion 246 | rem Pad the time remaining with zeroes on the left side, so that we 247 | rem can select a substring of fixed size from the right side in 248 | rem order to display the time left values as a fixed width column. 249 | rem We do not allow a duration that exceeds nine digits. Therefore, 250 | rem padding with eight zeroes is sufficient. 251 | set time_left_padded=00000000%~1 252 | 253 | rem Determine the number of characters in the duration string. We 254 | rem will use these many characters to print the time left in order 255 | rem to display the time left values as a fixed width column. 256 | call :strlen %duration% len 257 | 258 | rem Replace any spaces with zeros so that the current time values 259 | rem are displayed as a fixed width column. 260 | set time_curr=%time: =0% 261 | 262 | rem Display time remaining in HH:MM:SS - N format where the string 263 | rem length of N equals the string length of specified duration. 264 | rem Note: The time_curr variable contains time in HH:MM:NN.DD 265 | rem format. That is why we remove the last three characters from it. 266 | echo %time_curr:~0,-3% - !time_left_padded:~-%len%! 267 | endlocal 268 | goto :eof 269 | 270 | 271 | rem Play one or more beeps. 272 | rem 273 | rem Arguments: 274 | rem count (optional): Number of beeps to play, defaults to 1. 275 | :beep 276 | setlocal 277 | if "%~1" == "" ( 278 | set /a count = 1 279 | ) else ( 280 | set /a count = %~1 281 | ) 282 | for /l %%i in (1, 1, %count%) do ( 283 | set /p a="" < nul 284 | ) 285 | endlocal 286 | goto :eof 287 | 288 | 289 | rem Sleep for specified number of seconds. 290 | rem 291 | rem Arguments: 292 | rem seconds: Number of seconds to sleep. 293 | :sleep 294 | setlocal 295 | set /a count = %~1 + 1 296 | ping -n %count% 127.0.0.1 > nul 297 | endlocal 298 | goto :eof 299 | 300 | 301 | rem Get the length of a string. 302 | rem 303 | rem Arguments: 304 | rem str: String. 305 | rem len_var_name: Name of variable that should be set with the length. 306 | :strlen 307 | setlocal enabledelayedexpansion 308 | set str=%~1. 309 | set len_var_name=%~2 310 | set len=0 311 | 312 | if not "%str:~256,1%" == "" ( 313 | call :err String too large. 314 | goto :eof 315 | ) 316 | 317 | for %%i in (128 64 32 16 8 4 2 1) do ( 318 | if not "!str:~%%i,1!" == "" ( 319 | set /a len += %%i 320 | set str=!str:~%%i! 321 | ) 322 | ) 323 | endlocal & ( 324 | set %len_var_name%=%len% 325 | ) 326 | goto :eof 327 | 328 | 329 | rem Show the path of this script. 330 | :where_am_i 331 | echo %~f0 332 | call :pause 333 | goto :eof 334 | 335 | 336 | rem Print dedication. 337 | :qtpi 338 | setlocal enabledelayedexpansion 339 | 340 | set dedication=For Sunaina, for all time 341 | call :strlen "%dedication%" len 342 | set /a indentation = (80 - %len%) / 2 343 | 344 | echo %cmdcmdline% | findstr /i /c:"%~nx0" > nul && set mode=explorer 345 | if "%mode%" == "explorer" ( 346 | for /l %%i in (1, 1, 11) do echo. 347 | for /l %%i in (1, 1, %indentation%) do ( 348 | set dedication= !dedication! 349 | ) 350 | echo !dedication! 351 | for /l %%i in (1, 1, 12) do echo. 352 | call :pause 353 | ) else ( 354 | echo. 355 | echo %dedication% 356 | ) 357 | endlocal 358 | goto :eof 359 | 360 | 361 | rem Print error message. 362 | rem 363 | rem Arguments: 364 | rem string...: String to print to standard error stream. 365 | :err 366 | >&2 echo %NAME%: %* 367 | call :pause 368 | goto :eof 369 | 370 | 371 | rem Pause if this script was invoked from command prompt. 372 | :pause 373 | echo %cmdcmdline% | findstr /i /c:"%~nx0" > nul && pause > nul 374 | goto :eof 375 | 376 | 377 | rem Show help. 378 | :show_help 379 | setlocal 380 | echo Usage: %NAME% [-w] [-h] [-v] [DURATION [MESSAGE ...]] 381 | echo. 382 | echo The timeboxing script runs for the number of minutes specified 383 | echo as the duration argument. If no duration argument is specified, 384 | echo it runs for %DEFAULT_DURATION% minutes. 385 | echo. 386 | echo Options: 387 | echo -w, --where Display the path where this script is present. 388 | echo -h, --help, /? Display this help and exit. 389 | echo -v, --version Display version information and exit. 390 | echo. 391 | echo Examples: 392 | echo %NAME% Run a %DEFAULT_DURATION% minute time box. 393 | echo %NAME% 15 Run a 15 minute time box. 394 | echo %NAME% 30 write essay Specify a comment to be recorded in log. 395 | echo. 396 | echo Report bugs to ^<%SUPPORT_URL%^>. 397 | call :pause 398 | goto :eof 399 | 400 | 401 | rem Show version and copyright. 402 | :show_version 403 | echo Timebox %VERSION% 404 | echo %COPYRIGHT% 405 | echo. 406 | echo This is free and open source software. You can use, copy, modify, 407 | echo merge, publish, distribute, sublicense, and/or sell copies of it, 408 | echo under the terms of the MIT License. You can obtain a copy of the 409 | echo MIT License at <%LICENSE_URL%>. 410 | echo. 411 | echo This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, 412 | echo express or implied. See the MIT License for details. 413 | call :pause 414 | goto :eof 415 | --------------------------------------------------------------------------------