+54
| 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); | ||
| 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) | ||
| dur = '#[fg='+color+']' + | ||
| duration(remaining).replace(/ .*$/, ''); | ||
| return dur + ' ' + progress + '#[fg=0]'; | ||
| } | ||
| function home() { | ||
| return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; | ||
| } | ||
| require('./setup'); | ||
| var Timer = require('../lib/timer'); | ||
| describe('Timer promises', function() { | ||
| var timer; | ||
| var say; | ||
| beforeEach(function() { | ||
| timer = new Timer(10, { speed: 100 }); | ||
| }); | ||
| afterEach(function() { | ||
| timer.now.restore(); | ||
| }); | ||
| it('.elapsed', pt(function() { | ||
| return Q.try(function() { | ||
| setTime('May 5 2013 03:00'); | ||
| return timer.start(); | ||
| }).then(0, 0, function() { | ||
| setTime('May 5 2013 03:02'); | ||
| assert.equal(timer.elapsed(), 2 * minutes); | ||
| setTime('May 5 2013 04:00'); | ||
| }); | ||
| })); | ||
| it('.isLapsed', pt(function() { | ||
| return Q.try(function() { | ||
| setTime('May 5 2013 03:00'); | ||
| return timer.start(); | ||
| }).then(0, 0, function() { | ||
| assert.equal(timer.isLapsed(), false); | ||
| setTime('May 5 2013 04:00'); | ||
| assert.equal(timer.isLapsed(), true); | ||
| }); | ||
| })); | ||
| /** | ||
| * Move in time by stubbing timer.now | ||
| */ | ||
| function setTime(date) { | ||
| if (timer.now.restore) timer.now.restore(); | ||
| sinon.stub(timer, 'now', function() { | ||
| return moment(date).toDate(); | ||
| }); | ||
| } | ||
| }); |
+17
-2
@@ -1,3 +0,18 @@ | ||
| ## v1.0.1 - June 17, 2013 | ||
| ## v1.0.5 - June 18, 2013 | ||
| Initial. | ||
| * Tmux integration with `pomojs -t`. | ||
| ## v1.0.4 - June 18, 2013 | ||
| * Add `pomojs -d <mins>` for no-break timers. | ||
| * Fix 'a minute to go!' announcement. | ||
| * The progress bar and speaking is now better synchronized. | ||
| ## v1.0.3 - June 18, 2013 | ||
| * Fix progress bar clearing unnecesarily. | ||
| * Minor style tweaks. | ||
| ## v1.0.2 - June 17, 2013 | ||
| * Initial release. |
+10
-1
@@ -37,5 +37,7 @@ var _ = require('underscore'); | ||
| * (12000 => "12s") | ||
| * | ||
| * The +999 hack is there to make `duration(12001) == '13s'` rather than 12s | ||
| */ | ||
| exports.duration = function(n) { | ||
| var d = moment.duration(+n); | ||
| var d = moment.duration(+n + 999); | ||
| var segs = []; | ||
@@ -62,1 +64,8 @@ if (d.minutes() > 0) segs.push(d.minutes() + 'm'); | ||
| }; | ||
| /** | ||
| * Quantizes a duration to the nearest second. | ||
| */ | ||
| exports.quantize = function(n) { | ||
| return moment.duration(Math.round(n / 1000) * 1000); | ||
| }; |
+50
-17
| 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(perc, msg, color) { | ||
| // Done | ||
| if (perc === 1) { | ||
| process.stdout.write("\n"); | ||
| } | ||
| module.exports = function(percent, elapsed, remaining, mode) { | ||
| if (percent === null) return; | ||
| // Progress | ||
| else { | ||
| var dot = '⋅'; | ||
| var peg = '✈'; | ||
| var color = (mode === 'break' ? 32 : 34); | ||
| var dot = '⋅'; | ||
| var peg = '✈'; | ||
| var check = '✔'; | ||
| var len = 50; | ||
| var left = parseInt(len * perc, 10); | ||
| var right = len - left; | ||
| var len = 50; | ||
| var left = parseInt(len * percent, 10); | ||
| var right = len - left; | ||
| var progress = | ||
| c(color, strRepeat(dot, left)) + | ||
| peg + | ||
| c(color, strRepeat(dot, right)); | ||
| // The thing in the middle | ||
| var glyph = ((percent === 1) ? c(color, dot) : peg); | ||
| process.stdout.write("\r " + progress + ' ' + c(color, msg) + ' '); | ||
| // 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("\r" + strRepeat(' ', 79) + "\r" + line + ' '); | ||
| if (percent === 1) process.stdout.write("\n"); | ||
| }; |
+66
-33
| var moment = require('moment'); | ||
| var duration = require('./helpers').duration; | ||
| var qz = require('./helpers').quantize; | ||
| var Q = require('q'); | ||
| var _ = require('underscore'); | ||
| /** | ||
| * Timer. | ||
| * Timer. Creates a timer for `mins` minutes. Doesn't actually do anything | ||
| * until you call `.start()`. | ||
| * | ||
@@ -13,5 +16,10 @@ * t = new Timer(30); | ||
| * | ||
| * * progress - progress callback. | ||
| * * progress - progress callbacks. | ||
| * * say - the growl/speak callback. | ||
| * | ||
| * More options: | ||
| * | ||
| * * speed - duration (in ms) in between progress updates | ||
| * * mode - 'break' or 'work' | ||
| * | ||
| */ | ||
@@ -23,8 +31,7 @@ | ||
| this.say = options.say || function(){}; | ||
| this.color = options.color || 34; | ||
| this.progress = options.progress || []; | ||
| this.mode = options.mode || 'work'; | ||
| this.speed = options.speed || 1000; | ||
| this.duration = moment.duration(mins * 60000); | ||
| this.speed = options.speed || 100; | ||
| this.startDate = options.startDate || null; | ||
| this.progress = options.progress || function(){}; | ||
| this.offset = options.offset || 0; | ||
| this.startDate = null; | ||
| }; | ||
@@ -37,3 +44,3 @@ | ||
| Timer.prototype.elapsed = function() { | ||
| return (+this.now() - this.startDate) || 0.0; | ||
| return moment.duration(+this.now() - this.startDate) || 0.0; | ||
| }; | ||
@@ -51,3 +58,3 @@ | ||
| /** | ||
| * Starts a timer. | ||
| * Starts a timer, making periodic updates. | ||
| * | ||
@@ -61,3 +68,3 @@ * timer.start().then(function() { ... }); | ||
| return Q.promise(function(ok, fail, progress) { | ||
| timer.startDate = new Date(+timer.now() - timer.offset); | ||
| timer.startDate = timer.now(); | ||
| var n = 0; | ||
@@ -67,4 +74,7 @@ var speed = timer.speed; | ||
| function run() { | ||
| // Throttle to once every N updates | ||
| n = ((n + 1) % (1000 / speed)); | ||
| if (n === 0) timer.speakTime(); | ||
| // Update progress bar | ||
| timer.update(); | ||
@@ -107,38 +117,58 @@ | ||
| Timer.prototype.remaining = function() { | ||
| return +this.duration - this.elapsed(); | ||
| return moment.duration(+this.duration - this.elapsed()); | ||
| }; | ||
| /** | ||
| * Elapsed msg (in English) | ||
| * (`1m 3s`) | ||
| * Speak the remaining time | ||
| * ("2 minutes remaining") | ||
| */ | ||
| Timer.prototype.elapsedMsg = function() { | ||
| return duration(this.elapsed()); | ||
| Timer.prototype.speakTime = function() { | ||
| var msg = this.getMessage(); | ||
| if (msg) this.say(msg); | ||
| }; | ||
| /** | ||
| * Speak the remaining time | ||
| * ("2 minutes remaining") | ||
| * Returns the message for the current second (to be spoken). | ||
| */ | ||
| Timer.prototype.speakTime = function() { | ||
| var lapsed = moment.duration(this.elapsed()); | ||
| var remaining = moment.duration(this.remaining()); | ||
| Timer.prototype.getMessage = function() { | ||
| var lapsed = qz(this.elapsed()); | ||
| var remaining = qz(this.remaining()); | ||
| var int = parseInt; | ||
| if (lapsed.seconds() === 0 && [5, 10, 15].indexOf(lapsed.asMinutes()) > -1) { | ||
| this.say(parseInt(lapsed.asMinutes(), 10) + " minutes in!"); | ||
| // If too early, don't say anything | ||
| if (lapsed < 2000) | ||
| return; | ||
| } else if (lapsed.seconds() === 0 && [5, 3, 2, 1].indexOf(remaining.asMinutes()) > -1) { | ||
| this.say(lapsed.minutes() + " minutes to go!"); | ||
| // "5 minutes in!" | ||
| if (lapsed.seconds() === 0 && _([5, 10, 15]).include(lapsed.asMinutes())) | ||
| return lapsed.asMinutes() + " minutes in!"; | ||
| } else if (lapsed.asSeconds() === 15) { | ||
| this.say("Last fifteen seconds"); | ||
| // "3 minutes to go!" | ||
| if (remaining.seconds() === 0 && _([5, 3, 2, 1]).include(remaining.asMinutes())) | ||
| return remaining.humanize() + " to go!"; | ||
| } else if (remaining.asSeconds() <= 5 && remaining.asSeconds() >= 1) { | ||
| this.say(parseInt(remaining.asSeconds(), 10)); | ||
| } | ||
| // "last X seconds..." | ||
| if (_([30, 15]).include(remaining.asSeconds())) | ||
| return "Last " + remaining.asSeconds() + " seconds"; | ||
| // "5"... "4"... "3"... | ||
| var secs = remaining.asSeconds(); | ||
| if (secs <= 5 && secs >= 1) | ||
| return secs; | ||
| }; | ||
| /** | ||
| * Run progress meters | ||
| */ | ||
| Timer.prototype.doProgress = function() { | ||
| var args = arguments; | ||
| this.progress.forEach(function(callback) { | ||
| callback.apply(null, args); | ||
| }); | ||
| }; | ||
| /** | ||
| * Display an update | ||
@@ -148,13 +178,16 @@ */ | ||
| Timer.prototype.initial = function() { | ||
| this.progress(0, '-', this.color); | ||
| this.doProgress(0, 0, 0, this.mode); | ||
| }; | ||
| Timer.prototype.update = function() { | ||
| this.progress(this.percent(), this.elapsedMsg(), this.color); | ||
| if (this.percent() < 1) | ||
| this.doProgress(this.percent(), this.elapsed(), this.remaining(), this.mode); | ||
| }; | ||
| Timer.prototype.done = function() { | ||
| this.progress(this.percent(), this.elapsedMsg(), this.color); | ||
| this.progress(1); | ||
| this.doProgress(1, this.duration, 0, this.mode); | ||
| }; | ||
| Timer.prototype.abort = function() { | ||
| this.doProgress(null); | ||
| }; |
+1
-1
@@ -8,3 +8,3 @@ { | ||
| "author": "Rico Sta. Cruz <hi@ricostacruz.com>", | ||
| "version": "1.0.1", | ||
| "version": "1.0.5", | ||
| "repository": { | ||
@@ -11,0 +11,0 @@ "type": "git", |
+26
-4
@@ -10,12 +10,13 @@ # Pomo.js | ||
| [](https://travis-ci.org/rstacruz/ndialog) | ||
| [](https://travis-ci.org/rstacruz/pomo.js) | ||
| Features: | ||
| ### Features | ||
| * Command-line goodness | ||
| * Configurable work and break durations | ||
| * Configurable work and break durations (`pomojs --work 10 --break 2`) | ||
| * Announces via text-to-speech ("5 minutes to go!") | ||
| * Growls | ||
| * Tmux support | ||
| Requirements: | ||
| ### Requirements | ||
@@ -25,2 +26,23 @@ * node.js and OSX | ||
| ### Tmux integration | ||
| Just add this to `~.tmux.conf`: (works almost exactly like in [pomo.rb][pomo-tmux]) | ||
| set-option -g status-right '#(cat ~/.pomo_stat)' | ||
| ...then invoke it with `pomojs -t`. | ||
| ### Also see | ||
| * [visionmedia/pomo] - pomodoro task manager (ruby gem) | ||
| * [pmd] - has OSX status bar integration | ||
| * [pom] - shell script | ||
| ### Acknowledgements | ||
| MIT | ||
| [visionmedia/pomo]: https://github.com/visionmedia/pomo | ||
| [pmd]: http://me.dt.in.th/page/pmd | ||
| [pom]: https://github.com/tobym/pom | ||
| [pomo-tmux]: https://github.com/visionmedia/pomo#tmux-status-bar-integration |
+1
-1
@@ -1,2 +0,2 @@ | ||
| global.Moment = require('moment'); | ||
| global.moment = require('moment'); | ||
| global.chai = require('chai'); | ||
@@ -3,0 +3,0 @@ global.sinon = require('sinon'); |
+111
-37
| require('./setup'); | ||
| var Timer = require('../lib/timer'); | ||
| var secs = 1000; | ||
| var mins = 60*secs; | ||
| describe('Timer', function() { | ||
| var timer; | ||
@@ -11,66 +14,137 @@ var say; | ||
| say = sinon.spy(); | ||
| timer = new Timer(10); | ||
| }); | ||
| afterEach(function() { | ||
| timer.now.restore(); | ||
| }); | ||
| it('.elapsed', pt(function() { | ||
| return Q.try(function() { | ||
| setTime('May 5 2013 03:00'); | ||
| return timer.start(); | ||
| /** | ||
| * Test some of the basic utility methods | ||
| */ | ||
| }).then(0, 0, function() { | ||
| setTime('May 5 2013 03:02'); | ||
| assert.equal(timer.elapsed(), 2 * minutes); | ||
| describe('utilities', function() { | ||
| it(".elapsed()", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 6*secs }); | ||
| assert.equal(+timer.elapsed(), 6*secs); | ||
| }); | ||
| setTime('May 5 2013 04:00'); | ||
| it(".remaining()", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 6*secs }); | ||
| assert.equal(+timer.remaining(), 60*secs); | ||
| }); | ||
| })); | ||
| it('.isLapsed', pt(function() { | ||
| return Q.try(function() { | ||
| setTime('May 5 2013 03:00'); | ||
| return timer.start(); | ||
| it(".isLapsed() true", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 1*mins + 7*secs }); | ||
| assert.equal(timer.isLapsed(), true); | ||
| }); | ||
| }).then(0, 0, function() { | ||
| it(".isLapsed() false", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 1*mins + 5*secs }); | ||
| assert.equal(timer.isLapsed(), false); | ||
| }); | ||
| }); | ||
| setTime('May 5 2013 04:00'); | ||
| assert.equal(timer.isLapsed(), true); | ||
| /** | ||
| * Percent | ||
| */ | ||
| describe('percent()', function() { | ||
| it("at 0", function() { | ||
| timer = makeTimer({ mins: 3, elapsed: 0 }); | ||
| assert.equal(+timer.percent(), 0); | ||
| }); | ||
| })); | ||
| it("at 0.5", function() { | ||
| timer = makeTimer({ mins: 10, elapsed: 5*mins }); | ||
| assert.equal(+timer.percent(), 0.5); | ||
| }); | ||
| it("at 1.0", function() { | ||
| timer = makeTimer({ mins: 10, elapsed: 10*mins }); | ||
| assert.equal(+timer.percent(), 1.0); | ||
| }); | ||
| }); | ||
| /** | ||
| * Test .say() | ||
| * Test what happens when we're off by a few milliseconds | ||
| */ | ||
| describe('.say', pt(function() { | ||
| timer = new Timer(10, { say: say }); | ||
| describe('quantization', function() { | ||
| it("-0.1", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 5.9*secs }); | ||
| return Q.try(function() { | ||
| setTime('May 5 2013 03:00'); | ||
| return timer.start(); | ||
| assert.match(timer.getMessage(), /a minute to go/); | ||
| }); | ||
| }).then(0, 0, function() { /* Progress */ | ||
| // Check say last call | ||
| it("+0.1", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 6.1*secs }); | ||
| }).then(function() { | ||
| setTime('May 5 2013 04:00'); | ||
| assert.match(timer.getMessage(), /a minute to go/); | ||
| }); | ||
| }); | ||
| /** | ||
| * Messages for speaking | ||
| */ | ||
| describe('getMessage()', function() { | ||
| it("don't say when too early", function() { | ||
| timer = makeTimer({ mins: 1, elapsed: 0 }); | ||
| assert.isUndefined(timer.getMessage()); | ||
| }); | ||
| })); | ||
| it("1 minute to go", function() { | ||
| timer = makeTimer({ mins: 1.1, elapsed: 6*secs }); | ||
| assert.match(timer.getMessage(), /a minute to go/); | ||
| }); | ||
| it("2 minutes to go", function() { | ||
| timer = makeTimer({ mins: 2.1, elapsed: 6*secs }); | ||
| assert.match(timer.getMessage(), /2 minutes to go/); | ||
| }); | ||
| it("last 15 secs", function() { | ||
| timer = makeTimer({ mins: 1, elapsed: 45*secs }); | ||
| assert.match(timer.getMessage(), /Last 15/); | ||
| }); | ||
| it("last 5 secs", function() { | ||
| timer = makeTimer({ mins: 1, elapsed: 55*secs }); | ||
| assert.match(timer.getMessage(), /5/); | ||
| }); | ||
| }); | ||
| /** | ||
| * Move in time by stubbing timer.now | ||
| * Test calls to speak() | ||
| */ | ||
| function setTime(date) { | ||
| if (timer.now.restore) timer.now.restore(); | ||
| sinon.stub(timer, 'now', function() { | ||
| return Moment(date).toDate(); | ||
| 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 | ||
| */ | ||
| function makeTimer(options) { | ||
| var timer = new Timer(options.mins); | ||
| var start = moment('jan 10 2011, 01:00 am').toDate(); | ||
| timer.startDate = start; | ||
| // Stub the now() to simulate a given elapsed time | ||
| timer.now = function() { return moment(+start + options.elapsed).toDate(); }; | ||
| timer.say = say; | ||
| return timer; | ||
| } | ||
| }); |
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 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
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
32820
30.07%15
15.38%478
70.71%47
88%3
Infinity%