pax_global_header00006660000000000000000000000064127116577660014534gustar00rootroot0000000000000052 comment=00fa363baf14135c5f13b7b0b3c77f34dd72e3fa node-charm-1.0.1/000077500000000000000000000000001271165776600135505ustar00rootroot00000000000000node-charm-1.0.1/README.markdown000066400000000000000000000111711271165776600162520ustar00rootroot00000000000000charm ===== Use [ansi terminal characters](http://www.termsys.demon.co.uk/vtansi.htm) to write colors and cursor positions. ![me lucky charms](http://substack.net/images/charms.png) example ======= lucky ----- ````javascript var charm = require('charm')(); charm.pipe(process.stdout); charm.reset(); var colors = [ 'red', 'cyan', 'yellow', 'green', 'blue' ]; var text = 'Always after me lucky charms.'; var offset = 0; var iv = setInterval(function () { var y = 0, dy = 1; for (var i = 0; i < 40; i++) { var color = colors[(i + offset) % colors.length]; var c = text[(i + offset) % text.length]; charm .move(1, dy) .foreground(color) .write(c) ; y += dy; if (y <= 0 || y >= 5) dy *= -1; } charm.position(0, 1); offset ++; }, 150); ```` events ====== Charm objects pass along the data events from their input stream except for events generated from querying the terminal device. Because charm puts stdin into raw mode, charm emits two special events: "^C" and "^D" when the user types those combos. It's super convenient with these events to do: ````javascript charm.on('^C', process.exit) ```` The above is set on all `charm` streams. If you want to add your own handling for these special events simply: ````javascript charm.removeAllListeners('^C') charm.on('^C', function () { // Don't exit. Do some mad science instead. }) ```` methods ======= var charm = require('charm')(param or stream, ...) -------------------------------------------------- Create a new readable/writable `charm` stream. You can pass in readable or writable streams as parameters and they will be piped to or from accordingly. You can also pass `process` in which case `process.stdin` and `process.stdout` will be used. You can `pipe()` to and from the `charm` object you get back. charm.reset() ------------- Reset the entire screen, like the /usr/bin/reset command. charm.destroy(), charm.end() ---------------------------- Emit an `"end"` event downstream. charm.write(msg) ---------------- Pass along `msg` to the output stream. charm.position(x, y) -------------------- Set the cursor position to the absolute coordinates `x, y`. charm.position(cb) ------------------ Query the absolute cursor position from the input stream through the output stream (the shell does this automatically) and get the response back as `cb(x, y)`. charm.move(x, y) ---------------- Move the cursor position by the relative coordinates `x, y`. charm.up(y) ----------- Move the cursor up by `y` rows. charm.down(y) ------------- Move the cursor down by `y` rows. charm.left(x) ------------- Move the cursor left by `x` columns. charm.right(x) -------------- Move the cursor right by `x` columns. charm.push(withAttributes=false) -------------------------------- Push the cursor state and optionally the attribute state. charm.pop(withAttributes=false) ------------------------------- Pop the cursor state and optionally the attribute state. charm.erase(s) -------------- Erase a region defined by the string `s`. `s` can be: * end - erase from the cursor to the end of the line * start - erase from the cursor to the start of the line * line - erase the current line * down - erase everything below the current line * up - erase everything above the current line * screen - erase the entire screen charm.delete(mode, n) --------------------- Delete `'line'` or `'char'`s. `delete` differs from erase because it does not write over the deleted characters with whitesapce, but instead removes the deleted space. `mode` can be `'line'` or `'char'`. `n` is the number of items to be deleted. `n` must be a positive integer. The cursor position is not updated. charm.insert(mode, n) --------------------- Insert space into the terminal. `insert` is the opposite of` delete`, and the arguments are the same. charm.display(attr) ------------------- Set the display mode with the string `attr`. `attr` can be: * reset * bright * dim * underscore * blink * reverse * hidden charm.foreground(color) ----------------------- Set the foreground color with the string `color`, which can be: * red * yellow * green * blue * cyan * magenta * black * white or `color` can be an integer from 0 to 255, inclusive. charm.background(color) ----------------------- Set the background color with the string `color`, which can be: * red * yellow * green * blue * cyan * magenta * black * white or `color` can be an integer from 0 to 255, inclusive. charm.cursor(visible) --------------------- Set the cursor visibility with a boolean `visible`. install ======= With [npm](http://npmjs.org) do: ``` npm install charm ``` node-charm-1.0.1/example/000077500000000000000000000000001271165776600152035ustar00rootroot00000000000000node-charm-1.0.1/example/256.js000066400000000000000000000005021271165776600160520ustar00rootroot00000000000000var charm = require('../')(process); function exit () { charm.display('reset'); process.exit(); } charm.on('^C', exit); var ix = 0; var iv = setInterval(function () { charm.background(ix++).write(' '); if (ix === 256) { clearInterval(iv); charm.write('\n'); exit(); } }, 10); node-charm-1.0.1/example/column.js000066400000000000000000000002421271165776600170340ustar00rootroot00000000000000var charm = require('../')(); charm.pipe(process.stdout); charm .column(16) .write('beep') .down() .column(32) .write('boop\n') .end() ; node-charm-1.0.1/example/cursor.js000066400000000000000000000010131271165776600170510ustar00rootroot00000000000000var charm = require('../')(process); charm.position(5, 10); charm.position(function (x, y) { console.dir([ x, y ]); charm.move(7,2); charm.push(); process.stdout.write('lul'); charm.left(3).up(1).foreground('magenta'); process.stdout.write('v'); charm.left(1).up(1).display('reset'); process.stdout.write('|'); charm.down(3); charm.pop().background('blue'); process.stdout.write('popped\npow'); charm.display('reset').erase('line'); charm.destroy(); }); node-charm-1.0.1/example/http_spin.js000066400000000000000000000017321271165776600175540ustar00rootroot00000000000000var http = require('http'); var charmer = require('../'); http.createServer(function (req, res) { res.setHeader('content-type', 'text/ansi'); var charm = charmer(); charm.pipe(res); charm.reset(); var radius = 10; var theta = 0; var points = []; var iv = setInterval(function () { var x = 2 + (radius + Math.cos(theta) * radius) * 2; var y = 2 + radius + Math.sin(theta) * radius; points.unshift([ x, y ]); var colors = [ 'red', 'yellow', 'green', 'cyan', 'blue', 'magenta' ]; points.forEach(function (p, i) { charm.position(p[0], p[1]); var c = colors[Math.floor(i / 12)]; charm.background(c).write(' ') }); points = points.slice(0, 12 * colors.length - 1); theta += Math.PI / 40; }, 50); req.connection.on('end', function () { clearInterval(iv); charm.end(); }); }).listen(8081); node-charm-1.0.1/example/lucky.js000066400000000000000000000011421271165776600166660ustar00rootroot00000000000000var charm = require('../')(); charm.pipe(process.stdout); charm.reset(); var colors = [ 'red', 'cyan', 'yellow', 'green', 'blue' ]; var text = 'Always after me lucky charms.'; var offset = 0; var iv = setInterval(function () { var y = 0, dy = 1; for (var i = 0; i < 40; i++) { var color = colors[(i + offset) % colors.length]; var c = text[(i + offset) % text.length]; charm .move(1, dy) .foreground(color) .write(c) ; y += dy; if (y <= 0 || y >= 5) dy *= -1; } charm.position(0, 1); offset ++; }, 150); node-charm-1.0.1/example/position.js000066400000000000000000000002171271165776600174050ustar00rootroot00000000000000var charm = require('charm')(process); charm.on('^C', process.exit); charm.position(function (x, y) { console.log('(%d, %d)', x, y); }); node-charm-1.0.1/example/progress.js000066400000000000000000000005231271165776600174050ustar00rootroot00000000000000var charm = require('../')(); charm.pipe(process.stdout); charm.write('Progress: 0 %'); var i = 0; var iv = setInterval(function () { charm.left(i.toString().length + 2); i ++; charm.write(i + ' %'); if (i === 100) { charm.end('\nDone!\n'); clearInterval(iv); } }, 25); charm.on('^C',process.exit); node-charm-1.0.1/example/spin.js000066400000000000000000000011521271165776600165110ustar00rootroot00000000000000var charm = require('../')(process); charm.reset(); var radius = 10; var theta = 0; var points = []; var iv = setInterval(function () { var x = 2 + (radius + Math.cos(theta) * radius) * 2; var y = 2 + radius + Math.sin(theta) * radius; points.unshift([ x, y ]); var colors = [ 'red', 'yellow', 'green', 'cyan', 'blue', 'magenta' ]; points.forEach(function (p, i) { charm.position(p[0], p[1]); var c = colors[Math.floor(i / 12)]; charm.background(c).write(' ') }); points = points.slice(0, 12 * colors.length - 1); theta += Math.PI / 40; }, 50); node-charm-1.0.1/index.js000066400000000000000000000203041271165776600152140ustar00rootroot00000000000000var tty = require('tty'); var encode = require('./lib/encode'); var Stream = require('stream').Stream; var inherits = require('inherits') inherits(Charm, Stream) var exports = module.exports = function () { var input = null; function setInput (s) { if (input) throw new Error('multiple inputs specified') else input = s } var output = null; function setOutput (s) { if (output) throw new Error('multiple outputs specified') else output = s } for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!arg) continue; if (arg.readable) setInput(arg) else if (arg.stdin || arg.input) setInput(arg.stdin || arg.input) if (arg.writable) setOutput(arg) else if (arg.stdout || arg.output) setOutput(arg.stdout || arg.output) } if (input && typeof input.fd === 'number' && tty.isatty(input.fd)) { if (process.stdin.setRawMode) { process.stdin.setRawMode(true); } else tty.setRawMode(true); } var charm = new Charm; if (input) { input.pipe(charm); } if (output) { charm.pipe(output); } charm.once('^C', process.exit); charm.once('end', function () { if (input) { if (typeof input.fd === 'number' && tty.isatty(input.fd)) { if (process.stdin.setRawMode) { process.stdin.setRawMode(false); } else tty.setRawMode(false); } input.destroy(); } }); return charm; }; function Charm () { this.writable = true; this.readable = true; this.pending = []; } exports.Charm = Charm Charm.prototype.write = function (buf) { var self = this; if (self.pending.length) { var codes = extractCodes(buf); var matched = false; for (var i = 0; i < codes.length; i++) { for (var j = 0; j < self.pending.length; j++) { var cb = self.pending[j]; if (cb(codes[i])) { matched = true; self.pending.splice(j, 1); break; } } } if (matched) return; } if (buf.length === 1) { if (buf[0] === 3) self.emit('^C'); if (buf[0] === 4) self.emit('^D'); } self.emit('data', buf); return self; }; Charm.prototype.destroy = function () { this.end(); }; Charm.prototype.end = function (buf) { if (buf) this.write(buf); this.emit('end'); }; Charm.prototype.reset = function (cb) { // resets the screen on iTerm, which appears // to lack support for the reset character. this.write(encode('[0m')); this.write(encode('[2J')); this.write(encode('c')); return this; }; Charm.prototype.position = function (x, y) { // get/set absolute coordinates if (typeof x === 'function') { var cb = x; this.pending.push(function (buf) { if (buf[0] === 27 && buf[1] === encode.ord('[') && buf[buf.length-1] === encode.ord('R')) { var pos = buf.toString() .slice(2,-1) .split(';') .map(Number) ; cb(pos[1], pos[0]); return true; } }); this.write(encode('[6n')); } else { this.write(encode( '[' + Math.floor(y) + ';' + Math.floor(x) + 'f' )); } return this; }; Charm.prototype.move = function (x, y) { // set relative coordinates var bufs = []; if (y < 0) this.up(-y) else if (y > 0) this.down(y) if (x > 0) this.right(x) else if (x < 0) this.left(-x) return this; }; Charm.prototype.up = function (y) { if (y === undefined) y = 1; this.write(encode('[' + Math.floor(y) + 'A')); return this; }; Charm.prototype.down = function (y) { if (y === undefined) y = 1; this.write(encode('[' + Math.floor(y) + 'B')); return this; }; Charm.prototype.right = function (x) { if (x === undefined) x = 1; this.write(encode('[' + Math.floor(x) + 'C')); return this; }; Charm.prototype.left = function (x) { if (x === undefined) x = 1; this.write(encode('[' + Math.floor(x) + 'D')); return this; }; Charm.prototype.column = function (x) { this.write(encode('[' + Math.floor(x) + 'G')); return this; }; Charm.prototype.push = function (withAttributes) { this.write(encode(withAttributes ? '7' : '[s')); return this; }; Charm.prototype.pop = function (withAttributes) { this.write(encode(withAttributes ? '8' : '[u')); return this; }; Charm.prototype.erase = function (s) { if (s === 'end' || s === '$') { this.write(encode('[K')); } else if (s === 'start' || s === '^') { this.write(encode('[1K')); } else if (s === 'line') { this.write(encode('[2K')); } else if (s === 'down') { this.write(encode('[J')); } else if (s === 'up') { this.write(encode('[1J')); } else if (s === 'screen') { this.write(encode('[1J')); } else { this.emit('error', new Error('Unknown erase type: ' + s)); } return this; }; Charm.prototype.delete = function (s, n) { n = n || 1 if (s === 'line') { this.write(encode('[' + n + 'M')); } else if (s === 'char') { this.write(encode('[' + n + 'M')); } else { this.emit('error', new Error('Unknown delete type: ' + s)); } return this; }; Charm.prototype.insert = function (mode, n) { n = n || 1 if(mode === true) { this.write(encode('[4h')); } else if (mode === false) { this.write(encode('[l')); } else if (mode === 'line') { this.write(encode('[' + n + 'L')); } else if (mode === 'char') { this.write(encode('[' + n + '@')); } else { this.emit('error', new Error('Unknown delete type: ' + s)); } return this; }; Charm.prototype.display = function (attr) { var c = { reset : 0, bright : 1, dim : 2, underscore : 4, blink : 5, reverse : 7, hidden : 8 }[attr]; if (c === undefined) { this.emit('error', new Error('Unknown attribute: ' + attr)); } this.write(encode('[' + c + 'm')); return this; }; Charm.prototype.foreground = function (color) { if (typeof color === 'number') { if (color < 0 || color >= 256) { this.emit('error', new Error('Color out of range: ' + color)); } this.write(encode('[38;5;' + color + 'm')); } else { var c = { black : 30, red : 31, green : 32, yellow : 33, blue : 34, magenta : 35, cyan : 36, white : 37 }[color.toLowerCase()]; if (!c) this.emit('error', new Error('Unknown color: ' + color)); this.write(encode('[' + c + 'm')); } return this; }; Charm.prototype.background = function (color) { if (typeof color === 'number') { if (color < 0 || color >= 256) { this.emit('error', new Error('Color out of range: ' + color)); } this.write(encode('[48;5;' + color + 'm')); } else { var c = { black : 40, red : 41, green : 42, yellow : 43, blue : 44, magenta : 45, cyan : 46, white : 47 }[color.toLowerCase()]; if (!c) this.emit('error', new Error('Unknown color: ' + color)); this.write(encode('[' + c + 'm')); } return this; }; Charm.prototype.cursor = function (visible) { this.write(encode(visible ? '[?25h' : '[?25l')); return this; }; var extractCodes = exports.extractCodes = function (buf) { var codes = []; var start = -1; for (var i = 0; i < buf.length; i++) { if (buf[i] === 27) { if (start >= 0) codes.push(buf.slice(start, i)); start = i; } else if (start >= 0 && i === buf.length - 1) { codes.push(buf.slice(start)); } } return codes; } node-charm-1.0.1/lib/000077500000000000000000000000001271165776600143165ustar00rootroot00000000000000node-charm-1.0.1/lib/encode.js000066400000000000000000000007111271165776600161100ustar00rootroot00000000000000var encode = module.exports = function (xs) { function bytes (s) { if (typeof s === 'string') { return s.split('').map(ord); } else if (Array.isArray(s)) { return s.reduce(function (acc, c) { return acc.concat(bytes(c)); }, []); } } return new Buffer([ 0x1b ].concat(bytes(xs))); }; var ord = encode.ord = function ord (c) { return c.charCodeAt(0) }; node-charm-1.0.1/package.json000066400000000000000000000014451271165776600160420ustar00rootroot00000000000000{ "name" : "charm", "version" : "1.0.1", "description" : "ansi control sequences for terminal cursor hopping and colors", "main" : "index.js", "directories" : { "lib" : ".", "example" : "example", "test" : "test" }, "repository" : { "type" : "git", "url" : "http://github.com/substack/node-charm.git" }, "keywords" : [ "terminal", "ansi", "cursor", "color", "console", "control", "escape", "sequence" ], "dependencies": { "inherits": "^2.0.1" }, "author" : { "name" : "James Halliday", "email" : "mail@substack.net", "url" : "http://substack.net" }, "license" : "MIT/X11", "engine" : { "node" : ">=0.4" } }