Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| var strRepeat = require('../../lib/helpers').strRepeat; | ||
| var duration = require('../../lib/helpers').duration; | ||
| var qz = require('../../lib/helpers').quantize; | ||
| var c = require('../../lib/helpers').c; | ||
| var clear = require('../../lib/helpers').clear; | ||
| var format = require('util').format; | ||
| var moment = require('moment'); | ||
| /** | ||
| * Landing strip reporter. | ||
| */ | ||
| exports.extend = function(pomo) { | ||
| var self = this; | ||
| pomo.on({ | ||
| 'start': function() { | ||
| clear(); | ||
| var str = format("time for %s (%s)", | ||
| pomo.reason, duration(pomo.work.duration)); | ||
| if (pomo.snack) | ||
| str += format("\na %s break follows", | ||
| duration(pomo.snack.duration)); | ||
| self.info(str); | ||
| }, | ||
| // Work-to-snack transition | ||
| 'snack': function() { | ||
| self.info(format("time for a break (%s)", | ||
| duration(pomo.snack.duration))); | ||
| }, | ||
| 'timer': function(mode, perc, elapsed, remaining, words) { | ||
| self.showStrip.apply(this, arguments); | ||
| }, | ||
| // All done | ||
| 'finish': function() { | ||
| self.info("done"); | ||
| }, | ||
| 'interrupt': function() { | ||
| console.log(""); | ||
| self.info("interrupted"); | ||
| } | ||
| }); | ||
| }; | ||
| /** | ||
| * Print the landing strip. | ||
| * Method signature is same as runner.on('timer') | ||
| */ | ||
| exports.showStrip = function(mode, percent, elapsed, remaining) { | ||
| if (mode === null) return; | ||
| var color = (mode === 'snack' ? 32 : 34); | ||
| var dot = '⋅'; | ||
| var peg = '✈'; | ||
| var check = '✔'; | ||
| var len = 50; | ||
| var left = parseInt(len * percent, 10); | ||
| var right = len - left; | ||
| // The thing in the middle | ||
| var glyph = ((percent === 1) ? c(color, dot) : peg); | ||
| // Progress bar | ||
| var progress = | ||
| c(color, strRepeat(dot, left)) + | ||
| glyph + | ||
| c(color, strRepeat(dot, right)); | ||
| var line = ''; | ||
| line += " " + progress + ' '; | ||
| // Quant | ||
| elapsed = qz(elapsed); | ||
| remaining = qz(remaining); | ||
| // Start | ||
| if (percent === 0) { | ||
| line += ''; | ||
| // Finished | ||
| } else if (percent === 1) { | ||
| line += ' ' + c(color, duration(elapsed)); | ||
| line += ' ' + c(32, check); | ||
| // Last few seconds | ||
| } else if (remaining < 60000) { | ||
| line += ' ' + c(31, 'last ' + duration(remaining).replace(/ .*$/, '')); | ||
| // All else | ||
| } else { | ||
| line += ' ' + c(color, duration(elapsed)); | ||
| if (elapsed >= 1000) { | ||
| line += ' ' + c(30, dot + ' ' + duration(remaining).replace(/ .*$/, '+') + ' left'); | ||
| } | ||
| } | ||
| // Clear it out and print | ||
| process.stdout.write("\033[2K\r" + line + ' '); | ||
| if (percent === 1) process.stdout.write("\n"); | ||
| }; | ||
| /** | ||
| * Prints a line, used by .info | ||
| */ | ||
| exports.print = function(msg) { | ||
| process.stdout.write(c(33,' > ')); | ||
| console.log(msg.replace(/\n/g, '\n ')); | ||
| }; | ||
| /** | ||
| * Prints a line | ||
| * | ||
| * .info('hello there') | ||
| * " > 03:34pm - hello there" | ||
| */ | ||
| exports.info = function(msg) { | ||
| var dot = ' ⋅ '; | ||
| console.log(''); | ||
| this.print(moment().format('HH:mma') + dot + msg); | ||
| console.log(''); | ||
| }; |
| var fs = require('fs'); | ||
| var ini = require('ini'); | ||
| var moment = require('moment'); | ||
| var format = require('util').format; | ||
| var duration = require('../../lib/helpers').shortDuration; | ||
| /** | ||
| * Logs the given `pomodoro` to `file`. | ||
| * | ||
| * pomodoro == { | ||
| * reason: 'xx' | ||
| * duration: 30000 | ||
| * break: 5000 | ||
| * date: | ||
| * interrupted: false | ||
| * } | ||
| */ | ||
| exports.extend = function(pomo, logfile) { | ||
| var self = this; | ||
| pomo.on('exit', function() { | ||
| if (!pomo.work.startDate) return; | ||
| self.log(logfile, { | ||
| reason : pomo.reason, | ||
| start : pomo.start, | ||
| end : pomo.end || pomo.now(), | ||
| duration : pomo.work.elapsed(), | ||
| 'break' : pomo.snack.elapsed(), | ||
| interrupted : pomo.interrupted | ||
| }); | ||
| }); | ||
| }; | ||
| exports.log = function(file, pomodoro) { | ||
| var data = this.load(this.read(file)) || {}; | ||
| // Heading | ||
| var date = moment(pomodoro.start).format('YYYY-MM-DD ddd').toLowerCase(); | ||
| // Key | ||
| var time = format("%s - %s", | ||
| moment(pomodoro.start).format('h:mma'), | ||
| moment(pomodoro.end).format('h:mma')); | ||
| var str = format("%s (%s%s%s)", | ||
| pomodoro.reason, | ||
| duration(pomodoro.duration), | ||
| (pomodoro['break'] ? (' + ' + duration(pomodoro['break'])) : ''), | ||
| (pomodoro.interrupted ? ', stopped' : '')); | ||
| if (!data[date]) data[date] = {}; | ||
| data[date][time] = str; | ||
| var output = this.dump(data); | ||
| this.write(file, output); | ||
| }; | ||
| /** | ||
| * Dump/load from object to/from sting | ||
| * @api internal | ||
| */ | ||
| exports.dump = function(obj) { | ||
| return ini.stringify(obj); | ||
| }; | ||
| exports.load = function(str) { | ||
| return ini.parse(str); | ||
| }; | ||
| /** | ||
| * Reads file | ||
| * @api internal | ||
| */ | ||
| exports.read = function(file) { | ||
| try { | ||
| return fs.readFileSync(file, 'utf-8'); | ||
| } catch(e) { | ||
| return ''; | ||
| } | ||
| }; | ||
| exports.write = function(file, output) { | ||
| fs.writeFileSync(file, output, { encoding: 'utf-8' }); | ||
| }; |
| var Helpers = require('../../lib/helpers'); | ||
| var speak = require('../../lib/speak'); | ||
| var moment = require('moment'); | ||
| var format = require('util').format; | ||
| /** | ||
| * Reporter for speaking and growling. | ||
| */ | ||
| exports.extend = function(pomo, options) { | ||
| var self = this; | ||
| var say = function(words) { | ||
| if (!options.quiet) self.speak(words); | ||
| self.growl(words); | ||
| }; | ||
| pomo.on({ | ||
| 'start': function() { | ||
| say(format("%s, %s for %s", | ||
| moment(pomo.now()).format("h:mm a"), | ||
| pomo.work.duration.humanize(), | ||
| pomo.reason)); | ||
| }, | ||
| 'snack': function() { | ||
| say(format("Done! %s, break for %s", | ||
| moment().format("hh:mm a"), | ||
| pomo.snack.duration.humanize())); | ||
| }, | ||
| 'finish': function() { | ||
| say(pomo.reason + ": all done!"); | ||
| }, | ||
| 'timer': function(_, _, _, _, words) { | ||
| if (words) say(words); | ||
| } | ||
| }); | ||
| }; | ||
| exports.speak = speak; | ||
| exports.growl = function(msg) { | ||
| var fruit = require.resolve('../../data/tomato.png'); | ||
| require('growl')(msg, { title: 'Pomo', image: fruit }); | ||
| }; | ||
| var fs = require('fs'); | ||
| var path = require('path'); | ||
| var strRepeat = require('../../lib/helpers').strRepeat; | ||
| var duration = require('../../lib/helpers').duration; | ||
| var qz = require('../../lib/helpers').quantize; | ||
| /** | ||
| * Tmux reporter. | ||
| */ | ||
| exports.extend = function(pomo, options) { | ||
| var self = this; | ||
| var fn = path.resolve((options.file || '').replace(/^~/, home())); | ||
| pomo.on({ | ||
| 'timer': function(mode, percent, elapsed, remaining) { | ||
| var str = self.get.apply(this, arguments); | ||
| fs.writeFile(fn, str, function(err) { if (err) throw(err); }); | ||
| }, | ||
| 'exit': function() { | ||
| fs.writeFileSync(fn, ''); | ||
| } | ||
| }); | ||
| }; | ||
| exports.dot = '⋅'; | ||
| exports.peg = '◦'; | ||
| exports.len = 8; | ||
| exports.color = { work: 4, snack: 2 }; | ||
| /** | ||
| * Returns the timer msg | ||
| */ | ||
| exports.get = function(mode, percent, elapsed, remaining) { | ||
| var dot = exports.dot; | ||
| var peg = exports.peg; | ||
| var len = exports.len; | ||
| var color = exports.color[mode]; | ||
| elapsed = qz(elapsed); | ||
| remaining = qz(remaining); | ||
| var left = parseInt(len * percent, 10); | ||
| var right = len - left; | ||
| var progress = | ||
| '#[fg=0]' + | ||
| strRepeat(dot, left) + | ||
| '#[fg='+color+']' + | ||
| peg + | ||
| '#[fg=0]' + | ||
| strRepeat(dot, right); | ||
| var dur = ''; | ||
| if (+remaining > 0 && +elapsed > 0) | ||
| dur = '#[fg='+color+']' + | ||
| duration(remaining).replace(/ .*$/, '') + ' '; | ||
| return dur + progress + '#[fg=0]'; | ||
| }; | ||
| function home() { | ||
| return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; | ||
| } | ||
+144
| var EventEmitter = require('events').EventEmitter; | ||
| var moment = require('moment'); | ||
| var Q = require('q'); | ||
| var _ = require('underscore'); | ||
| var Timer = require('./timer'); | ||
| /** | ||
| * Manages a work and snack timer and emits events along the way. | ||
| * | ||
| * Events: | ||
| * - start | ||
| * - timer (mode, perc, elapsed, remaining, words) - fired on update/start/finish | ||
| * - finish | ||
| * - interrupt | ||
| * - exit (happens after finish or interrupt) | ||
| * | ||
| * The timer event can be fired with: | ||
| * | ||
| * - Initial printout ('work', 0, 0, 0) | ||
| * - Progress ('work', 0.5, 2000, 4000, null) | ||
| * - Progress ('work', 0.5, 2000, 4000, "last 2 seconds") | ||
| * - Done ('work', 1, ...) | ||
| * - Interrupt (null) | ||
| * | ||
| * Example: | ||
| * | ||
| * var runner = new Runner(25, 5); | ||
| * runner | ||
| * .on('start', function() { ... }) | ||
| * .on('work:start', function() { ... }) | ||
| * .run(); | ||
| */ | ||
| var Runner = module.exports = function(work, snack, options) { | ||
| // Timers | ||
| this.work = new Timer(work, {mode: 'work'}); | ||
| this.snack = snack ? new Timer(snack, {mode: 'snack'}) : null; | ||
| // Start and end times | ||
| this.start = null; | ||
| this.end = null; | ||
| this.interrupted = false; | ||
| // Misc | ||
| this.reason = options.reason; | ||
| this.events = new EventEmitter(); | ||
| }; | ||
| /** | ||
| * Executes the test trunner. | ||
| */ | ||
| Runner.prototype.run = function() { | ||
| var self = this; | ||
| var events = self.events; | ||
| process.on('SIGINT', function() { | ||
| self.interrupted = true; | ||
| events.emit('interrupt'); | ||
| events.emit('timer', null); | ||
| events.emit('exit'); | ||
| process.exit(0); | ||
| }); | ||
| function progress(a, b, c, d, e) { | ||
| events.emit('timer', a, b, c, d, e); | ||
| } | ||
| return Q['try'](function() { /* Work: initial */ | ||
| self.work.progress = progress; | ||
| if (self.snack) self.snack.progress = progress; | ||
| events.emit('start'); | ||
| self.work.initial(); | ||
| self.start = self.now(); | ||
| self.interrupted = false; | ||
| return Q.delay(1000); | ||
| }).then(function() { /* Work: start */ | ||
| return self.work.start(); | ||
| }).then(function() { /* Snack */ | ||
| if (!self.snack) return; | ||
| return Q['try'](function() { /* Snack: initial */ | ||
| events.emit('snack'); | ||
| self.snack.initial(); | ||
| return Q.delay(3000); | ||
| }).then(function() { /* Snack: start */ | ||
| self.end = self.now(); | ||
| return self.snack.start(); | ||
| }); | ||
| }).then(function() { /* Everything finish */ | ||
| events.emit('finish'); | ||
| events.emit('exit'); | ||
| }).done(); | ||
| }; | ||
| /** | ||
| * Returns the current date. | ||
| * (Here to be stubbable) | ||
| */ | ||
| Runner.prototype.now = function() { | ||
| return moment(this.work.now()); | ||
| }; | ||
| /** | ||
| * Interrupts the current run. | ||
| */ | ||
| Runner.prototype.interrupt = function() { | ||
| this.events.emit('interrupt'); | ||
| this.work.interrupt(); | ||
| this.snack.interrupt(); | ||
| return this; | ||
| }; | ||
| /** | ||
| * Binds to an event. | ||
| */ | ||
| Runner.prototype.on = function(event, fn) { | ||
| var self = this; | ||
| if (typeof event === 'object') { | ||
| _.each(event, function(listener, e) { | ||
| self.on(e, listener); | ||
| }); | ||
| return this; | ||
| } | ||
| _.each(event.trim().split(' '), function(event) { | ||
| self.events.on(event, _(fn).bind(this)); | ||
| }); | ||
| return this; | ||
| }; | ||
| module.exports = Runner; |
+22
| var which = require('../lib/helpers').which; | ||
| var exec = require('../lib/helpers').exec; | ||
| var format = require('util').format; | ||
| var pkgs = { | ||
| say: "say %s", | ||
| espeak: "echo %s | espeak" | ||
| }; | ||
| var sayCmd = | ||
| which('say') ? pkgs.say : | ||
| which('espeak') ? pkgs.espeak : null; | ||
| /** | ||
| * Polyfill for speaking | ||
| * Uses `say` on OSX and `espeak` on linux | ||
| */ | ||
| module.exports = function(words) { | ||
| exec(format(sayCmd, JSON.stringify(words))); | ||
| }; | ||
| require('./setup'); | ||
| describe('misc', function() { | ||
| it('should require files fine', function() { | ||
| require('../lib/speak'); | ||
| require('../lib/helpers'); | ||
| require('../lib/timer'); | ||
| require('../lib/runner'); | ||
| require('../lib/reporters/tmux'); | ||
| require('../lib/reporters/speaker'); | ||
| require('../lib/reporters/landing'); | ||
| require('../lib/reporters/logger'); | ||
| }); | ||
| }); |
| require('./setup'); | ||
| var Landing = require('../lib/reporters/landing'); | ||
| var Tmux = require('../lib/reporters/tmux'); | ||
| var Logger = require('../lib/reporters/logger'); | ||
| var Speaker = require('../lib/reporters/speaker'); | ||
| var Runner = require('../lib/runner'); | ||
| var EventEmitter = require('events').EventEmitter; | ||
| var Helpers = require('../lib/helpers'); | ||
| var _ = require('underscore'); | ||
| var fs = require('fs'); | ||
| var Q = require('q'); | ||
| describe('Simulations', function() { | ||
| var pomo = {}; | ||
| var events; | ||
| // Stub `pomo` | ||
| beforeEach(function() { | ||
| pomo = new Runner(25, 5, { reason: 'torture' }); | ||
| events = pomo.events; | ||
| sinon.stub(pomo, 'now', function() { | ||
| return moment('jan 1 2013 03:00 am'); | ||
| }); | ||
| }); | ||
| /** | ||
| * Test the tmux reporter | ||
| */ | ||
| describe('tmux', function() { | ||
| var file; | ||
| beforeEach(function() { | ||
| Tmux.dot = '.'; | ||
| Tmux.peg = 'X'; | ||
| Tmux.color = { work: 2, snack: 4 }; | ||
| file = '/tmp/pomo_simulation_test_'+Math.random(); | ||
| Tmux.extend(pomo, { file: file }); | ||
| }); | ||
| it('initial', pt(function(done) { | ||
| return Q['try'](function() { | ||
| events.emit('start'); | ||
| events.emit('timer', 'work', 0, 0, 10000, null); | ||
| return Q.delay(50); | ||
| }).then(function() { | ||
| assert.equal(read(file), '#[fg=0]#[fg=2]X#[fg=0]........#[fg=0]'); | ||
| }); | ||
| })); | ||
| it('progress', pt(function() { | ||
| return Q['try'](function() { | ||
| events.emit('timer', 'work', 0.4, 4000, 10000, null); | ||
| return Q.delay(50); | ||
| }).then(function() { | ||
| assert.equal(read(file), '#[fg=2]10s #[fg=0]...#[fg=2]X#[fg=0].....#[fg=0]'); | ||
| }); | ||
| })); | ||
| }); | ||
| /** | ||
| * Test the Speak reporter | ||
| */ | ||
| describe('speaker', function() { | ||
| var speak, growl; | ||
| beforeEach(function() { | ||
| speak = sinon.stub(Speaker, 'speak'); | ||
| growl = sinon.stub(Speaker, 'growl'); | ||
| Speaker.extend(pomo, { quiet: false }); | ||
| }); | ||
| afterEach(function() { | ||
| Speaker.speak.restore(); | ||
| Speaker.growl.restore(); | ||
| }); | ||
| it('initial', function() { | ||
| events.emit('start'); | ||
| events.emit('timer', 'work', 0, 0, 10000, null); | ||
| assert.isTrue(speak.calledOnce); | ||
| assert.jsonEqual(speak.firstCall.args, ["3:00 am, 25 minutes for torture"]); | ||
| }); | ||
| it('5 minutes in', function() { | ||
| events.emit('timer', 'work', 0.2, 5*minutes, 25*minutes, '5 minutes in'); | ||
| assert.isTrue(speak.calledOnce); | ||
| assert.jsonEqual(speak.firstCall.args, ["5 minutes in"]); | ||
| }); | ||
| it('done', function() { | ||
| events.emit('snack'); | ||
| assert(Speaker.speak.calledOnce); | ||
| }); | ||
| }); | ||
| }); | ||
| function read(file) { | ||
| return fs.readFileSync(file, 'utf-8'); | ||
| } |
| require('./setup'); | ||
| if (process.env['fast']) return; | ||
| var Runner = require('../lib/runner'); | ||
| var secs = 1000; | ||
| describe('Runner', function() { | ||
| this.timeout(30*secs); | ||
| it('should work', function(done) { | ||
| var runner = new Runner(5 / 60, 5 / 60, { reason: "Tea" }); | ||
| var events = []; | ||
| runner.on({ | ||
| 'start': function() { | ||
| events.push(['start', v(arguments)]); | ||
| }, | ||
| 'timer': function(mode, perc, elapsed, remaining, words) { | ||
| process.stdout.write('.'); | ||
| events.push(['timer', [ mode, r(perc, 0.1), r(elapsed, 500), r(remaining, 500), words ] ]); | ||
| }, | ||
| 'snack': function() { | ||
| events.push(['snack', v(arguments)]); | ||
| }, | ||
| 'finish': function() { | ||
| events.push(['finish', v(arguments)]); | ||
| assert.jsonEqual(events, [ | ||
| [ 'start', [] ], | ||
| // mode perc ela rem words | ||
| [ 'timer', [ 'work', 0, 0, 0, null ] ], | ||
| [ 'timer', [ 'work', 0, 0, 5000, null ] ], | ||
| [ 'timer', [ 'work', 0.2, 1000, 4000, null ] ], | ||
| [ 'timer', [ 'work', 0.4, 2000, 3000, 3 ] ], | ||
| [ 'timer', [ 'work', 0.6, 3000, 2000, 2 ] ], | ||
| [ 'timer', [ 'work', 0.8, 4000, 1000, 1 ] ], | ||
| [ 'timer', [ 'work', 1.0, 5000, 0, null ] ], | ||
| [ 'snack', [] ], | ||
| // mode perc ela rem words | ||
| [ 'timer', [ 'snack', 0, 0, 0, null ] ], | ||
| [ 'timer', [ 'snack', 0, 0, 5000, null ] ], | ||
| [ 'timer', [ 'snack', 0.2, 1000, 4000, null ] ], | ||
| [ 'timer', [ 'snack', 0.4, 2000, 3000, 3 ] ], | ||
| [ 'timer', [ 'snack', 0.6, 3000, 2000, 2 ] ], | ||
| [ 'timer', [ 'snack', 0.8, 4000, 1000, 1 ] ], | ||
| [ 'timer', [ 'snack', 1.0, 5000, 0, null ] ], | ||
| [ 'finish', [] ] | ||
| ]); | ||
| done(); | ||
| } | ||
| }).run(); | ||
| }); | ||
| }); | ||
| // "normalizes" an arguments object | ||
| function v(args) { | ||
| return [].slice.call(args).map(function(item) { | ||
| return (item && item.valueOf) ? item.valueOf() : item; | ||
| }); | ||
| } | ||
| // Round | ||
| function r(number, precision) { | ||
| return Math.round(1000 * precision * Math.round(number / precision)) / 1000; | ||
| } |
+10
-1
@@ -0,1 +1,8 @@ | ||
| ## v1.1.0 - June 20, 2013 | ||
| * Big refactor! | ||
| * Refactored to allow custom reporters in the future | ||
| * Linux speech support (uses espeak instead of say) | ||
| * Growl support for other platforms (uses [growl] package) | ||
| ## v1.0.7 - June 19, 2013 | ||
@@ -17,3 +24,3 @@ | ||
| * Fix 'a minute to go!' announcement. | ||
| * The progress bar and speaking is now better synchronized. | ||
| * The progress bar and speaking is now better synchronizedk | ||
@@ -28,1 +35,3 @@ ## v1.0.3 - June 18, 2013 | ||
| * Initial release. | ||
| [growl]: https://npmjs.org/package/growl |
+15
-5
| var _ = require('underscore'); | ||
| var moment = require('moment'); | ||
| var fs = require('fs'); | ||
| var path = require('path'); | ||
| var exists = fs.existsSync || path.existsSync; | ||
| var format = require('util').format; | ||
@@ -22,8 +26,14 @@ exports.strRepeat = function(str, n) { | ||
| exports.speak = function(words) { | ||
| exec('say '+JSON.stringify(words)); | ||
| }; | ||
| /** | ||
| * Taken from npm package growl | ||
| */ | ||
| exports.growl = function(words) { | ||
| exec("echo " + JSON.stringify(words) + " | growlnotify Pomo"); | ||
| var which = exports.which = function(name) { | ||
| var paths = process.env.PATH.split(':'); | ||
| var loc; | ||
| for (var i = 0, len = paths.length; i < len; ++i) { | ||
| loc = path.join(paths[i], name); | ||
| if (exists(loc)) return loc; | ||
| } | ||
| }; | ||
@@ -30,0 +40,0 @@ |
+14
-27
@@ -22,3 +22,3 @@ var moment = require('moment'); | ||
| * * speed - duration (in ms) in between progress updates | ||
| * * mode - 'break' or 'work' | ||
| * * mode - 'snack' or 'work' | ||
| * | ||
@@ -30,4 +30,3 @@ */ | ||
| this.say = options.say || function(){}; | ||
| this.progress = options.progress || []; | ||
| this.progress = options.progress || function(){}; | ||
| this.mode = options.mode || 'work'; | ||
@@ -72,7 +71,2 @@ this.speed = options.speed || 1000; | ||
| function run() { | ||
| // Throttle to once every N updates | ||
| n = ((n + 1) % (1000 / speed)); | ||
| if (n === 0) timer.speakTime(); | ||
| // Update progress bar | ||
| timer.update(); | ||
@@ -86,3 +80,3 @@ | ||
| progress(timer.elapsed()); | ||
| setTimeout(run, speed); | ||
| timer._id = setTimeout(run, speed); | ||
| } | ||
@@ -120,12 +114,2 @@ } | ||
| /** | ||
| * Speak the remaining time | ||
| * ("2 minutes remaining") | ||
| */ | ||
| Timer.prototype.speakTime = function() { | ||
| var msg = this.getMessage(); | ||
| if (msg) this.say(msg); | ||
| }; | ||
| /** | ||
| * Returns the message for the current second (to be spoken). | ||
@@ -161,2 +145,9 @@ */ | ||
| Timer.prototype.interrupt = function() { | ||
| if (this._id) { | ||
| clearTimeout(this._id); | ||
| delete this._id; | ||
| } | ||
| }; | ||
| /** | ||
@@ -166,3 +157,3 @@ * Run progress meters | ||
| Timer.prototype.doProgress = function() { | ||
| Timer.prototype.progress = function() { | ||
| var args = arguments; | ||
@@ -179,3 +170,3 @@ this.progress.forEach(function(callback) { | ||
| Timer.prototype.initial = function() { | ||
| this.doProgress(0, 0, 0, this.mode); | ||
| this.progress(this.mode, 0, 0, 0); | ||
| }; | ||
@@ -185,11 +176,7 @@ | ||
| if (this.percent() < 1) | ||
| this.doProgress(this.percent(), this.elapsed(), this.remaining(), this.mode); | ||
| this.progress(this.mode, this.percent(), this.elapsed(), this.remaining(), this.getMessage()); | ||
| }; | ||
| Timer.prototype.done = function() { | ||
| this.doProgress(1, this.duration, 0, this.mode); | ||
| this.progress(this.mode, 1, this.duration, 0); | ||
| }; | ||
| Timer.prototype.abort = function() { | ||
| this.doProgress(null); | ||
| }; |
+3
-2
@@ -10,3 +10,3 @@ { | ||
| "author": "Rico Sta. Cruz <hi@ricostacruz.com>", | ||
| "version": "1.0.7", | ||
| "version": "1.1.0", | ||
| "repository": { | ||
@@ -33,4 +33,5 @@ "type": "git", | ||
| "q": "~0.9.6", | ||
| "ini": "~1.1.0" | ||
| "ini": "~1.1.0", | ||
| "growl": "~1.7.0" | ||
| } | ||
| } |
+21
-11
@@ -1,2 +0,2 @@ | ||
| # Pomo.js | ||
| # Pomo  | ||
@@ -24,15 +24,20 @@ ``` | ||
| * Ridiculously simple (just type `pomojs`) | ||
| * Configurable work and break durations (`pomojs --work 10 --break 2`) | ||
| * Announces via text-to-speech ("5 minutes to go!") | ||
| * Growls (via growlnotify) | ||
| * No support for long breaks (this is a feature. problem?) | ||
| * Tmux support (status bar integration) | ||
| * Optional logging | ||
| * ridiculously simple (just type `pomojs`) | ||
| * configurable work and break durations (`pomojs --work 10 --break 2`) | ||
| * announces via text-to-speech ("5 minutes to go!") | ||
| * notifications | ||
| * no support for long breaks (this is a feature. problem?) | ||
| * tmux support (status bar integration, shown above) | ||
| * optional logging | ||
| ### Requirements | ||
| * node.js and OSX | ||
| * growlnotify | ||
| * node.js (required) | ||
| * osx 10.8+: `gem install terminal-notifier` | ||
| * linux: `sudo apt-get install libnotify-bin` | ||
| * osx (others): [growlnotify] | ||
| * linux: `sudo apt-get install espeak` | ||
| see [growl] readme for growl requirements. | ||
| ---- | ||
@@ -94,4 +99,6 @@ | ||
| MIT | ||
| Tomato icon by artbees. (via [iconfinder.net][icon]) | ||
| (c) 2013, Rico Sta. Cruz. MIT | ||
| [visionmedia/pomo]: https://github.com/visionmedia/pomo | ||
@@ -101,1 +108,4 @@ [pmd]: http://me.dt.in.th/page/pmd | ||
| [pomo-tmux]: https://github.com/visionmedia/pomo#tmux-status-bar-integration | ||
| [icon]: http://www.iconfinder.com/icondetails/56019/128/tomato_vegetable_icon | ||
| [growl]: https://npmjs.org/package/growl | ||
| [growlnotify]: http://growl.info/downloads |
+18
-13
| require('./setup'); | ||
| var logger = require('../lib/logger'); | ||
| var logger = require('../lib/reporters/logger'); | ||
| var fs = require('fs'); | ||
@@ -35,6 +35,7 @@ var mins = 60 * 1000; | ||
| it('should work fresh', function() { | ||
| logger('x.txt', { | ||
| logger.log('x.txt', { | ||
| reason: 'working', | ||
| duration: 35*mins, 'break': 5*mins, | ||
| date: moment('May 5, 2013 1:00 pm').toDate() | ||
| start: moment('May 5, 2013 1:00 pm').toDate(), | ||
| end: moment('May 5, 2013 1:40 pm').toDate() | ||
| }); | ||
@@ -54,6 +55,7 @@ | ||
| it('no break', function() { | ||
| logger('x.txt', { | ||
| logger.log('x.txt', { | ||
| reason: 'working', | ||
| duration: 35*mins, 'break': 0, | ||
| date: moment('May 5, 2013 1:00 pm').toDate() | ||
| start: moment('May 5, 2013 1:00 pm').toDate(), | ||
| end: moment('May 5, 2013 1:35 pm').toDate() | ||
| }); | ||
@@ -69,18 +71,21 @@ | ||
| it('should consolidate', function() { | ||
| logger('x.txt', { | ||
| it('should consolistart', function() { | ||
| logger.log('x.txt', { | ||
| reason: 'working', | ||
| duration: 35*mins, 'break': 5*mins, | ||
| date: moment('May 5, 2013 3:00 pm').toDate() | ||
| start: moment('May 5, 2013 3:00 pm').toDate(), | ||
| end: moment('May 5, 2013 3:40 pm').toDate() | ||
| }); | ||
| logger('x.txt', { | ||
| logger.log('x.txt', { | ||
| reason: 'working again', | ||
| duration: 30*mins, 'break': 5*mins, | ||
| date: moment('May 5, 2013 3:30 pm').toDate(), | ||
| start: moment('May 5, 2013 4:00 pm').toDate(), | ||
| end: moment('May 5, 2013 4:35 pm').toDate(), | ||
| interrupted: true | ||
| }); | ||
| logger('x.txt', { | ||
| logger.log('x.txt', { | ||
| reason: 'also working', | ||
| duration: 25*mins, 'break': 5*mins, | ||
| date: moment('May 6, 2013 5:00 am').toDate() | ||
| start: moment('May 6, 2013 5:00 am').toDate(), | ||
| end: moment('May 6, 2013 5:30 am').toDate() | ||
| }); | ||
@@ -93,3 +98,3 @@ | ||
| '3:00pm - 3:40pm': 'working (35m + 5m)', | ||
| '3:30pm - 4:05pm': 'working again (30m + 5m, stopped)' | ||
| '4:00pm - 4:35pm': 'working again (30m + 5m, stopped)' | ||
| }, | ||
@@ -96,0 +101,0 @@ '2013-05-06 mon': { |
+0
-14
@@ -129,16 +129,2 @@ require('./setup'); | ||
| /** | ||
| * Test calls to speak() | ||
| */ | ||
| describe('speakTime()', function() { | ||
| it("say '2 minutes to go'", function() { | ||
| timer = makeTimer({ mins: 2.1, elapsed: 6*secs }); | ||
| timer.speakTime(); | ||
| assert.equal(say.callCount, 1); | ||
| assert.match(say.firstCall.args[0], /2 minutes to go/); | ||
| }); | ||
| }); | ||
| /** | ||
| * Create a mock timer object | ||
@@ -145,0 +131,0 @@ */ |
| var strRepeat = require('./helpers').strRepeat; | ||
| var duration = require('./helpers').duration; | ||
| var qz = require('./helpers').quantize; | ||
| var c = require('./helpers').c; | ||
| var format = require('util').format; | ||
| module.exports = function(percent, elapsed, remaining, mode) { | ||
| if (percent === null) return; | ||
| var color = (mode === 'break' ? 32 : 34); | ||
| var dot = '⋅'; | ||
| var peg = '✈'; | ||
| var check = '✔'; | ||
| var len = 50; | ||
| var left = parseInt(len * percent, 10); | ||
| var right = len - left; | ||
| // The thing in the middle | ||
| var glyph = ((percent === 1) ? c(color, dot) : peg); | ||
| // Progress bar | ||
| var progress = | ||
| c(color, strRepeat(dot, left)) + | ||
| glyph + | ||
| c(color, strRepeat(dot, right)); | ||
| var line = ''; | ||
| line += " " + progress + ' '; | ||
| // Quant | ||
| elapsed = qz(elapsed); | ||
| remaining = qz(remaining); | ||
| // Start | ||
| if (percent === 0) { | ||
| line += ''; | ||
| // Finished | ||
| } else if (percent === 1) { | ||
| line += ' ' + c(color, duration(elapsed)); | ||
| line += ' ' + c(32, check); | ||
| // Last few seconds | ||
| } else if (remaining < 60000) { | ||
| line += ' ' + c(31, 'last ' + duration(remaining).replace(/ .*$/, '')); | ||
| // All else | ||
| } else { | ||
| line += ' ' + c(color, duration(elapsed)); | ||
| if (elapsed >= 1000) { | ||
| line += ' ' + c(30, dot + ' ' + duration(remaining).replace(/ .*$/, '+') + ' left'); | ||
| } | ||
| } | ||
| // Clear it out and print | ||
| process.stdout.write("\033[2K\r" + line + ' '); | ||
| if (percent === 1) process.stdout.write("\n"); | ||
| }; |
| var fs = require('fs'); | ||
| var ini = require('ini'); | ||
| var moment = require('moment'); | ||
| var duration = require('./helpers').shortDuration; | ||
| var format = require('util').format; | ||
| /** | ||
| * Logs the given `pomodoro` to `file`. | ||
| * | ||
| * pomodoro == { | ||
| * reason: 'xx' | ||
| * duration: 30000 | ||
| * break: 5000 | ||
| * date: | ||
| * interrupted: false | ||
| * } | ||
| */ | ||
| var logger = module.exports = function(file, pomodoro) { | ||
| var data = logger.load(logger.read(file)) || {}; | ||
| // Infer the end date. | ||
| var end = moment(+pomodoro.date + (pomodoro['break']||0) + pomodoro.duration); | ||
| // Heading | ||
| var date = moment(pomodoro.date).format('YYYY-MM-DD ddd').toLowerCase(); | ||
| // Key | ||
| var time = format("%s - %s", | ||
| moment(pomodoro.date).format('h:mma'), | ||
| moment(end).format('h:mma')); | ||
| var str = format("%s (%s%s%s)", | ||
| pomodoro.reason, | ||
| duration(pomodoro.duration), | ||
| (pomodoro['break'] ? (' + ' + duration(pomodoro['break'])) : ''), | ||
| (pomodoro.interrupted ? ', stopped' : '')); | ||
| if (!data[date]) data[date] = {}; | ||
| data[date][time] = str; | ||
| var output = logger.dump(data); | ||
| logger.write(file, output); | ||
| }; | ||
| logger.dump = function(obj) { | ||
| return ini.stringify(obj); | ||
| }; | ||
| logger.load = function(str) { | ||
| return ini.parse(str); | ||
| }; | ||
| /** | ||
| * Reads file | ||
| * @api internal | ||
| */ | ||
| logger.read = function(file) { | ||
| try { | ||
| return fs.readFileSync(file, 'utf-8'); | ||
| } catch(e) { | ||
| return ''; | ||
| } | ||
| }; | ||
| logger.write = function(file, output) { | ||
| fs.writeFileSync(file, output, { encoding: 'utf-8' }); | ||
| }; |
-15
| var c = require('./helpers').c; | ||
| var moment = require('moment'); | ||
| exports.info = function(msg) { | ||
| process.stdout.write(c(33,' > ')); | ||
| console.log(msg.replace(/\n/g, '\n ')); | ||
| }; | ||
| exports.now = function(msg) { | ||
| var dot = ' ⋅ '; | ||
| console.log(''); | ||
| exports.info(moment().format('HH:mma') + dot + msg); | ||
| console.log(''); | ||
| }; |
-56
| var fs = require('fs'); | ||
| var path = require('path'); | ||
| var strRepeat = require('./helpers').strRepeat; | ||
| var duration = require('./helpers').duration; | ||
| var qz = require('./helpers').quantize; | ||
| var fn = path.resolve(home(), '.pomo_stat'); | ||
| module.exports = function(percent, elapsed, remaining, mode) { | ||
| var str = get.apply(this, arguments); | ||
| // Do it synchronously at the end to ensure sequential-ness | ||
| if (+remaining < 5000) | ||
| fs.writeFileSync(fn, str); | ||
| else | ||
| fs.writeFile(fn, str, function(err) { if (err) throw(err); }); | ||
| }; | ||
| function get(percent, elapsed, remaining, mode) { | ||
| elapsed = qz(elapsed); | ||
| remaining = qz(remaining); | ||
| if (percent === null) return ' '; | ||
| if (+remaining === 0 && mode === 'break') return ''; | ||
| var color = (mode === 'break' ? 2 : 4); | ||
| var dot = '⋅'; | ||
| var peg = '◦'; | ||
| var check = '✔'; | ||
| var len = 8; | ||
| var left = parseInt(len * percent, 10); | ||
| var right = len - left; | ||
| var progress = | ||
| '#[fg=0]' + | ||
| strRepeat(dot, left) + | ||
| '#[fg='+color+']' + | ||
| peg + | ||
| '#[fg=0]' + | ||
| strRepeat(dot, right); | ||
| var dur = ''; | ||
| if (+remaining > 0 && elapsed > 0) | ||
| dur = '#[fg='+color+']' + | ||
| duration(remaining).replace(/ .*$/, ''); | ||
| return dur + ' ' + progress + '#[fg=0]'; | ||
| } | ||
| function home() { | ||
| return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; | ||
| } | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
68804
76.61%24
41.18%1027
62.5%109
10.1%6
20%9
80%+ Added
+ Added