This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
7.8 KiB
405 lines
7.8 KiB
|
|
/** |
|
* References: |
|
* |
|
* - http://en.wikipedia.org/wiki/ANSI_escape_code |
|
* - http://www.termsys.demon.co.uk/vtansi.htm |
|
* |
|
*/ |
|
|
|
/** |
|
* Module dependencies. |
|
*/ |
|
|
|
var emitNewlineEvents = require('./newlines') |
|
, prefix = '\x1b[' // For all escape codes |
|
, suffix = 'm' // Only for color codes |
|
|
|
/** |
|
* The ANSI escape sequences. |
|
*/ |
|
|
|
var codes = { |
|
up: 'A' |
|
, down: 'B' |
|
, forward: 'C' |
|
, back: 'D' |
|
, nextLine: 'E' |
|
, previousLine: 'F' |
|
, horizontalAbsolute: 'G' |
|
, eraseData: 'J' |
|
, eraseLine: 'K' |
|
, scrollUp: 'S' |
|
, scrollDown: 'T' |
|
, savePosition: 's' |
|
, restorePosition: 'u' |
|
, queryPosition: '6n' |
|
, hide: '?25l' |
|
, show: '?25h' |
|
} |
|
|
|
/** |
|
* Rendering ANSI codes. |
|
*/ |
|
|
|
var styles = { |
|
bold: 1 |
|
, italic: 3 |
|
, underline: 4 |
|
, inverse: 7 |
|
} |
|
|
|
/** |
|
* The negating ANSI code for the rendering modes. |
|
*/ |
|
|
|
var reset = { |
|
bold: 22 |
|
, italic: 23 |
|
, underline: 24 |
|
, inverse: 27 |
|
} |
|
|
|
/** |
|
* The standard, styleable ANSI colors. |
|
*/ |
|
|
|
var colors = { |
|
white: 37 |
|
, black: 30 |
|
, blue: 34 |
|
, cyan: 36 |
|
, green: 32 |
|
, magenta: 35 |
|
, red: 31 |
|
, yellow: 33 |
|
, grey: 90 |
|
, brightBlack: 90 |
|
, brightRed: 91 |
|
, brightGreen: 92 |
|
, brightYellow: 93 |
|
, brightBlue: 94 |
|
, brightMagenta: 95 |
|
, brightCyan: 96 |
|
, brightWhite: 97 |
|
} |
|
|
|
|
|
/** |
|
* Creates a Cursor instance based off the given `writable stream` instance. |
|
*/ |
|
|
|
function ansi (stream, options) { |
|
if (stream._ansicursor) { |
|
return stream._ansicursor |
|
} else { |
|
return stream._ansicursor = new Cursor(stream, options) |
|
} |
|
} |
|
module.exports = exports = ansi |
|
|
|
/** |
|
* The `Cursor` class. |
|
*/ |
|
|
|
function Cursor (stream, options) { |
|
if (!(this instanceof Cursor)) { |
|
return new Cursor(stream, options) |
|
} |
|
if (typeof stream != 'object' || typeof stream.write != 'function') { |
|
throw new Error('a valid Stream instance must be passed in') |
|
} |
|
|
|
// the stream to use |
|
this.stream = stream |
|
|
|
// when 'enabled' is false then all the functions are no-ops except for write() |
|
this.enabled = options && options.enabled |
|
if (typeof this.enabled === 'undefined') { |
|
this.enabled = stream.isTTY |
|
} |
|
this.enabled = !!this.enabled |
|
|
|
// then `buffering` is true, then `write()` calls are buffered in |
|
// memory until `flush()` is invoked |
|
this.buffering = !!(options && options.buffering) |
|
this._buffer = [] |
|
|
|
// controls the foreground and background colors |
|
this.fg = this.foreground = new Colorer(this, 0) |
|
this.bg = this.background = new Colorer(this, 10) |
|
|
|
// defaults |
|
this.Bold = false |
|
this.Italic = false |
|
this.Underline = false |
|
this.Inverse = false |
|
|
|
// keep track of the number of "newlines" that get encountered |
|
this.newlines = 0 |
|
emitNewlineEvents(stream) |
|
stream.on('newline', function () { |
|
this.newlines++ |
|
}.bind(this)) |
|
} |
|
exports.Cursor = Cursor |
|
|
|
/** |
|
* Helper function that calls `write()` on the underlying Stream. |
|
* Returns `this` instead of the write() return value to keep |
|
* the chaining going. |
|
*/ |
|
|
|
Cursor.prototype.write = function (data) { |
|
if (this.buffering) { |
|
this._buffer.push(arguments) |
|
} else { |
|
this.stream.write.apply(this.stream, arguments) |
|
} |
|
return this |
|
} |
|
|
|
/** |
|
* Buffer `write()` calls into memory. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Cursor.prototype.buffer = function () { |
|
this.buffering = true |
|
return this |
|
} |
|
|
|
/** |
|
* Write out the in-memory buffer. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Cursor.prototype.flush = function () { |
|
this.buffering = false |
|
var str = this._buffer.map(function (args) { |
|
if (args.length != 1) throw new Error('unexpected args length! ' + args.length); |
|
return args[0]; |
|
}).join(''); |
|
this._buffer.splice(0); // empty |
|
this.write(str); |
|
return this |
|
} |
|
|
|
|
|
/** |
|
* The `Colorer` class manages both the background and foreground colors. |
|
*/ |
|
|
|
function Colorer (cursor, base) { |
|
this.current = null |
|
this.cursor = cursor |
|
this.base = base |
|
} |
|
exports.Colorer = Colorer |
|
|
|
/** |
|
* Write an ANSI color code, ensuring that the same code doesn't get rewritten. |
|
*/ |
|
|
|
Colorer.prototype._setColorCode = function setColorCode (code) { |
|
var c = String(code) |
|
if (this.current === c) return |
|
this.cursor.enabled && this.cursor.write(prefix + c + suffix) |
|
this.current = c |
|
return this |
|
} |
|
|
|
|
|
/** |
|
* Set up the positional ANSI codes. |
|
*/ |
|
|
|
Object.keys(codes).forEach(function (name) { |
|
var code = String(codes[name]) |
|
Cursor.prototype[name] = function () { |
|
var c = code |
|
if (arguments.length > 0) { |
|
c = toArray(arguments).map(Math.round).join(';') + code |
|
} |
|
this.enabled && this.write(prefix + c) |
|
return this |
|
} |
|
}) |
|
|
|
/** |
|
* Set up the functions for the rendering ANSI codes. |
|
*/ |
|
|
|
Object.keys(styles).forEach(function (style) { |
|
var name = style[0].toUpperCase() + style.substring(1) |
|
, c = styles[style] |
|
, r = reset[style] |
|
|
|
Cursor.prototype[style] = function () { |
|
if (this[name]) return this |
|
this.enabled && this.write(prefix + c + suffix) |
|
this[name] = true |
|
return this |
|
} |
|
|
|
Cursor.prototype['reset' + name] = function () { |
|
if (!this[name]) return this |
|
this.enabled && this.write(prefix + r + suffix) |
|
this[name] = false |
|
return this |
|
} |
|
}) |
|
|
|
/** |
|
* Setup the functions for the standard colors. |
|
*/ |
|
|
|
Object.keys(colors).forEach(function (color) { |
|
var code = colors[color] |
|
|
|
Colorer.prototype[color] = function () { |
|
this._setColorCode(this.base + code) |
|
return this.cursor |
|
} |
|
|
|
Cursor.prototype[color] = function () { |
|
return this.foreground[color]() |
|
} |
|
}) |
|
|
|
/** |
|
* Makes a beep sound! |
|
*/ |
|
|
|
Cursor.prototype.beep = function () { |
|
this.enabled && this.write('\x07') |
|
return this |
|
} |
|
|
|
/** |
|
* Moves cursor to specific position |
|
*/ |
|
|
|
Cursor.prototype.goto = function (x, y) { |
|
x = x | 0 |
|
y = y | 0 |
|
this.enabled && this.write(prefix + y + ';' + x + 'H') |
|
return this |
|
} |
|
|
|
/** |
|
* Resets the color. |
|
*/ |
|
|
|
Colorer.prototype.reset = function () { |
|
this._setColorCode(this.base + 39) |
|
return this.cursor |
|
} |
|
|
|
/** |
|
* Resets all ANSI formatting on the stream. |
|
*/ |
|
|
|
Cursor.prototype.reset = function () { |
|
this.enabled && this.write(prefix + '0' + suffix) |
|
this.Bold = false |
|
this.Italic = false |
|
this.Underline = false |
|
this.Inverse = false |
|
this.foreground.current = null |
|
this.background.current = null |
|
return this |
|
} |
|
|
|
/** |
|
* Sets the foreground color with the given RGB values. |
|
* The closest match out of the 216 colors is picked. |
|
*/ |
|
|
|
Colorer.prototype.rgb = function (r, g, b) { |
|
var base = this.base + 38 |
|
, code = rgb(r, g, b) |
|
this._setColorCode(base + ';5;' + code) |
|
return this.cursor |
|
} |
|
|
|
/** |
|
* Same as `cursor.fg.rgb(r, g, b)`. |
|
*/ |
|
|
|
Cursor.prototype.rgb = function (r, g, b) { |
|
return this.foreground.rgb(r, g, b) |
|
} |
|
|
|
/** |
|
* Accepts CSS color codes for use with ANSI escape codes. |
|
* For example: `#FF000` would be bright red. |
|
*/ |
|
|
|
Colorer.prototype.hex = function (color) { |
|
return this.rgb.apply(this, hex(color)) |
|
} |
|
|
|
/** |
|
* Same as `cursor.fg.hex(color)`. |
|
*/ |
|
|
|
Cursor.prototype.hex = function (color) { |
|
return this.foreground.hex(color) |
|
} |
|
|
|
|
|
// UTIL FUNCTIONS // |
|
|
|
/** |
|
* Translates a 255 RGB value to a 0-5 ANSI RGV value, |
|
* then returns the single ANSI color code to use. |
|
*/ |
|
|
|
function rgb (r, g, b) { |
|
var red = r / 255 * 5 |
|
, green = g / 255 * 5 |
|
, blue = b / 255 * 5 |
|
return rgb5(red, green, blue) |
|
} |
|
|
|
/** |
|
* Turns rgb 0-5 values into a single ANSI color code to use. |
|
*/ |
|
|
|
function rgb5 (r, g, b) { |
|
var red = Math.round(r) |
|
, green = Math.round(g) |
|
, blue = Math.round(b) |
|
return 16 + (red*36) + (green*6) + blue |
|
} |
|
|
|
/** |
|
* Accepts a hex CSS color code string (# is optional) and |
|
* translates it into an Array of 3 RGB 0-255 values, which |
|
* can then be used with rgb(). |
|
*/ |
|
|
|
function hex (color) { |
|
var c = color[0] === '#' ? color.substring(1) : color |
|
, r = c.substring(0, 2) |
|
, g = c.substring(2, 4) |
|
, b = c.substring(4, 6) |
|
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)] |
|
} |
|
|
|
/** |
|
* Turns an array-like object into a real array. |
|
*/ |
|
|
|
function toArray (a) { |
|
var i = 0 |
|
, l = a.length |
|
, rtn = [] |
|
for (; i<l; i++) { |
|
rtn.push(a[i]) |
|
} |
|
return rtn |
|
}
|
|
|