├── .editorconfig ├── .gitignore ├── .travis.yml ├── README.md ├── package.json └── src ├── ascii-art.txt ├── index.js └── stopwatch.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | 17 | # Indentation override 18 | #[lib/**.js] 19 | #[{package.json,.travis.yml}] 20 | #[**/**.js] 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stopwatch-cli 2 | A simple stopwatch app that runs on the shell. 3 | 4 | [![Build Status](https://travis-ci.org/codealchemist/stopwatch-cli.svg?branch=master)](https://travis-ci.org/codealchemist/stopwatch-cli) 5 | 6 | ![screenshot](https://cldup.com/Svbrr16C7v.gif) 7 | 8 | ## Install 9 | 10 | `npm install -g stopwatch-cli` 11 | 12 | ## Usage 13 | 14 | Just run `stopwatch-cli` on your shell to start the timer. 15 | 16 | Keyboard interface: 17 | 18 | - `ENTER`: Creates a lap. 19 | - `SPACE`: Pauses the timer. 20 | - `q` or `ctrl+c`: Quit. 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stopwatch-cli", 3 | "version": "1.0.0", 4 | "description": "A simple stopwatch app that runs on the shell.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "lint-focus": "standard-focus", 9 | "test": "standard" 10 | }, 11 | "bin": { 12 | "stopwatch-cli": "./src/index.js" 13 | }, 14 | "keywords": [ 15 | "timer" 16 | ], 17 | "author": "Alberto Miranda", 18 | "repository": "codealchemist/stopwatch-cli", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "standard-focus": "^1.1.2" 22 | }, 23 | "dependencies": { 24 | "clivas": "^0.2.0", 25 | "format-duration": "^1.0.0", 26 | "keypress": "^0.2.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ascii-art.txt: -------------------------------------------------------------------------------- 1 | _ _ _ _ _ 2 | ___| |_ ___ _ ____ ____ _| |_ ___| |__ ___| (_) 3 | / __| __/ _ \| '_ \ \ /\ / / _` | __/ __| '_ \ _____ / __| | | 4 | \__ \ || (_) | |_) \ V V / (_| | || (__| | | |_____| (__| | | 5 | |___/\__\___/| .__/ \_/\_/ \__,_|\__\___|_| |_| \___|_|_| 6 | |_| 7 | 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | const path = require('path') 5 | const fs = require('fs') 6 | const clivas = require('clivas') 7 | 8 | // Hide cursor. 9 | clivas.cursor(false) 10 | 11 | // Avoid enter key from generating new lines 12 | // and allows capturing key events. 13 | const readline = require('readline') 14 | readline.emitKeypressEvents(process.stdin) 15 | process.stdin.setRawMode(true) 16 | 17 | // print ascii art 18 | const artFile = path.join(__dirname, './ascii-art.txt') 19 | const art = fs.readFileSync(artFile, 'utf8') 20 | console.log(art) 21 | console.log('Press ENTER key to create LAPS, SPACE to PAUSE or "q" to quit.') 22 | console.log() 23 | 24 | const Stopwatch = require('./stopwatch') 25 | const stopwatch = new Stopwatch({render: true}) 26 | stopwatch.start() 27 | 28 | process.stdin.on('keypress', (str, key) => { 29 | // Lap. 30 | if (key && key.name === 'return') { 31 | stopwatch.lap() 32 | } 33 | 34 | // Pause. 35 | if (key.name === 'space') { 36 | clivas.line(`{yellow:-- Paused --}`) 37 | stopwatch.pause() 38 | } 39 | 40 | // Exit. 41 | if (key.name === 'q' || key.sequence === '\u0003') { 42 | clivas.line(` 43 | {yellow:Have a good time!} 44 | `) 45 | process.exit() 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /src/stopwatch.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const format = require('format-duration') 3 | const clivas = require('clivas') 4 | 5 | class Stopwatch { 6 | constructor ({title = ' ELAPSED TIME:', showIcon = true, render = false} = {}) { 7 | this.title = title 8 | this.showIcon = showIcon 9 | this.render = render 10 | this.timeStart 11 | this.timeEnd 12 | this.currentTime 13 | this.icons = ['⏳', '⌛'] 14 | this.currentIcon = -1 15 | this.totalIcons = this.icons.length 16 | this.laps = [] 17 | this.running = false 18 | this.callback 19 | } 20 | 21 | onTick (callback) { 22 | this.callback = callback 23 | return this 24 | } 25 | 26 | start () { 27 | this.running = true 28 | this.timeStart = new Date() 29 | this.loop() 30 | } 31 | 32 | loop () { 33 | this.currentTime = new Date() 34 | if (typeof this.callback === 'function') { 35 | this.callback({ 36 | timeStart: this.timeStart, 37 | currentTime: this.currentTime, 38 | elapsedTime: this.getElapsedTime(), 39 | icon: this.getIcon(), 40 | laps: this.laps, 41 | running: this.running 42 | }) 43 | } 44 | 45 | if (this.render) this.show() 46 | 47 | this.timeoutRef = setTimeout(() => { 48 | this.loop() 49 | }, 1000) 50 | } 51 | 52 | pause () { 53 | if (!this.running) { 54 | this.running = true 55 | this.timeStart = new Date(new Date() - this.getElapsedTime()) 56 | clivas.clear() 57 | this.loop() 58 | return 59 | } 60 | 61 | clearTimeout(this.timeoutRef) 62 | this.running = false 63 | } 64 | 65 | lap () { 66 | const elapsedTime = this.getElapsedTime() 67 | const time = format(elapsedTime) 68 | this.laps.push(time) 69 | } 70 | 71 | getElapsedTime () { 72 | return this.currentTime - this.timeStart 73 | } 74 | 75 | getIcon () { 76 | ++this.currentIcon 77 | if (this.currentIcon >= this.totalIcons) this.currentIcon = 0 78 | return this.icons[this.currentIcon] 79 | } 80 | 81 | show () { 82 | const elapsedTime = this.getElapsedTime() 83 | const time = format(elapsedTime) 84 | 85 | let icon = '' 86 | if (this.showIcon) icon = this.getIcon() 87 | clivas.clear() 88 | clivas.line(`{white:${icon} ${this.title} ${time}}`) 89 | this.showLaps() 90 | } 91 | 92 | showLaps () { 93 | this.laps.forEach((lapTime, i) => { 94 | const lapNumber = i + 1 95 | const lapInfo = ` - LAP ${lapNumber}: ${lapTime}` 96 | clivas.line(lapInfo) 97 | }) 98 | } 99 | } 100 | 101 | module.exports = Stopwatch 102 | --------------------------------------------------------------------------------