Permalink
Browse files

- Add fileReader and filesReader API for automatic file input with sm…

…art type coercion.

- Add file(s) input to built-in cat / grep
- Use monospace fonts for plain text
- preliminary hex support
- preliminary ansi support
- separate unix legacy output filters from binary escaping
- Add more mime types
- Add more inline documentation
- More testsa
  • Loading branch information...
1 parent 5fa6922 commit 6ee14bcdaee9c823fcfee525233153497f7c4a13 @unconed committed Jun 13, 2011
View
BIN HTML/Images/folder.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
40 HTML/outputview/outputfactory.js
@@ -358,7 +358,6 @@ widgets.code = function (properties) {
'text/x-sql': 'sql',
'text/xml': 'xml',
'text/html': 'text',
- 'text/plain': 'text',
};
this.brush = brushes[properties.language];
@@ -378,11 +377,44 @@ widgets.code.prototype = $.extend(new widgets.text(), {
updateElement: function () {
this.$contents.html('<pre></pre>');
this.$pre = this.$contents.find('pre');
-
this.$pre.text(this.properties.contents);
- this.$pre.attr('class', 'brush: ' + this.brush);
- SyntaxHighlighter.highlight({}, this.$pre[0]);
+ if (this.brush) {
+ this.$pre.attr('class', 'brush: ' + this.brush);
+
+ SyntaxHighlighter.highlight({}, this.$pre[0]);
+ }
+
+ this.$element.data('controller', this);
+ },
+
+});
+
+/**
+ * Widget: Hex output
+ */
+widgets.hex = function (properties) {
+
+ // Initialize node.
+ ov.outputNode.call(this, properties);
+
+ this.$contents = this.$element.find('.contents');
+ this.$pre = this.$contents.find('pre');
+
+ this.updateElement();
+};
+
+widgets.hex.prototype = $.extend(new widgets.text(), {
+
+ // Return active markup for this widget.
+ $markup: function () {
+ var $outputNode = $('<div class="termkitOutputNode widgetHex"></div>').data('controller', this);
+ var that = this;
+ return $outputNode;
+ },
+
+ // Update markup to match.
+ updateElement: function () {
this.$element.data('controller', this);
},
View
109 HTML/termkit.css
@@ -57,6 +57,115 @@ body {
word-wrap: break-word;
}
+.termkitOutputView .widgetCode pre {
+
+}
+
+.termkitAnsi30 {
+ color: #000;
+}
+.termkitAnsi31 {
+ color: #ba3615;
+}
+.termkitAnsi32 {
+ color: #65970b;
+}
+.termkitAnsi33 {
+ color: #ba7215;
+}
+.termkitAnsi34 {
+ color: #4362d1;
+}
+.termkitAnsi35 {
+ color: #a64e80;
+}
+.termkitAnsi36 {
+ color: #419197;
+}
+.termkitAnsi37 {
+ color: #8c805f;
+}
+.termkitAnsi30b {
+ color: #524b37;
+ font-weight: bolder;
+}
+.termkitAnsi31b {
+ color: #ff623b;
+ font-weight: bolder;
+}
+.termkitAnsi32b {
+ color: #c8f82d;
+ font-weight: bolder;
+}
+.termkitAnsi33b {
+ color: #fddc35;
+ font-weight: bolder;
+}
+.termkitAnsi34b {
+ color: #97c4ff;
+ font-weight: bolder;
+}
+.termkitAnsi35b {
+ color: #fa8fc6;
+ font-weight: bolder;
+}
+.termkitAnsi36b {
+ color: #adffe9;
+ font-weight: bolder;
+}
+.termkitAnsi37b {
+ color: #f3ebe2;
+ font-weight: bolder;
+}
+.termkitAnsi40 {
+ background-color: #000;
+}
+.termkitAnsi41 {
+ background-color: #ba3615;
+}
+.termkitAnsi42 {
+ background-color: #65970b;
+}
+.termkitAnsi43 {
+ background-color: #ba7215;
+}
+.termkitAnsi44 {
+ background-color: #4362d1;
+}
+.termkitAnsi45 {
+ background-color: #a64e80;
+}
+.termkitAnsi46 {
+ background-color: #419197;
+}
+.termkitAnsi47 {
+ background-color: #8c805f;
+}
+.termkitAnsi40b {
+ background-color: #524b37;
+}
+.termkitAnsi41b {
+ background-color: #ff623b;
+}
+.termkitAnsi42b {
+ background-color: #c8f82d;
+}
+.termkitAnsi43b {
+ background-color: #fddc35;
+}
+.termkitAnsi44b {
+ background-color: #97c4ff;
+}
+.termkitAnsi45b {
+ background-color: #fa8fc6;
+}
+.termkitAnsi46b {
+ background-color: #adffe9;
+}
+.termkitAnsi47b {
+ background-color: #f3ebe2;
+}
+
.termkitOutputView .widgetCode div.syntaxhighlighter > div.toolbar {
display: none;
}
View
2 Node-API.md
@@ -102,7 +102,7 @@ To handle headers on dataIn, you can use the built-in `reader.js`. To use it, cr
var handler = { ... };
// Attach reader to dataIn pipe.
-var pipe = new reader.reader(pipes.dataIn,
+var pipe = new reader.dataReader(pipes.dataIn,
function (headers) {
// Inspect headers, return appropriate handler
return handler;
View
69 Node/misc.js
@@ -219,27 +219,10 @@ exports.objectKeys = function (object) {
* Escape binary data for display as HTML.
*/
exports.escapeBinary = function (data) {
- var binary = data.toString('utf-8'), n = binary.length, i = 0;
-
- // Escape HTML characters.
- binary = binary.replace(/[<&]/g, function (x) {
- return { '<': '&lt;', '&': '&amp;' };
- });
-
- // Handle antique italic escapes used by grotty -c.
- binary = binary.replace(/_\u0008(.)\u0008\1/g, function (x, char) {
- return '<b><i>' + char + '</i></b>';
- });
-
- // Handle antique bold escapes used by grotty -c.
- binary = binary.replace(/(.)\u0008\1/g, function (x, char) {
- return '<b>' + char + '</b>';
- });
-
- // Handle antique italic escapes used by grotty -c.
- binary = binary.replace(/_\u0008(.)/g, function (x, char) {
- return '<i>' + char + '</i>';
- });
+ if (typeof data != 'string') {
+ data = data.toString('utf-8');
+ }
+ var binary = data, n = binary.length, i = 0;
// Escape non-printables
binary = binary.replace(/([\u0000-\u001F\u0080-\u009F])/g, function (x, char) {
@@ -253,3 +236,47 @@ exports.escapeBinary = function (data) {
return binary;
}
+
+/**
+ * Escape textual Unix data for display as HTML.
+ */
+exports.escapeUnixText = function (data) {
+ if (typeof data != 'string') {
+ data = data.toString('utf-8');
+ }
+ var binary = data, n = binary.length, i = 0;
+
+ // Escape HTML characters.
+ binary = binary.replace(/[<&]/g, function (x) {
+ return { '<': '&lt;', '&': '&amp;' }[x];
+ });
+
+ // Handle ANSI color escapes.
+ var bold = false,
+ italic = false,
+ underline = false,
+ blink = false,
+ strike = false,
+ binary = binary.replace(/\u001b([0-9]+(;[0-9]+)*)?m/g, function (x, codes) {
+ codes = codes.split(';');
+ for (i in codes) {
+ switch (codes[i]) {
+
+ }
+ }
+ });
+
+ // Handle antique bold/italic escapes used by grotty -c.
+ binary = binary
+ .replace(/_\u0008(.)\u0008\1/g, function (x, char) {
+ return '<b><i>' + char + '</i></b>';
+ })
+ .replace(/(.)\u0008\1/g, function (x, char) {
+ return '<b>' + char + '</b>';
+ })
+ .replace(/_\u0008(.)/g, function (x, char) {
+ return '<i>' + char + '</i>';
+ });
+
+ return exports.escapeBinary(binary);
+}
View
1 Node/shell/builtin/builtin.js
@@ -5,6 +5,7 @@ exports.commands = {
echo: true,
get: true,
grep: true,
+ hex: true,
ls: true,
pwd: true,
};
View
136 Node/shell/builtin/cat.js
@@ -2,85 +2,83 @@ var fs = require('fs'),
view = require('view/view'),
whenDone = require('misc').whenDone,
meta = require('shell/meta'),
- expandPath = require('misc').expandPath;
-
+ reader = require('shell/reader');
+
exports.main = function (tokens, pipes, exit, environment) {
var out = new view.bridge(pipes.viewOut);
- var chunkSize = 16384;
// "cat <file> [file ...]" syntax.
- if (tokens.length < 2) {
+ tokens.shift();
+ if (tokens.length < 1) {
out.print('Usage: cat <file> [file] ...');
return exit(false);
}
- if (tokens.length > 2) {
- out.print('Multiple input files not supported yet.');
- return exit(false);
- }
-
- var errors = 0,
- track = whenDone(function () {
- exit(errors == 0);
- });
-
- for (i in tokens) if (i > 0) (function (file) {
- expandPath(file, track(function (file) {
- fs.stat(file, track(function (err, stats) {
- if (err) {
- errors++;
- out.print("No such file (" + file + ")");
- return;
- }
- fs.open(file, 'r', track(function (err, fd) {
- if (err) {
- errors++;
- out.print("Unable to open file (" + file + ")");
- return;
- }
-
- // See if we need a progress bar.
- var progress = stats.size > 1024 * 1024; // yes, this is arbitrary
- var position = 0;
-
- if (progress) {
- out.add(null, view.progress('progress', 0, 0, stats.size));
- }
-
- (function read() {
- var buffer = new Buffer(chunkSize);
- fs.read(fd, buffer, 0, chunkSize, position, track(function (err, bytesRead) {
- if (err) {
- errors++;
- out.print("Error reading file (" + file + ")");
- return;
- }
-
- var slice = buffer.slice(0, bytesRead);
- if (position == 0) {
- var headers = new meta.headers(),
- keys = file.split('/');
- headers.set({
- 'Content-Type': meta.sniff(file, slice),
- 'Content-Length': stats.size,
- 'Content-Disposition': [ 'attachment', { 'filename': keys.pop() } ],
- });
+ var files = tokens;
+
+ // Reader handler
+ var progress, position;
+ var handler = {
+ /**
+ * Files have been opened, unified headers available.
+ */
+ begin: function (headers) {
+
+ // See if we need a progress bar.
+ var size = headers.get('Content-Length');
+ progress = size > 1024 * 1024; // yes, this is arbitrary
+ if (progress) {
+ position = 0;
+ out.print(view.progress('progress', 0, 0, size));
+ }
+
+ // Forward headers.
+ pipes.dataOut.write(headers.generate());
+ process.stderr.write(headers.generate());
+ // Unbuffered operation.
+ return false;
+ },
- pipes.dataOut.write(headers.generate());
- }
+ /**
+ * Data coming in.
+ */
+ data: function (data) {
+ // Pipe through.
+ pipes.dataOut.write(data);
- pipes.dataOut.write(slice);
- position += bytesRead;
+ // Progress bar.
+ if (progress) {
+ position += data.length;
+ out.update('progress', { value: position });
+ }
+ },
+
+ /**
+ * Done reading.
+ */
+ end: function (exit) {
+ exit();
+ },
+ };
- if (position < stats.size) {
- read();
- }
+ // Reader callbacks
+ var errors = 0;
+ // Open
+ function readerOpen() {
+ return handler;
+ };
+ // Close
+ function readerClose() {
+ exit(errors == 0);
+ };
+ // Error
+ function readerError(error) {
+ errors++;
+ out.print(error);
+ };
- progress && out.update('progress', { value: position });
- })); // fs.read
- })(); // read
- })); // fs.open
- })); // fs.stat
- })); // expandPath
- })(tokens[i]); // for i in tokens
+ // Spawn multiple files reader.
+ // Handles type coercion, file naming, etc.
+ var errors = 0,
+ pipe = new reader.filesReader(files, readerOpen, readerClose, readerError);
};
View
2 Node/shell/builtin/get.js
@@ -47,7 +47,7 @@ exports.main = function (tokens, pipes, exit, environment) {
if (length !== null) {
progress = length > 16 * 1024; // yes, this is arbitrary
if (progress) {
- out.add(null, view.progress('progress', 0, 0, length));
+ out.print(view.progress('progress', 0, 0, length));
}
}
View
253 Node/shell/builtin/grep.js
@@ -19,140 +19,151 @@ exports.main = function (tokens, pipes, exit, environment) {
var pattern = args.values.shift(),
files = args.values;
- if (files.length) {
- // TODO file grep
- out.print("File grep unsupported.");
- return exit(false);
- }
- else {
- var json, tail = '';
+ var json, tail = '';
+
+ // In-place grepper
+ function grep(object, value, force) {
+ var i;
- // In-place grepper
- function grep(object, value, force) {
- var i;
-
- if (object.constructor == String || object.constructor == Number) {
- // Match strings.
- return ((!matchKeys || force) && (negative ^ !!(''+object).match(value))) ? object : null;
+ if (object.constructor == String || object.constructor == Number) {
+ // Match strings.
+ return ((!matchKeys || force) && (negative ^ !!(''+object).match(value))) ? object : null;
+ }
+ if (object.constructor == Array) {
+ // Keep only non-null items after grep.
+ object = object.map(function (key) {
+ return grep(key, value);
+ }).filter(function (x) { return x !== null; });
+
+ // Prune empties.
+ return object.length ? object : null;
+ }
+
+ var out = {};
+ for (i in object) {
+ var item;
+ if (matchKeys) {
+ // Pass keys through grep.
+ item = (grep(i, value, true) !== null) ? object[i] : null
}
- if (object.constructor == Array) {
- // Keep only non-null items after grep.
- object = object.map(function (key) {
- return grep(key, value);
- }).filter(function (x) { return x !== null; });
-
- // Prune empties.
- return object.length ? object : null;
+ else {
+ // Pass values through grep.
+ item = grep(object[i], value);
}
- var out = {};
- for (i in object) {
- var item;
- if (matchKeys) {
- // Pass keys through grep.
- item = (grep(i, value, true) !== null) ? object[i] : null
- }
- else {
- // Pass values through grep.
- item = grep(object[i], value);
- }
-
- // Keep non-null items.
- if (item !== null) {
- out[i] = item;
- }
+ // Keep non-null items.
+ if (item !== null) {
+ out[i] = item;
}
+ }
- // Prune empties.
- if (JSON.stringify(out) == '{}') {
- return null;
- }
- return out;
+ // Prune empties.
+ if (JSON.stringify(out) == '{}') {
+ return null;
}
+ return out;
+ }
- // Buffered mime reader handler.
- var handler = {
+ // Buffered mime reader handler.
+ var handler = {
+
+ /**
+ * Pipe open, headers found.
+ */
+ begin: function (headers) {
+
+ var type = headers.get('Content-Type'),
+ buffered = false;
+
+ switch (type) {
+ default:
+ case 'text/plain':
+ json = false;
+ break;
+
+ case 'application/json':
+ buffered = json = true;
+ break;
+ }
+
+ // Remove content-length, output rest.
+ headers.set('Content-Length', null);
+ pipes.dataOut.write(headers.generate());
- /**
- * Pipe open, headers found.
- */
- begin: function (headers) {
-
- var type = headers.get('Content-Type');
- buffered = false;
-
- switch (type) {
- default:
- case 'text/plain':
- json = false;
- break;
-
- case 'application/json':
- buffered = json = true;
- break;
- }
+ return buffered;
+ },
- // Remove content-length, output rest.
- headers.set('Content-Length', null);
- pipes.dataOut.write(headers.generate());
-
- return buffered;
- },
-
- /**
- * Pipe data.
- */
- data: function (data) {
-
- if (json) {
- // Process whole json object (buffered).
- data = JSON.parse(data.toString('utf-8'));
- }
- else {
- // Process line by line (unbuffered chunks).
- data = (tail + data).toString('utf-8').split("\n");
- tail = data.pop();
- }
+ /**
+ * Pipe data.
+ */
+ data: function (data) {
- // Filter values recursively.
- data = grep(data, pattern);
-
- if (json) {
- // Serialize
- data = JSON.stringify(data);
- }
- else {
- // Join lines.
- data = data.join("\n");
- }
-
- // Pipe out.
- pipes.dataOut.write(data);
- },
+ if (json) {
+ // Process whole json object (buffered).
+ data = JSON.parse(data.toString('utf-8'));
+ }
+ else {
+ // Process line by line (unbuffered chunks).
+ data = (tail + data).toString('utf-8').split("\n");
+ tail = data.pop();
+ }
+
+ // Filter values recursively.
+ data = grep(data, pattern);
+
+ if (json) {
+ // Serialize
+ data = JSON.stringify(data);
+ }
+ else {
+ // Join lines.
+ data = data.join("\n");
+ }
- /**
- * Pipe closed.
- */
- end: function (exit) {
- if (tail) {
- // Last dangling line.
- data = grep([tail], pattern);
- if (data !== null) {
- pipes.dataOut.write(data);
- }
+ // Pipe out.
+ pipes.dataOut.write(data);
+ },
+
+ /**
+ * Pipe closed.
+ */
+ end: function (exit) {
+ if (tail) {
+ // Last dangling line.
+ data = grep([tail], pattern);
+ if (data !== null) {
+ pipes.dataOut.write(data);
}
+ }
- // Quit.
- exit();
- },
- };
-
- // Stdin grep.
- var pipe = new reader.reader(pipes.dataIn,
- function () { return handler; },
- function () {
- exit(true);
- });
-
+ // Quit.
+ exit();
+ },
+ };
+
+ // Reader callbacks
+ var errors = 0;
+ // Open
+ function readerOpen() {
+ return handler;
+ };
+ // Close
+ function readerClose() {
+ exit(errors == 0);
+ };
+ // Error
+ function readerError(error) {
+ errors++;
+ out.print(error);
+ };
+
+ // Spawn appropriate reader.
+ if (files.length) {
+ // Spawn files reader.
+ var pipe = new reader.filesReader(files, readerOpen, readerClose, readerError);
+ }
+ else {
+ // Spawn data reader.
+ var pipe = new reader.dataReader(pipes.dataIn, readerOpen, readerClose, readerError);
}
};
View
3 Node/shell/builtin/ls.js
@@ -31,8 +31,7 @@ exports.main = function (tokens, pipes, exit, environment) {
// Format data.
var data = JSON.stringify(output);
- process.stderr.write(data);
-
+
// Prepare headers.
var headers = new meta.headers();
headers.set({
View
2 Node/shell/command.js
@@ -287,7 +287,7 @@ exports.commandUnit.unixCommand.prototype.go = function () {
// Add MIME headers to raw output from process.
var headers = new meta.headers();
- headers.set('Content-Type', 'application/octet-stream');
+ headers.set('Content-Type', [ 'application/octet-stream', { schema: 'termkit.unix' } ]);
this.process.stdout.emit('data', headers.generate());
};
View
88 Node/shell/formatter.js
@@ -8,7 +8,8 @@ var fs = require('fs'),
composePath = require('misc').composePath,
objectKeys = require('misc').objectKeys,
reader = require('reader'),
- escapeBinary = require('misc').escapeBinary;
+ escapeBinary = require('misc').escapeBinary,
+ escapeUnixText = require('misc').escapeUnixText;
/**
* Error logger.
@@ -20,7 +21,7 @@ exports.logger = function (unit, viewOut) {
// Link up to stderr of process.
unit.process.stderr.on('data', function (data) {
var binary = escapeBinary(data);
- this.out.add(null, view.code(null, binary, 'text/plain'));
+ this.out.print(view.code(null, binary, 'text/plain'));
});
};
@@ -35,7 +36,7 @@ exports.formatter = function (tail, viewOut, exit) {
this.out = new view.bridge(viewOut);
// Start reading the output.
- this.reader = new reader.reader(tail.process.stdout, function (headers) {
+ this.reader = new reader.dataReader(tail.process.stdout, function (headers) {
// Construct appropriate plugin for this type.
return that.plugin = exports.factory(headers, that.out);
}, function () {
@@ -106,7 +107,7 @@ exports.plugins.fallback.supports = function (headers) {
exports.plugins.fallback.prototype = extend(new exports.plugin(), {
begin: function (headers) {
- this.out.add(null, view.code('output', headers.generate(), 'text/plain'));
+ this.out.print(view.code('output', headers.generate(), 'text/plain'));
},
});
@@ -121,8 +122,8 @@ exports.plugins.text = function (headers, out) {
exports.plugins.text.prototype = extend(new exports.plugin(), {
begin: function () {
-// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
- this.out.add(null, view.text('output'));
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.code('output', '', 'text/plain'));
},
data: function (data) {
@@ -147,8 +148,8 @@ exports.plugins.pdf = function (headers, out) {
exports.plugins.pdf.prototype = extend(new exports.plugin(), {
begin: function () {
-// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
- this.out.add(null, view.html('output'));
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.html('output'));
// Buffered.
return true;
@@ -181,8 +182,8 @@ exports.plugins.html = function (headers, out) {
exports.plugins.html.prototype = extend(new exports.plugin(), {
begin: function () {
-// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
- this.out.add(null, view.html('output'));
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.html('output'));
// Buffered.
return true;
@@ -215,8 +216,8 @@ exports.plugins.code = function (headers, out) {
exports.plugins.code.prototype = extend(new exports.plugins.text(), {
begin: function () {
-// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
- this.out.add(null, view.code('output', '', this.headers.get('Content-Type')));
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.code('output', '', this.headers.get('Content-Type')));
// Buffered output.
return true;
@@ -278,7 +279,7 @@ exports.plugins.image.prototype = extend(new exports.plugin(), {
data: function (data) {
var url = 'data:' + this.headers.get('Content-Type') + ';base64,' + data.toString('base64');
- this.out.add(null, view.image('image', url));
+ this.out.print(view.image('image', url));
},
});
@@ -376,6 +377,34 @@ exports.plugins.files.supports = function (headers) {
};
/**
+ * Unix text formatter.
+ */
+exports.plugins.unix = function (headers, out) {
+ // Inherit.
+ exports.plugin.apply(this, arguments);
+};
+
+exports.plugins.unix.prototype = extend(new exports.plugin(), {
+
+ begin: function () {
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.html('output', ''));
+ },
+
+ data: function (data) {
+ var binary = '<pre>'+ escapeUnixText(data) +'</pre>';
+ this.out.update('output', { contents: binary }, true);
+ },
+
+});
+
+exports.plugins.unix.supports = function (headers) {
+ var type = headers.get('Content-Type'),
+ schema = headers.get('Content-Type', 'schema');
+ return !!(/^application\/octet-stream$/(type) && (schema == 'termkit.unix')) * 3;
+}
+
+/**
* Binary formatter.
*/
exports.plugins.binary = function (headers, out) {
@@ -386,12 +415,12 @@ exports.plugins.binary = function (headers, out) {
exports.plugins.binary.prototype = extend(new exports.plugin(), {
begin: function () {
-// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
- this.out.add(null, view.html('output', ''));
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.code('output', '', 'text/plain'));
},
data: function (data) {
- var binary = '<pre>'+ escapeBinary(data) + '</pre>';
+ var binary = escapeBinary(data);
this.out.update('output', { contents: binary }, true);
},
@@ -402,3 +431,30 @@ exports.plugins.binary.supports = function (headers) {
return !!(/^application\/octet-stream/(type)) * 1;
}
+/**
+ * Hex formatter.
+ */
+exports.plugins.hex = function (headers, out) {
+ // Inherit.
+ exports.plugin.apply(this, arguments);
+};
+
+exports.plugins.hex.prototype = extend(new exports.plugin(), {
+
+ begin: function () {
+// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
+ this.out.print(view.hex('output', ''));
+ },
+
+ data: function (data) {
+ this.out.update('output', { contents: data.toString('base64') }, true);
+ },
+
+});
+
+exports.plugins.hex.supports = function (headers) {
+ var type = headers.get('Content-Type'),
+ schema = headers.get('Content-Type', 'schema');
+ return !!(/^application\/octet-stream$/(type) && (schema == 'termkit.hex')) * 3;
+}
+
View
123 Node/shell/meta.js
@@ -1,13 +1,16 @@
var mime = require('mime');
+// Is x an object?
function isObject(x) {
return typeof x == 'object';
}
+// Is x a string?
function isString(x) {
return (isObject(x) || typeof x == 'string') && x.constructor == ''.constructor;
}
+// Is x an array?
function isArray(x) {
return isObject(x) && x.constructor == [].constructor;
}
@@ -17,7 +20,7 @@ function join(string) {
return (''+string).replace(/[\r\n] ?/g, ' ');
}
-// Quoted string
+// Quote a string
function quote(string) {
if (/[\u0080-\uFFFF]/(string)) {
// TODO: RFC2047 mime encoded tokens.
@@ -28,7 +31,11 @@ function quote(string) {
return string;
}
-
+/**
+ * Container for header key/values + parameters.
+ *
+ * Can parse from and generate to MIME format.
+ */
exports.headers = function () {
this.fields = {};
this.params = {};
@@ -46,14 +53,23 @@ exports.headers = function () {
exports.headers.prototype = {
+ /**
+ * Set header parsing hint.
+ */
hint: function (key, args) {
this.hints[key] = args;
},
+ /**
+ * Return all values + parameters for a key.
+ */
all: function (key) {
return [ this.fields[key], this.params[key] ];
},
+ /**
+ * Get one value or parameter.
+ */
get: function (key, param) {
if (typeof this.fields[key] != 'undefined') {
if (param) {
@@ -63,28 +79,30 @@ exports.headers.prototype = {
}
},
+ /**
+ * Set one or more values and/or parameters.
+ *
+ * -- Single setters
+ * set(key, value)
+ * set(key, param, value)
+ *
+ * -- Combined value/param setter.
+ * set(key, [ value, { param: value, param: value } ])
+ *
+ * -- Multi-value setter.
+ * set(key, [ value, value, value ])
+ * set(key, [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ])
+ *
+ * -- Generic hash syntax.
+ * set({
+ * key: value,
+ * key: [ value, { param: value }],
+ * key: [ value, value, value ],
+ * key: [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ],
+ * })
+ */
set: function (keyObject, paramValue, value, raw) {
var i, j;
- /*
- // Single setters
- set(key, value)
- set(key, param, value)
-
- // Combined value/param setter.
- set(key, [ value, { param: value, param: value } ])
-
- // Multi-value setter.
- set(key, [ value, value, value ])
- set(key, [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ])
-
- // Generic hash syntax.
- set({
- key: value,
- key: [ value, { param: value }],
- key: [ value, value, value ],
- key: [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ],
- })
- */
// Raw set to parse individual value from other source.
if (raw) {
@@ -428,6 +446,67 @@ exports.sniff = function (file, data) {
return 'text/plain';
};
+/**
+ * Default type for type classes.
+ */
+exports.base = function (type) {
+ var map = {
+ 'application/javascript': 'text',
+ 'application/x-perl': 'text',
+ 'application/x-php': 'text',
+ };
+
+ if (map[type]) {
+ type = map[type];
+ }
+
+ return exports.default(type) || 'application/octet-stream';
+}
+
+/**
+ * Default type for type classes.
+ */
+exports.default = function (type) {
+ type = type.split('/')[0];
+ return {
+ 'text': 'text/plain',
+ }[type];
+};
+
+/**
+ * List of mime types with composable content.
+ */
+exports.composable = function () {
+ return {
+ 'application/javascript': true,
+ 'application/x-perl': true,
+ 'application/x-php': true,
+ 'text/css': true,
+ 'text/csv': true,
+ 'text/javascript': true,
+ 'text/html': true,
+ 'text/plain': true,
+ 'text/x-actionscript': true,
+ 'text/x-applescript': true,
+ 'text/x-c': true,
+ 'text/x-c++': true,
+ 'text/x-csharpsrc': true,
+ 'text/x-diff': true,
+ 'text/x-erlang': true,
+ 'text/x-groovy': true,
+ 'text/x-java-source': true,
+ 'text/x-python': true,
+ 'text/x-ruby': true,
+ 'text/x-sass': true,
+ 'text/x-scala': true,
+ 'text/x-shellscript': true,
+ 'text/x-sql': true,
+ };
+};
+
+/**
+ * Additional mime types for node-mime.
+ */
mime.define({
'text/x-applescript': ['applescript'],
'text/x-actionscript': ['actionscript'],
View
475 Node/shell/reader.js
@@ -5,27 +5,26 @@ var fs = require('fs'),
async = require('misc').async,
extend = require('misc').extend,
JSONPretty = require('misc').JSONPretty,
- composePath = require('misc').composePath;
-
+ composePath = require('misc').composePath,
+ expandPath = require('misc').expandPath;
-var debug;
-debug = process.stderr.write;
-debug = console.log
-
/**
- * Data reader for termkit-style stdI/O.
+ * Data reader for termkit-style dataIn.
*
* Waits for mime headers, parses them, then calls begin() to construct
* an appropriate stream/data handler.
*
+ * Supports buffered and unbuffered input.
+ *
* Calls exit when the pipe closes.
*/
-exports.reader = function (dataIn, begin, exit) {
+exports.dataReader = function (dataIn, begin, exit, error) {
var that = this;
this.dataIn = dataIn;
- this.begin = begin;
- this.exit = exit;
+ this.begin = begin || (function () {});
+ this.exit = exit || (function () {});
+ this.error = error || (function () {});
// Header sniffing.
this.identified = false;
@@ -50,48 +49,20 @@ exports.reader = function (dataIn, begin, exit) {
});
};
-exports.reader.prototype = {
-
- done: function () {
-
- // No data.
- if (!this.handler) {
- return this.exit();
- }
-
- // Send all buffered output to plug-in in one chunk.
- if (this.buffered) {
-
- if (!this.buffer) {
- this.buffer = new Buffer(this.length);
-
- // Join chunks.
- for (i in this.chunks) {
- var data = this.chunks[i];
-
- data.copy(this.buffer, this.offset, 0, data.length)
- this.offset += data.length;
- }
- }
-
- this.handler.data(this.buffer);
- }
-
- this.handler.end(this.exit);
-
- },
+exports.dataReader.prototype = {
+ /**
+ * Parse headers string into headers object.
+ */
parse: function (headers) {
this.headers = new meta.headers();
this.headers.parse(headers);
- for (i in this.headers.fields) {
-// debug('Header ' + i +': ' + this.headers.fields[i] +"\n");
- }
},
- // Parse MIME headers for stream
+ /**
+ * Read stream, find MIME headers.
+ */
data: function (data) {
-// debug('CHUNK ' + data + "\n\n");
if (!this.identified) {
// Swallow data until we encounter the MIME header delimiter.
this.lookahead += data.toString('ascii');
@@ -104,14 +75,13 @@ exports.reader.prototype = {
// Notify context of headers, receive handler.
this.identified = true;
this.handler = this.begin(this.headers);
- this.buffered = this.handler.begin(this.headers);
+ this.buffered = this.handler.begin && this.handler.begin(this.headers);
// See if size is known ahead of time.
var length = this.length = parseInt(this.headers.get('Content-Length'));
if (this.buffered && !isNaN(length)) {
// Allocate large buffer.
this.buffer = new Buffer(length);
- //debug('allocated ' + this.length+" got " + this.buffer.length + "\n");
}
else {
this.length = 0;
@@ -125,20 +95,15 @@ exports.reader.prototype = {
}
}
else {
- // Send output to plugin.
+ // Send output to handler.
if (this.buffered) {
// Collect output.
if (this.buffer) {
- //debug('buffered, known size ' + this.length+", data size " + data.length +"\n");
-
// Append chunk to buffer.
data.copy(this.buffer, this.offset, 0, data.length);
this.offset += data.length;
}
else {
-
- //debug('buffered, mystery size ' + this.length+", data size " + data.length +"\n");
-
// Size not known. Push chunk onto array to grow indefinitely.
this.chunks.push(data);
@@ -147,10 +112,410 @@ exports.reader.prototype = {
}
}
else {
- //debug('stream, packet size ' + data.length+"\n");
// Stream out data.
- this.handler.data(data);
+ this.handler.data && this.handler.data(data);
}
}
},
+
+ /**
+ * Finish reading.
+ */
+ done: function () {
+
+ // No data.
+ if (!this.handler) {
+ return this.exit();
+ }
+
+ // Send all buffered output to handler in one chunk.
+ if (this.buffered) {
+
+ if (!this.buffer) {
+ this.buffer = new Buffer(this.length);
+
+ // Join chunks.
+ for (i in this.chunks) {
+ var data = this.chunks[i];
+
+ data.copy(this.buffer, this.offset, 0, data.length)
+ this.offset += data.length;
+ }
+ }
+
+ this.handler.data && this.handler.data(this.buffer);
+ }
+
+ this.handler.end && this.handler.end(this.exit);
+
+ },
+
+};
+
+/**
+ * File reader for termkit-style dataIn.
+ *
+ * Generates mime headers, then calls begin() to construct
+ * an appropriate stream/data handler.
+ *
+ * Supports buffered and unbuffered input.
+ *
+ * Calls exit when the file has been read.
+ */
+exports.fileReader = function (file, begin, exit, error) {
+ var that = this;
+
+ this.file = file;
+ this.begin = begin || (function () {});
+ this.exit = exit || (function () {});
+ this.error = error || (function () {});
+
+ // Begin reading.
+ this.open();
+};
+
+exports.fileReader.prototype = {
+
+ /**
+ * Open file, determine type, begin reading.
+ */
+ open: function () {
+
+ var that = this,
+ chunkSize = 16384,
+ errors = 0,
+ track = whenDone(function () {
+ that.done();
+ });
+
+ expandPath(this.file, track(function (file) {
+ fs.stat(file, track(function (err, stats) {
+ if (err) {
+ errors++;
+ that.error("No such file (" + that.file + ")");
+ return;
+ }
+ fs.open(file, 'r', track(function (err, fd) {
+ if (err) {
+ errors++;
+ that.error("Unable to open file (" + that.file + ")");
+ return;
+ }
+
+ var position = 0;
+ (function read() {
+ var buffer = new Buffer(chunkSize);
+ fs.read(fd, buffer, 0, chunkSize, position, track(function (err, bytesRead) {
+ if (err) {
+ errors++;
+ that.error("Error reading file (" + that.file + ")");
+ return;
+ }
+
+ var slice = buffer.slice(0, bytesRead);
+
+ // Determine headers from first slice.
+ if (position == 0) {
+ var headers = new meta.headers(),
+ keys = file.split('/');
+ headers.set({
+ 'Content-Type': meta.sniff(file, slice),
+ 'Content-Length': stats.size,
+ 'Content-Disposition': [ 'attachment', { 'filename': keys.pop() } ],
+ });
+
+ // Get handler and begin processing.
+ that.handler = that.begin(headers);
+ that.buffered = that.handler.begin && that.handler.begin(headers);
+ }
+
+ // If buffered, read file and return.
+ if (that.buffered) {
+ that.buffer = new Buffer(stats.size);
+
+ fs.read(fd, that.buffer, 0, stats.size, 0, track(function (err, bytesRead) {
+ var slice = buffer.slice(0, bytesRead);
+ that.data(slice);
+ }));
+ return;
+ }
+
+ // Process slice.
+ that.data(slice);
+ position += bytesRead;
+
+ if (position < stats.size) {
+ read();
+ }
+ })); // fs.read
+ })(); // read
+ })); // fs.open
+ })); // fs.stat
+ })); // expandPath
+ },
+
+ /**
+ * Send data to handler.
+ */
+ data: function (data) {
+ this.handler.data && this.handler.data(data);
+ },
+
+ /**
+ * Finish reading.
+ */
+ done: function () {
+
+ // No data.
+ if (!this.handler) {
+ return this.exit();
+ }
+
+ this.handler.end && this.handler.end(this.exit);
+
+ },
+
+};
+
+
+/**
+ * Multiple files reader for termkit-style dataIn.
+ *
+ * Generates mime headers, then calls begin() to construct
+ * an appropriate stream/data handler.
+ *
+ * Supports buffered and unbuffered input.
+ *
+ * Calls exit when the file has been read.
+ */
+exports.filesReader = function (files, begin, exit, error) {
+ var that = this;
+
+ this.files = files;
+ this.begin = begin || (function () {});
+ this.exit = exit || (function () {});
+ this.error = error || (function () {});
+
+ this.offset = 0;
+ this.buffer = null;
+
+ this.open();
+};
+
+exports.filesReader.prototype = {
+
+ /**
+ * Open files, determine output type, begin reading.
+ */
+ open: function () {
+
+ var that = this,
+ chunkSize = 16384,
+ errors = 0,
+ size = 0,
+ types = [],
+ track = whenDone(function () {
+ // Done inspecting files.
+ if (errors > 0) {
+ that.done();
+ }
+ else {
+ that.process(that.files, types, size);
+ }
+ });
+
+ // Identify the content-type of all the files
+ for (var i in this.files) (function (file) {
+
+ fs.stat(file, track(function (err, stats) {
+ if (err) {
+ errors++;
+ that.error("No such file (" + file + ")");
+ return;
+ }
+
+ size += stats.size;
+
+ // Read beginning of file for sniffing.
+ fs.open(file, 'r', track(function (err, fd) {
+ if (err) {
+ errors++;
+ that.error("Unable to open file (" + file + ")");
+ return;
+ }
+
+ var buffer = new Buffer(chunkSize);
+ fs.read(fd, buffer, 0, chunkSize, 0, track(function (err, bytesRead) {
+ if (err) {
+ errors++;
+ that.error("Error reading file (" + file + ")");
+ return;
+ }
+
+ var slice = buffer.slice(0, bytesRead);
+
+ // Determine content-type from slice.
+ var type = meta.sniff(file, slice);
+ types.push(type);
+ })); // fs.read
+ })); // fs.open
+ })); // fs.stat
+
+ })(this.files[i]);
+
+ // Ensure tracker completes without files.
+ if (this.files.length == 0) {
+ that.done();
+ }
+ },
+
+ /**
+ * Determine common mime type for files.
+ */
+ type: function (types) {
+
+ // Single type.
+ if (types.length == 1) {
+ return types[0];
+ }
+
+ function commonPrefix(types) {
+ // Sort types, inspect first/last strings.
+ types.sort();
+ var first = types[0],
+ last = types[types.length - 1],
+ n = Math.min(first.length, last.length),
+ match = '';
+
+ // Find common prefix.
+ while (n > 0) {
+ last = last.substring(0, n--);
+ if (first.indexOf(last) == 0) {
+ match = last;
+ break;
+ }
+ }
+
+ return match;
+ }
+
+ prefix = commonPrefix(types);
+ if (!(/^[^\/]+\/[^\/]+$/(prefix))) {
+ // If we only matched a type category (e.g. text/),
+ // coerce types to their base.
+ types = types.map(function (type) {
+ return meta.base(type);
+ });
+ prefix = commonPrefix(types);
+
+ if (!(/^[^\/]+\/[^\/]+$/(prefix))) {
+ // Replace with generic type.
+ return meta.default(prefix) || 'application/octet-stream';
+ }
+
+ return prefix;
+ }
+ else {
+ // Only return a unified type for content types which can be composed safely.
+ var composable = meta.composable();
+ return composable[prefix] && prefix || 'application/octet-stream';
+ }
+ },
+
+ /**
+ * Begin reading files.
+ */
+ process: function (files, types, size) {
+ var that = this;
+
+ // Find common mime-type prefix.
+ var type = this.type(types);
+
+ // Build headers
+ var headers = new meta.headers();
+ headers.set('Content-Type', type);
+ headers.set('Content-Length', size);
+ if (files.length == 1) {
+ var keys = files[0].split('/');
+ headers.set('Content-Disposition', [ 'attachment', { 'filename': keys.pop() } ]);
+ }
+
+ // Get handler and begin processing.
+ this.handler = this.begin(headers);
+ this.buffered = this.handler.begin && this.handler.begin(headers);
+
+ // If buffered, create buffer of appropriate size.
+ if (this.buffered) {
+ this.buffer = new Buffer(size);
+ }
+
+ // File reader pass-through handler.
+ var handler = {
+ // Already determined mime type.
+ // Always use unbuffed filereader, so we only buffer once for all files.
+ begin: function () { return false; },
+ data: function (data) { that.data(data) },
+ end: function (exit) { exit(); },
+ };
+
+ // Helper: Process a single file.
+ function read(file, complete) {
+ var reader = new exports.fileReader(file, function (headers) {
+ return handler;
+ }, function () {
+ complete();
+ }, function (error) {
+ that.error(error);
+ });
+ }
+
+ // Process all files in sequence, asynchronously.
+ var files = files.slice();
+ (function nextFile() {
+ if (files.length) {
+ var file = files.shift();
+ read(file, nextFile);
+ }
+ else {
+ that.done();
+ }
+ })();
+
+ },
+
+ /**
+ * Send data to handler.
+ */
+ data: function (data) {
+
+ // Send output to plugin.
+ if (this.buffered) {
+ // Append chunk to buffer.
+ data.copy(this.buffer, this.offset, 0, data.length);
+ this.offset += data.length;
+ }
+ else {
+ // Stream out data.
+ this.handler.data && this.handler.data(data);
+ }
+ },
+
+ /**
+ * Finish reading.
+ */
+ done: function () {
+
+ // No data.
+ if (!this.handler) {
+ return this.exit();
+ }
+
+ // Send all buffered output to handler in one chunk.
+ if (this.buffered) {
+ this.handler.data && this.handler.data(this.buffer);
+ }
+
+ this.handler.end && this.handler.end(this.exit);
+ },
+
};
View
109 Node/test.js
@@ -16,6 +16,7 @@ var EventEmitter = require("events").EventEmitter;
var router = require("router");
var processor = require("shell/processor");
var meta = require("shell/meta");
+var reader = require("shell/reader");
var autocomplete = require("shell/autocomplete");
var misc = require("misc");
var grep = require("shell/builtin/grep");
@@ -143,10 +144,12 @@ function testCommands(assert) {
// { query: 5, method: 'shell.run', args: { tokens: [ 'pwd' ], ref: 7 } },
{ query: 5, method: 'shell.run', args: { tokens: [ 'cat', 'test.js' ], ref: 4 } },
], function (messages, success) {
+ /*
for (i in messages) {
console.log(messages[i]);
messages[i].args && messages[i].args.objects && console.log('Objects', messages[i].args.objects);
}
+ */
var last = messages[messages.length - 1];
assert(last.success && last.answer == 5, "Run single command");
});
@@ -285,9 +288,10 @@ function testAutocomplete(assert) {
});
auto.process(process.cwd(), [], [ 'c' ], 0, function (m) {
- assert(m && m.length == 2 &&
- m[0].label == 'cat' && m[0].type == 'command' &&
- m[1].label == 'cd' && m[1].type == 'command', "Autocomplete c command");
+ assert(m && m.length == 3 &&
+ m[0].label == 'cat' && m[0].type == 'command' &&
+ m[1].label == 'cd' && m[1].type == 'command' &&
+ m[2].label == 'clear' && m[2].type == 'command', "Autocomplete c command");
});
auto.process(process.cwd(), [], [ 'cat', 'test.j' ], 1, function (m) {
@@ -374,6 +378,11 @@ function testParseArgs(assert) {
assert(args.values.length == 1 && args.values[0] == 'foo bar', "Argument values");
assert(args.options.v && args.options.a && args.options.b && (args.options.magic == '7'),
"Argument options");
+
+ var args = misc.parseArgs([ 'grep', '-vab', 'foo bar', '--magic', '7' ]);
+ assert(args.values.length == 1 && args.values[0] == 'foo bar', "Argument values");
+ assert(args.options.v && args.options.a && args.options.b && (args.options.magic == '7'),
+ "Compact argument options");
}
@@ -386,6 +395,14 @@ function testGrep(assert) {
exit = function () {},
headers, content, pipes;
+ // Helper for converting strings to buffers.
+ function buffer(data) {
+ if (data.constructor != Buffer) {
+ data = new Buffer(data, 'utf8');
+ }
+ return data;
+ }
+
// Simple grep.
pipes = mockPipes();
handler([ 'grep', 'ba' ], pipes, exit);
@@ -402,8 +419,8 @@ function testGrep(assert) {
content = "foo\nbar\nbaz\nbingo\nccbacc\n\n\nfffuuu\n";
headers.set('Content-Type', 'text/plain');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
// Simple grep (negative).
@@ -422,8 +439,8 @@ function testGrep(assert) {
content = "foo\nbar\nbaz\nbingo\nccbacc\n\n\nfffuuu\n";
headers.set('Content-Type', 'text/plain');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
// JSON grep.
@@ -441,8 +458,8 @@ function testGrep(assert) {
content = '{"foo":"bar","baz":"bang","bingo":"fffuu"}';
headers.set('Content-Type', 'application/json');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
// JSON grep (negative).
@@ -460,8 +477,8 @@ function testGrep(assert) {
content = '{"foo":"bar","baz":"bang","bingo":"fffuu"}';
headers.set('Content-Type', 'application/json');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
// Complex JSON grep.
@@ -479,8 +496,8 @@ function testGrep(assert) {
content = '[ "---", "xXx", { "foo": "XX", "bar": "YY", "baz": "X" }, { "meh": "no" }, [ 1, "XX", 3, "XXX" ]]';
headers.set('Content-Type', 'application/json');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
// Complex JSON key grep.
@@ -498,8 +515,8 @@ function testGrep(assert) {
content = '[ "---", "xXx", { "foo": "XX", "bar": "YY", "baz": "X" }, { "mah": "no" }, [ {"foo":"bar"}, "XX", 3, "XXX" ]]';
headers.set('Content-Type', 'application/json');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
// Tricky object grep
@@ -509,7 +526,6 @@ function testGrep(assert) {
pipes.dataOut.on('data', function (data) {
data = data.toString('utf-8');
if (data.indexOf('\r\n\r\n') >= 0) return;
- console.log('tricky', data);
assert(data == '{"list":[{"lol":"lulz"}]}',
"Tricky object grep");
});
@@ -518,8 +534,8 @@ function testGrep(assert) {
content = '{"foo": "bar", "meh": "teh", "lol": "wai", "list": [ { "lol": "lulz", "fail": "json" } , "wtf" ] }';
headers.set('Content-Type', 'application/json');
headers.set('Content-Length', content.length);
- pipes.dataIn.emit('data', headers.generate());
- pipes.dataIn.emit('data', content);
+ pipes.dataIn.emit('data', buffer(headers.generate()));
+ pipes.dataIn.emit('data', buffer(content));
pipes.dataIn.emit('end');
}
@@ -531,10 +547,12 @@ function testPipe(assert) {
mockShell([
{ query: 8, method: 'shell.run', args: { tokens: [ [ 'ls' ], [ 'grep', '.js' ] ], ref: 7 } },
], function (messages, success) {
+ /*
for (i in messages) {
console.log(messages[i]);
messages[i].args && messages[i].args.objects && console.log('Objects', messages[i].args.objects);
}
+ */
var last = messages[messages.length - 1];
assert(last.success && last.answer == 8, "Run pipelined command");
});
@@ -549,6 +567,60 @@ function testBinary(assert) {
assert(test == "\\u0000\\u0001\\u0002\\u0003 xxx \r\n \u1fff \ufffd", "Binary escaping");
}
+/**
+ * Test filereader.
+ */
+function testFilereader(assert) {
+
+ var pipe;
+
+ // Test type coercion.
+
+ pipe = new reader.filesReader([ '../termkit.txt' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'text/plain', 'Type for txt');
+ } };
+ });
+
+ pipe = new reader.filesReader([ '../Mockups/Shot-0.2.png' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'image/png', 'Type for img');
+ } };
+ });
+
+ pipe = new reader.filesReader([ 'router.js' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'application/javascript', 'Type for js');
+ } };
+ });
+
+ pipe = new reader.filesReader([ 'test.js', 'router.js' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'application/javascript', 'Type for (js+js)');
+ } };
+ });
+
+ pipe = new reader.filesReader([ 'misc.js', 'router.js', 'config.js' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'application/javascript', 'Type for (js+js+js)');
+ } };
+ });
+
+ pipe = new reader.filesReader([ 'misc.js', '../termkit.txt' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'text/plain', 'Type for (js+txt)');
+ } };
+ });
+
+ pipe = new reader.filesReader([ 'misc.js', '../termkit.txt', '../Mockups/Shot-0.2.png' ], function () {
+ return { begin: function (headers) {
+ assert(headers.get('Content-Type') == 'application/octet-stream', 'Type for (js+txt+img)');
+ } };
+ });
+
+}
+
+
// Run tests.
var tests = {
handshake: testHandshake,
@@ -561,6 +633,7 @@ var tests = {
pipe: testPipe,
grep: testGrep,
binary: testBinary,
+ filereader: testFilereader,
};
for (i in tests) (function (i, test) {
test(function (c, msg) {
View
11 Node/view/view.js
@@ -146,6 +146,17 @@ exports.code = function (id, contents, language) {
}
/**
+ * Widget: hex output
+ */
+exports.hex = function (id, contents) {
+ return {
+ type: 'hex',
+ id: id || null,
+ contents: contents || '',
+ };
+}
+
+/**
* Widget: file reference.
*/
exports.file = function (id, name, path, stats) {
View
2 sloc.sh
@@ -18,4 +18,4 @@ mv socket.io-node Node
mv external HTML
mv jquery.js HTML
-echo Lines of code: $loc
+echo "Lines of code: $loc (not including libraries / externals)"
View
12 todo.txt
@@ -1,6 +1,12 @@
Tasks:
[X] return process meta-data in environment
+[X] redefine ansi colors
+[X] make fileReader with same pattern as reader.js
+[X] cat multiple files -> octet-stream w/ type coercion
+[ ] hex widget
+[ ] hex utility
[ ] backgrounding / new command trigger
+[ ] move escapeUnixText into front-end, unixLegacy widget
[ ] command aliases
[ ] autocomplete history
[ ] async popup / completion keystroke buffering
@@ -12,6 +18,12 @@ Tasks:
[ ] interactive unix command attempt
+blog about:
+[ ] type coercion for multiple files: cat A B C, grep A B C
+[ ] ansi coloring / escapes
+[ ] hex viewer: | hex, or hex <file> [file ...]
+
+
Prototype:
1) UI prototypes

0 comments on commit 6ee14bc

Please sign in to comment.