Skip to content
This repository
Browse code

- 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...
commit 6ee14bcdaee9c823fcfee525233153497f7c4a13 1 parent 5fa6922
unconed authored
BIN  HTML/Images/folder.png
40 HTML/outputview/outputfactory.js
@@ -358,7 +358,6 @@ widgets.code = function (properties) {
358 358 'text/x-sql': 'sql',
359 359 'text/xml': 'xml',
360 360 'text/html': 'text',
361   - 'text/plain': 'text',
362 361 };
363 362 this.brush = brushes[properties.language];
364 363
@@ -378,11 +377,44 @@ widgets.code.prototype = $.extend(new widgets.text(), {
378 377 updateElement: function () {
379 378 this.$contents.html('<pre></pre>');
380 379 this.$pre = this.$contents.find('pre');
381   -
382 380 this.$pre.text(this.properties.contents);
383   - this.$pre.attr('class', 'brush: ' + this.brush);
384 381
385   - SyntaxHighlighter.highlight({}, this.$pre[0]);
  382 + if (this.brush) {
  383 + this.$pre.attr('class', 'brush: ' + this.brush);
  384 +
  385 + SyntaxHighlighter.highlight({}, this.$pre[0]);
  386 + }
  387 +
  388 + this.$element.data('controller', this);
  389 + },
  390 +
  391 +});
  392 +
  393 +/**
  394 + * Widget: Hex output
  395 + */
  396 +widgets.hex = function (properties) {
  397 +
  398 + // Initialize node.
  399 + ov.outputNode.call(this, properties);
  400 +
  401 + this.$contents = this.$element.find('.contents');
  402 + this.$pre = this.$contents.find('pre');
  403 +
  404 + this.updateElement();
  405 +};
  406 +
  407 +widgets.hex.prototype = $.extend(new widgets.text(), {
  408 +
  409 + // Return active markup for this widget.
  410 + $markup: function () {
  411 + var $outputNode = $('<div class="termkitOutputNode widgetHex"></div>').data('controller', this);
  412 + var that = this;
  413 + return $outputNode;
  414 + },
  415 +
  416 + // Update markup to match.
  417 + updateElement: function () {
386 418
387 419 this.$element.data('controller', this);
388 420 },
109 HTML/termkit.css
@@ -57,6 +57,115 @@ body {
57 57 word-wrap: break-word;
58 58 }
59 59
  60 +.termkitOutputView .widgetCode pre {
  61 +
  62 +}
  63 +
  64 +.termkitAnsi30 {
  65 + color: #000;
  66 +}
  67 +.termkitAnsi31 {
  68 + color: #ba3615;
  69 +}
  70 +.termkitAnsi32 {
  71 + color: #65970b;
  72 +}
  73 +.termkitAnsi33 {
  74 + color: #ba7215;
  75 +}
  76 +.termkitAnsi34 {
  77 + color: #4362d1;
  78 +}
  79 +.termkitAnsi35 {
  80 + color: #a64e80;
  81 +}
  82 +.termkitAnsi36 {
  83 + color: #419197;
  84 +}
  85 +.termkitAnsi37 {
  86 + color: #8c805f;
  87 +}
  88 +.termkitAnsi30b {
  89 + color: #524b37;
  90 + font-weight: bolder;
  91 +}
  92 +.termkitAnsi31b {
  93 + color: #ff623b;
  94 + font-weight: bolder;
  95 +}
  96 +.termkitAnsi32b {
  97 + color: #c8f82d;
  98 + font-weight: bolder;
  99 +}
  100 +.termkitAnsi33b {
  101 + color: #fddc35;
  102 + font-weight: bolder;
  103 +}
  104 +.termkitAnsi34b {
  105 + color: #97c4ff;
  106 + font-weight: bolder;
  107 +}
  108 +.termkitAnsi35b {
  109 + color: #fa8fc6;
  110 + font-weight: bolder;
  111 +}
  112 +.termkitAnsi36b {
  113 + color: #adffe9;
  114 + font-weight: bolder;
  115 +}
  116 +.termkitAnsi37b {
  117 + color: #f3ebe2;
  118 + font-weight: bolder;
  119 +}
  120 +.termkitAnsi40 {
  121 + background-color: #000;
  122 +}
  123 +.termkitAnsi41 {
  124 + background-color: #ba3615;
  125 +}
  126 +.termkitAnsi42 {
  127 + background-color: #65970b;
  128 +}
  129 +.termkitAnsi43 {
  130 + background-color: #ba7215;
  131 +}
  132 +.termkitAnsi44 {
  133 + background-color: #4362d1;
  134 +}
  135 +.termkitAnsi45 {
  136 + background-color: #a64e80;
  137 +}
  138 +.termkitAnsi46 {
  139 + background-color: #419197;
  140 +}
  141 +.termkitAnsi47 {
  142 + background-color: #8c805f;
  143 +}
  144 +.termkitAnsi40b {
  145 + background-color: #524b37;
  146 +}
  147 +.termkitAnsi41b {
  148 + background-color: #ff623b;
  149 +}
  150 +.termkitAnsi42b {
  151 + background-color: #c8f82d;
  152 +}
  153 +.termkitAnsi43b {
  154 + background-color: #fddc35;
  155 +}
  156 +.termkitAnsi44b {
  157 + background-color: #97c4ff;
  158 +}
  159 +.termkitAnsi45b {
  160 + background-color: #fa8fc6;
  161 +}
  162 +.termkitAnsi46b {
  163 + background-color: #adffe9;
  164 +}
  165 +.termkitAnsi47b {
  166 + background-color: #f3ebe2;
  167 +}
  168 +
60 169 .termkitOutputView .widgetCode div.syntaxhighlighter > div.toolbar {
61 170 display: none;
62 171 }
2  Node-API.md
Source Rendered
@@ -102,7 +102,7 @@ To handle headers on dataIn, you can use the built-in `reader.js`. To use it, cr
102 102 var handler = { ... };
103 103
104 104 // Attach reader to dataIn pipe.
105   -var pipe = new reader.reader(pipes.dataIn,
  105 +var pipe = new reader.dataReader(pipes.dataIn,
106 106 function (headers) {
107 107 // Inspect headers, return appropriate handler
108 108 return handler;
69 Node/misc.js
@@ -219,27 +219,10 @@ exports.objectKeys = function (object) {
219 219 * Escape binary data for display as HTML.
220 220 */
221 221 exports.escapeBinary = function (data) {
222   - var binary = data.toString('utf-8'), n = binary.length, i = 0;
223   -
224   - // Escape HTML characters.
225   - binary = binary.replace(/[<&]/g, function (x) {
226   - return { '<': '&lt;', '&': '&amp;' };
227   - });
228   -
229   - // Handle antique italic escapes used by grotty -c.
230   - binary = binary.replace(/_\u0008(.)\u0008\1/g, function (x, char) {
231   - return '<b><i>' + char + '</i></b>';
232   - });
233   -
234   - // Handle antique bold escapes used by grotty -c.
235   - binary = binary.replace(/(.)\u0008\1/g, function (x, char) {
236   - return '<b>' + char + '</b>';
237   - });
238   -
239   - // Handle antique italic escapes used by grotty -c.
240   - binary = binary.replace(/_\u0008(.)/g, function (x, char) {
241   - return '<i>' + char + '</i>';
242   - });
  222 + if (typeof data != 'string') {
  223 + data = data.toString('utf-8');
  224 + }
  225 + var binary = data, n = binary.length, i = 0;
243 226
244 227 // Escape non-printables
245 228 binary = binary.replace(/([\u0000-\u001F\u0080-\u009F])/g, function (x, char) {
@@ -253,3 +236,47 @@ exports.escapeBinary = function (data) {
253 236
254 237 return binary;
255 238 }
  239 +
  240 +/**
  241 + * Escape textual Unix data for display as HTML.
  242 + */
  243 +exports.escapeUnixText = function (data) {
  244 + if (typeof data != 'string') {
  245 + data = data.toString('utf-8');
  246 + }
  247 + var binary = data, n = binary.length, i = 0;
  248 +
  249 + // Escape HTML characters.
  250 + binary = binary.replace(/[<&]/g, function (x) {
  251 + return { '<': '&lt;', '&': '&amp;' }[x];
  252 + });
  253 +
  254 + // Handle ANSI color escapes.
  255 + var bold = false,
  256 + italic = false,
  257 + underline = false,
  258 + blink = false,
  259 + strike = false,
  260 + binary = binary.replace(/\u001b([0-9]+(;[0-9]+)*)?m/g, function (x, codes) {
  261 + codes = codes.split(';');
  262 + for (i in codes) {
  263 + switch (codes[i]) {
  264 +
  265 + }
  266 + }
  267 + });
  268 +
  269 + // Handle antique bold/italic escapes used by grotty -c.
  270 + binary = binary
  271 + .replace(/_\u0008(.)\u0008\1/g, function (x, char) {
  272 + return '<b><i>' + char + '</i></b>';
  273 + })
  274 + .replace(/(.)\u0008\1/g, function (x, char) {
  275 + return '<b>' + char + '</b>';
  276 + })
  277 + .replace(/_\u0008(.)/g, function (x, char) {
  278 + return '<i>' + char + '</i>';
  279 + });
  280 +
  281 + return exports.escapeBinary(binary);
  282 +}
1  Node/shell/builtin/builtin.js
@@ -5,6 +5,7 @@ exports.commands = {
5 5 echo: true,
6 6 get: true,
7 7 grep: true,
  8 + hex: true,
8 9 ls: true,
9 10 pwd: true,
10 11 };
136 Node/shell/builtin/cat.js
@@ -2,85 +2,83 @@ var fs = require('fs'),
2 2 view = require('view/view'),
3 3 whenDone = require('misc').whenDone,
4 4 meta = require('shell/meta'),
5   - expandPath = require('misc').expandPath;
6   -
  5 + reader = require('shell/reader');
  6 +
7 7 exports.main = function (tokens, pipes, exit, environment) {
8 8 var out = new view.bridge(pipes.viewOut);
9   - var chunkSize = 16384;
10 9
11 10 // "cat <file> [file ...]" syntax.
12   - if (tokens.length < 2) {
  11 + tokens.shift();
  12 + if (tokens.length < 1) {
13 13 out.print('Usage: cat <file> [file] ...');
14 14 return exit(false);
15 15 }
16   - if (tokens.length > 2) {
17   - out.print('Multiple input files not supported yet.');
18   - return exit(false);
19   - }
20   -
21   - var errors = 0,
22   - track = whenDone(function () {
23   - exit(errors == 0);
24   - });
25   -
26   - for (i in tokens) if (i > 0) (function (file) {
27   - expandPath(file, track(function (file) {
28   - fs.stat(file, track(function (err, stats) {
29   - if (err) {
30   - errors++;
31   - out.print("No such file (" + file + ")");
32   - return;
33   - }
34   - fs.open(file, 'r', track(function (err, fd) {
35   - if (err) {
36   - errors++;
37   - out.print("Unable to open file (" + file + ")");
38   - return;
39   - }
40   -
41   - // See if we need a progress bar.
42   - var progress = stats.size > 1024 * 1024; // yes, this is arbitrary
43   - var position = 0;
44   -
45   - if (progress) {
46   - out.add(null, view.progress('progress', 0, 0, stats.size));
47   - }
48   -
49   - (function read() {
50   - var buffer = new Buffer(chunkSize);
51   - fs.read(fd, buffer, 0, chunkSize, position, track(function (err, bytesRead) {
52   - if (err) {
53   - errors++;
54   - out.print("Error reading file (" + file + ")");
55   - return;
56   - }
57   -
58   - var slice = buffer.slice(0, bytesRead);
59 16
60   - if (position == 0) {
61   - var headers = new meta.headers(),
62   - keys = file.split('/');
63   - headers.set({
64   - 'Content-Type': meta.sniff(file, slice),
65   - 'Content-Length': stats.size,
66   - 'Content-Disposition': [ 'attachment', { 'filename': keys.pop() } ],
67   - });
  17 + var files = tokens;
  18 +
  19 + // Reader handler
  20 + var progress, position;
  21 + var handler = {
  22 + /**
  23 + * Files have been opened, unified headers available.
  24 + */
  25 + begin: function (headers) {
  26 +
  27 + // See if we need a progress bar.
  28 + var size = headers.get('Content-Length');
  29 + progress = size > 1024 * 1024; // yes, this is arbitrary
  30 + if (progress) {
  31 + position = 0;
  32 + out.print(view.progress('progress', 0, 0, size));
  33 + }
  34 +
  35 + // Forward headers.
  36 + pipes.dataOut.write(headers.generate());
  37 + process.stderr.write(headers.generate());
  38 + // Unbuffered operation.
  39 + return false;
  40 + },
68 41
69   - pipes.dataOut.write(headers.generate());
70   - }
  42 + /**
  43 + * Data coming in.
  44 + */
  45 + data: function (data) {
  46 + // Pipe through.
  47 + pipes.dataOut.write(data);
71 48
72   - pipes.dataOut.write(slice);
73   - position += bytesRead;
  49 + // Progress bar.
  50 + if (progress) {
  51 + position += data.length;
  52 + out.update('progress', { value: position });
  53 + }
  54 + },
  55 +
  56 + /**
  57 + * Done reading.
  58 + */
  59 + end: function (exit) {
  60 + exit();
  61 + },
  62 + };
74 63
75   - if (position < stats.size) {
76   - read();
77   - }
  64 + // Reader callbacks
  65 + var errors = 0;
  66 + // Open
  67 + function readerOpen() {
  68 + return handler;
  69 + };
  70 + // Close
  71 + function readerClose() {
  72 + exit(errors == 0);
  73 + };
  74 + // Error
  75 + function readerError(error) {
  76 + errors++;
  77 + out.print(error);
  78 + };
78 79
79   - progress && out.update('progress', { value: position });
80   - })); // fs.read
81   - })(); // read
82   - })); // fs.open
83   - })); // fs.stat
84   - })); // expandPath
85   - })(tokens[i]); // for i in tokens
  80 + // Spawn multiple files reader.
  81 + // Handles type coercion, file naming, etc.
  82 + var errors = 0,
  83 + pipe = new reader.filesReader(files, readerOpen, readerClose, readerError);
86 84 };
2  Node/shell/builtin/get.js
@@ -47,7 +47,7 @@ exports.main = function (tokens, pipes, exit, environment) {
47 47 if (length !== null) {
48 48 progress = length > 16 * 1024; // yes, this is arbitrary
49 49 if (progress) {
50   - out.add(null, view.progress('progress', 0, 0, length));
  50 + out.print(view.progress('progress', 0, 0, length));
51 51 }
52 52 }
53 53
253 Node/shell/builtin/grep.js
@@ -19,140 +19,151 @@ exports.main = function (tokens, pipes, exit, environment) {
19 19 var pattern = args.values.shift(),
20 20 files = args.values;
21 21
22   - if (files.length) {
23   - // TODO file grep
24   - out.print("File grep unsupported.");
25   - return exit(false);
26   - }
27   - else {
28   - var json, tail = '';
  22 + var json, tail = '';
  23 +
  24 + // In-place grepper
  25 + function grep(object, value, force) {
  26 + var i;
29 27
30   - // In-place grepper
31   - function grep(object, value, force) {
32   - var i;
33   -
34   - if (object.constructor == String || object.constructor == Number) {
35   - // Match strings.
36   - return ((!matchKeys || force) && (negative ^ !!(''+object).match(value))) ? object : null;
  28 + if (object.constructor == String || object.constructor == Number) {
  29 + // Match strings.
  30 + return ((!matchKeys || force) && (negative ^ !!(''+object).match(value))) ? object : null;
  31 + }
  32 + if (object.constructor == Array) {
  33 + // Keep only non-null items after grep.
  34 + object = object.map(function (key) {
  35 + return grep(key, value);
  36 + }).filter(function (x) { return x !== null; });
  37 +
  38 + // Prune empties.
  39 + return object.length ? object : null;
  40 + }
  41 +
  42 + var out = {};
  43 + for (i in object) {
  44 + var item;
  45 + if (matchKeys) {
  46 + // Pass keys through grep.
  47 + item = (grep(i, value, true) !== null) ? object[i] : null
37 48 }
38   - if (object.constructor == Array) {
39   - // Keep only non-null items after grep.
40   - object = object.map(function (key) {
41   - return grep(key, value);
42   - }).filter(function (x) { return x !== null; });
43   -
44   - // Prune empties.
45   - return object.length ? object : null;
  49 + else {
  50 + // Pass values through grep.
  51 + item = grep(object[i], value);
46 52 }
47 53
48   - var out = {};
49   - for (i in object) {
50   - var item;
51   - if (matchKeys) {
52   - // Pass keys through grep.
53   - item = (grep(i, value, true) !== null) ? object[i] : null
54   - }
55   - else {
56   - // Pass values through grep.
57   - item = grep(object[i], value);
58   - }
59   -
60   - // Keep non-null items.
61   - if (item !== null) {
62   - out[i] = item;
63   - }
  54 + // Keep non-null items.
  55 + if (item !== null) {
  56 + out[i] = item;
64 57 }
  58 + }
65 59
66   - // Prune empties.
67   - if (JSON.stringify(out) == '{}') {
68   - return null;
69   - }
70   - return out;
  60 + // Prune empties.
  61 + if (JSON.stringify(out) == '{}') {
  62 + return null;
71 63 }
  64 + return out;
  65 + }
72 66
73   - // Buffered mime reader handler.
74   - var handler = {
  67 + // Buffered mime reader handler.
  68 + var handler = {
  69 +
  70 + /**
  71 + * Pipe open, headers found.
  72 + */
  73 + begin: function (headers) {
  74 +
  75 + var type = headers.get('Content-Type'),
  76 + buffered = false;
  77 +
  78 + switch (type) {
  79 + default:
  80 + case 'text/plain':
  81 + json = false;
  82 + break;
  83 +
  84 + case 'application/json':
  85 + buffered = json = true;
  86 + break;
  87 + }
  88 +
  89 + // Remove content-length, output rest.
  90 + headers.set('Content-Length', null);
  91 + pipes.dataOut.write(headers.generate());
75 92
76   - /**
77   - * Pipe open, headers found.
78   - */
79   - begin: function (headers) {
80   -
81   - var type = headers.get('Content-Type');
82   - buffered = false;
83   -
84   - switch (type) {
85   - default:
86   - case 'text/plain':
87   - json = false;
88   - break;
89   -
90   - case 'application/json':
91   - buffered = json = true;
92   - break;
93   - }
  93 + return buffered;
  94 + },
94 95
95   - // Remove content-length, output rest.
96   - headers.set('Content-Length', null);
97   - pipes.dataOut.write(headers.generate());
98   -
99   - return buffered;
100   - },
101   -
102   - /**
103   - * Pipe data.
104   - */
105   - data: function (data) {
106   -
107   - if (json) {
108   - // Process whole json object (buffered).
109   - data = JSON.parse(data.toString('utf-8'));
110   - }
111   - else {
112   - // Process line by line (unbuffered chunks).
113   - data = (tail + data).toString('utf-8').split("\n");
114   - tail = data.pop();
115   - }
  96 + /**
  97 + * Pipe data.
  98 + */
  99 + data: function (data) {
116 100
117   - // Filter values recursively.
118   - data = grep(data, pattern);
119   -
120   - if (json) {
121   - // Serialize
122   - data = JSON.stringify(data);
123   - }
124   - else {
125   - // Join lines.
126   - data = data.join("\n");
127   - }
128   -
129   - // Pipe out.
130   - pipes.dataOut.write(data);
131   - },
  101 + if (json) {
  102 + // Process whole json object (buffered).
  103 + data = JSON.parse(data.toString('utf-8'));
  104 + }
  105 + else {
  106 + // Process line by line (unbuffered chunks).
  107 + data = (tail + data).toString('utf-8').split("\n");
  108 + tail = data.pop();
  109 + }
  110 +
  111 + // Filter values recursively.
  112 + data = grep(data, pattern);
  113 +
  114 + if (json) {
  115 + // Serialize
  116 + data = JSON.stringify(data);
  117 + }
  118 + else {
  119 + // Join lines.
  120 + data = data.join("\n");
  121 + }
132 122
133   - /**
134   - * Pipe closed.
135   - */
136   - end: function (exit) {
137   - if (tail) {
138   - // Last dangling line.
139   - data = grep([tail], pattern);
140   - if (data !== null) {
141   - pipes.dataOut.write(data);
142   - }
  123 + // Pipe out.
  124 + pipes.dataOut.write(data);
  125 + },
  126 +
  127 + /**
  128 + * Pipe closed.
  129 + */
  130 + end: function (exit) {
  131 + if (tail) {
  132 + // Last dangling line.
  133 + data = grep([tail], pattern);
  134 + if (data !== null) {
  135 + pipes.dataOut.write(data);
143 136 }
  137 + }
144 138
145   - // Quit.
146   - exit();
147   - },
148   - };
149   -
150   - // Stdin grep.
151   - var pipe = new reader.reader(pipes.dataIn,
152   - function () { return handler; },
153   - function () {
154   - exit(true);
155   - });
156   -
  139 + // Quit.
  140 + exit();
  141 + },
  142 + };
  143 +
  144 + // Reader callbacks
  145 + var errors = 0;
  146 + // Open
  147 + function readerOpen() {
  148 + return handler;
  149 + };
  150 + // Close
  151 + function readerClose() {
  152 + exit(errors == 0);
  153 + };
  154 + // Error
  155 + function readerError(error) {
  156 + errors++;
  157 + out.print(error);
  158 + };
  159 +
  160 + // Spawn appropriate reader.
  161 + if (files.length) {
  162 + // Spawn files reader.
  163 + var pipe = new reader.filesReader(files, readerOpen, readerClose, readerError);
  164 + }
  165 + else {
  166 + // Spawn data reader.
  167 + var pipe = new reader.dataReader(pipes.dataIn, readerOpen, readerClose, readerError);
157 168 }
158 169 };
3  Node/shell/builtin/ls.js
@@ -31,8 +31,7 @@ exports.main = function (tokens, pipes, exit, environment) {
31 31
32 32 // Format data.
33 33 var data = JSON.stringify(output);
34   - process.stderr.write(data);
35   -
  34 +
36 35 // Prepare headers.
37 36 var headers = new meta.headers();
38 37 headers.set({
2  Node/shell/command.js
@@ -287,7 +287,7 @@ exports.commandUnit.unixCommand.prototype.go = function () {
287 287
288 288 // Add MIME headers to raw output from process.
289 289 var headers = new meta.headers();
290   - headers.set('Content-Type', 'application/octet-stream');
  290 + headers.set('Content-Type', [ 'application/octet-stream', { schema: 'termkit.unix' } ]);
291 291 this.process.stdout.emit('data', headers.generate());
292 292
293 293 };
88 Node/shell/formatter.js
@@ -8,7 +8,8 @@ var fs = require('fs'),
8 8 composePath = require('misc').composePath,
9 9 objectKeys = require('misc').objectKeys,
10 10 reader = require('reader'),
11   - escapeBinary = require('misc').escapeBinary;
  11 + escapeBinary = require('misc').escapeBinary,
  12 + escapeUnixText = require('misc').escapeUnixText;
12 13
13 14 /**
14 15 * Error logger.
@@ -20,7 +21,7 @@ exports.logger = function (unit, viewOut) {
20 21 // Link up to stderr of process.
21 22 unit.process.stderr.on('data', function (data) {
22 23 var binary = escapeBinary(data);
23   - this.out.add(null, view.code(null, binary, 'text/plain'));
  24 + this.out.print(view.code(null, binary, 'text/plain'));
24 25 });
25 26
26 27 };
@@ -35,7 +36,7 @@ exports.formatter = function (tail, viewOut, exit) {
35 36 this.out = new view.bridge(viewOut);
36 37
37 38 // Start reading the output.
38   - this.reader = new reader.reader(tail.process.stdout, function (headers) {
  39 + this.reader = new reader.dataReader(tail.process.stdout, function (headers) {
39 40 // Construct appropriate plugin for this type.
40 41 return that.plugin = exports.factory(headers, that.out);
41 42 }, function () {
@@ -106,7 +107,7 @@ exports.plugins.fallback.supports = function (headers) {
106 107
107 108 exports.plugins.fallback.prototype = extend(new exports.plugin(), {
108 109 begin: function (headers) {
109   - this.out.add(null, view.code('output', headers.generate(), 'text/plain'));
  110 + this.out.print(view.code('output', headers.generate(), 'text/plain'));
110 111 },
111 112 });
112 113
@@ -121,8 +122,8 @@ exports.plugins.text = function (headers, out) {
121 122 exports.plugins.text.prototype = extend(new exports.plugin(), {
122 123
123 124 begin: function () {
124   -// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
125   - this.out.add(null, view.text('output'));
  125 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  126 + this.out.print(view.code('output', '', 'text/plain'));
126 127 },
127 128
128 129 data: function (data) {
@@ -147,8 +148,8 @@ exports.plugins.pdf = function (headers, out) {
147 148 exports.plugins.pdf.prototype = extend(new exports.plugin(), {
148 149
149 150 begin: function () {
150   -// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
151   - this.out.add(null, view.html('output'));
  151 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  152 + this.out.print(view.html('output'));
152 153
153 154 // Buffered.
154 155 return true;
@@ -181,8 +182,8 @@ exports.plugins.html = function (headers, out) {
181 182 exports.plugins.html.prototype = extend(new exports.plugin(), {
182 183
183 184 begin: function () {
184   -// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
185   - this.out.add(null, view.html('output'));
  185 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  186 + this.out.print(view.html('output'));
186 187
187 188 // Buffered.
188 189 return true;
@@ -215,8 +216,8 @@ exports.plugins.code = function (headers, out) {
215 216 exports.plugins.code.prototype = extend(new exports.plugins.text(), {
216 217
217 218 begin: function () {
218   -// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
219   - this.out.add(null, view.code('output', '', this.headers.get('Content-Type')));
  219 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  220 + this.out.print(view.code('output', '', this.headers.get('Content-Type')));
220 221
221 222 // Buffered output.
222 223 return true;
@@ -278,7 +279,7 @@ exports.plugins.image.prototype = extend(new exports.plugin(), {
278 279
279 280 data: function (data) {
280 281 var url = 'data:' + this.headers.get('Content-Type') + ';base64,' + data.toString('base64');
281   - this.out.add(null, view.image('image', url));
  282 + this.out.print(view.image('image', url));
282 283 },
283 284
284 285 });
@@ -376,6 +377,34 @@ exports.plugins.files.supports = function (headers) {
376 377 };
377 378
378 379 /**
  380 + * Unix text formatter.
  381 + */
  382 +exports.plugins.unix = function (headers, out) {
  383 + // Inherit.
  384 + exports.plugin.apply(this, arguments);
  385 +};
  386 +
  387 +exports.plugins.unix.prototype = extend(new exports.plugin(), {
  388 +
  389 + begin: function () {
  390 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  391 + this.out.print(view.html('output', ''));
  392 + },
  393 +
  394 + data: function (data) {
  395 + var binary = '<pre>'+ escapeUnixText(data) +'</pre>';
  396 + this.out.update('output', { contents: binary }, true);
  397 + },
  398 +
  399 +});
  400 +
  401 +exports.plugins.unix.supports = function (headers) {
  402 + var type = headers.get('Content-Type'),
  403 + schema = headers.get('Content-Type', 'schema');
  404 + return !!(/^application\/octet-stream$/(type) && (schema == 'termkit.unix')) * 3;
  405 +}
  406 +
  407 +/**
379 408 * Binary formatter.
380 409 */
381 410 exports.plugins.binary = function (headers, out) {
@@ -386,12 +415,12 @@ exports.plugins.binary = function (headers, out) {
386 415 exports.plugins.binary.prototype = extend(new exports.plugin(), {
387 416
388 417 begin: function () {
389   -// this.out.add(null, view.code('output', this.headers.generate(), 'text/plain'));
390   - this.out.add(null, view.html('output', ''));
  418 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  419 + this.out.print(view.code('output', '', 'text/plain'));
391 420 },
392 421
393 422 data: function (data) {
394   - var binary = '<pre>'+ escapeBinary(data) + '</pre>';
  423 + var binary = escapeBinary(data);
395 424 this.out.update('output', { contents: binary }, true);
396 425 },
397 426
@@ -402,3 +431,30 @@ exports.plugins.binary.supports = function (headers) {
402 431 return !!(/^application\/octet-stream/(type)) * 1;
403 432 }
404 433
  434 +/**
  435 + * Hex formatter.
  436 + */
  437 +exports.plugins.hex = function (headers, out) {
  438 + // Inherit.
  439 + exports.plugin.apply(this, arguments);
  440 +};
  441 +
  442 +exports.plugins.hex.prototype = extend(new exports.plugin(), {
  443 +
  444 + begin: function () {
  445 +// this.out.print(view.code('output', this.headers.generate(), 'text/plain'));
  446 + this.out.print(view.hex('output', ''));
  447 + },
  448 +
  449 + data: function (data) {
  450 + this.out.update('output', { contents: data.toString('base64') }, true);
  451 + },
  452 +
  453 +});
  454 +
  455 +exports.plugins.hex.supports = function (headers) {
  456 + var type = headers.get('Content-Type'),
  457 + schema = headers.get('Content-Type', 'schema');
  458 + return !!(/^application\/octet-stream$/(type) && (schema == 'termkit.hex')) * 3;
  459 +}
  460 +
123 Node/shell/meta.js
... ... @@ -1,13 +1,16 @@
1 1 var mime = require('mime');
2 2
  3 +// Is x an object?
3 4 function isObject(x) {
4 5 return typeof x == 'object';
5 6 }
6 7
  8 +// Is x a string?
7 9 function isString(x) {
8 10 return (isObject(x) || typeof x == 'string') && x.constructor == ''.constructor;
9 11 }
10 12
  13 +// Is x an array?
11 14 function isArray(x) {
12 15 return isObject(x) && x.constructor == [].constructor;
13 16 }
@@ -17,7 +20,7 @@ function join(string) {
17 20 return (''+string).replace(/[\r\n] ?/g, ' ');
18 21 }
19 22
20   -// Quoted string
  23 +// Quote a string
21 24 function quote(string) {
22 25 if (/[\u0080-\uFFFF]/(string)) {
23 26 // TODO: RFC2047 mime encoded tokens.
@@ -28,7 +31,11 @@ function quote(string) {
28 31 return string;
29 32 }
30 33
31   -
  34 +/**
  35 + * Container for header key/values + parameters.
  36 + *
  37 + * Can parse from and generate to MIME format.
  38 + */
32 39 exports.headers = function () {
33 40 this.fields = {};
34 41 this.params = {};
@@ -46,14 +53,23 @@ exports.headers = function () {
46 53
47 54 exports.headers.prototype = {
48 55
  56 + /**
  57 + * Set header parsing hint.
  58 + */
49 59 hint: function (key, args) {
50 60 this.hints[key] = args;
51 61 },
52 62
  63 + /**
  64 + * Return all values + parameters for a key.
  65 + */
53 66 all: function (key) {
54 67 return [ this.fields[key], this.params[key] ];
55 68 },
56 69
  70 + /**
  71 + * Get one value or parameter.
  72 + */
57 73 get: function (key, param) {
58 74 if (typeof this.fields[key] != 'undefined') {
59 75 if (param) {
@@ -63,28 +79,30 @@ exports.headers.prototype = {
63 79 }
64 80 },
65 81
  82 + /**
  83 + * Set one or more values and/or parameters.
  84 + *
  85 + * -- Single setters
  86 + * set(key, value)
  87 + * set(key, param, value)
  88 + *
  89 + * -- Combined value/param setter.
  90 + * set(key, [ value, { param: value, param: value } ])
  91 + *
  92 + * -- Multi-value setter.
  93 + * set(key, [ value, value, value ])
  94 + * set(key, [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ])
  95 + *
  96 + * -- Generic hash syntax.
  97 + * set({
  98 + * key: value,
  99 + * key: [ value, { param: value }],
  100 + * key: [ value, value, value ],
  101 + * key: [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ],
  102 + * })
  103 + */
66 104 set: function (keyObject, paramValue, value, raw) {
67 105 var i, j;
68   - /*
69   - // Single setters
70   - set(key, value)
71   - set(key, param, value)
72   -
73   - // Combined value/param setter.
74   - set(key, [ value, { param: value, param: value } ])
75   -
76   - // Multi-value setter.
77   - set(key, [ value, value, value ])
78   - set(key, [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ])
79   -
80   - // Generic hash syntax.
81   - set({
82   - key: value,
83   - key: [ value, { param: value }],
84   - key: [ value, value, value ],
85   - key: [ [ value, { param: value, param: value } ], [ value, { param: value, param: value } ] ],
86   - })
87   - */
88 106
89 107 // Raw set to parse individual value from other source.
90 108 if (raw) {
@@ -428,6 +446,67 @@ exports.sniff = function (file, data) {
428 446 return 'text/plain';
429 447 };
430 448
  449 +/**
  450 + * Default type for type classes.
  451 + */
  452 +exports.base = function (type) {
  453 + var map = {
  454 + 'application/javascript': 'text',
  455 + 'application/x-perl': 'text',
  456 + 'application/x-php': 'text',
  457 + };
  458 +
  459 + if (map[type]) {
  460 + type = map[type];
  461 + }
  462 +
  463 + return exports.default(type) || 'application/octet-stream';
  464 +}
  465 +
  466 +/**
  467 + * Default type for type classes.
  468 + */
  469 +exports.default = function (type) {
  470 + type = type.split('/')[0];
  471 + return {
  472 + 'text': 'text/plain',
  473 + }[type];
  474 +};
  475 +
  476 +/**
  477 + * List of mime types with composable content.
  478 + */
  479 +exports.composable = function () {
  480 + return {
  481 + 'application/javascript': true,
  482 + 'application/x-perl': true,
  483 + 'application/x-php': true,
  484 + 'text/css': true,
  485 + 'text/csv': true,
  486 + 'text/javascript': true,
  487 + 'text/html': true,
  488 + 'text/plain': true,
  489 + 'text/x-actionscript': true,
  490 + 'text/x-applescript': true,
  491 + 'text/x-c': true,
  492 + 'text/x-c++': true,
  493 + 'text/x-csharpsrc': true,
  494 + 'text/x-diff': true,
  495 + 'text/x-erlang': true,
  496 + 'text/x-groovy': true,
  497 + 'text/x-java-source': true,
  498 + 'text/x-python': true,
  499 + 'text/x-ruby': true,
  500 + 'text/x-sass': true,
  501 + 'text/x-scala': true,
  502 + 'text/x-shellscript': true,
  503 + 'text/x-sql': true,
  504 + };
  505 +};
  506 +
  507 +/**
  508 + * Additional mime types for node-mime.
  509 + */
431 510 mime.define({
432 511 'text/x-applescript': ['applescript'],
433 512 'text/x-actionscript': ['actionscript'],
475 Node/shell/reader.js
@@ -5,27 +5,26 @@ var fs = require('fs'),
5 5 async = require('misc').async,
6 6 extend = require('misc').extend,
7 7 JSONPretty = require('misc').JSONPretty,
8   - composePath = require('misc').composePath;
9   -
  8 + composePath = require('misc').composePath,
  9 + expandPath = require('misc').expandPath;
10 10
11   -var debug;
12   -debug = process.stderr.write;
13   -debug = console.log
14   -
15 11 /**
16   - * Data reader for termkit-style stdI/O.
  12 + * Data reader for termkit-style dataIn.
17 13 *
18 14 * Waits for mime headers, parses them, then calls begin() to construct
19 15 * an appropriate stream/data handler.
20 16 *
  17 + * Supports buffered and unbuffered input.
  18 + *
21 19 * Calls exit when the pipe closes.
22 20 */
23   -exports.reader = function (dataIn, begin, exit) {
  21 +exports.dataReader = function (dataIn, begin, exit, error) {
24 22 var that = this;
25 23
26 24 this.dataIn = dataIn;
27   - this.begin = begin;
28   - this.exit = exit;
  25 + this.begin = begin || (function () {});
  26 + this.exit = exit || (function () {});
  27 + this.error = error || (function () {});
29 28
30 29 // Header sniffing.
31 30 this.identified = false;
@@ -50,48 +49,20 @@ exports.reader = function (dataIn, begin, exit) {
50 49 });
51 50 };
52 51
53   -exports.reader.prototype = {
54   -
55   - done: function () {
56   -
57   - // No data.
58   - if (!this.handler) {
59   - return this.exit();
60   - }
61   -
62   - // Send all buffered output to plug-in in one chunk.
63   - if (this.buffered) {
64   -
65   - if (!this.buffer) {
66   - this.buffer = new Buffer(this.length);
67   -
68   - // Join chunks.
69   - for (i in this.chunks) {
70   - var data = this.chunks[i];
71   -
72   - data.copy(this.buffer, this.offset, 0, data.length)
73   - this.offset += data.length;
74   - }
75   - }
76   -
77   - this.handler.data(this.buffer);
78   - }
79   -
80   - this.handler.end(this.exit);
81   -
82   - },
  52 +exports.dataReader.prototype = {
83 53
  54 + /**
  55 + * Parse headers string into headers object.
  56 + */
84 57 parse: function (headers) {
85 58 this.headers = new meta.headers();
86 59 this.headers.parse(headers);
87   - for (i in this.headers.fields) {
88   -// debug('Header ' + i +': ' + this.headers.fields[i] +"\n");
89   - }
90 60 },
91 61
92   - // Parse MIME headers for stream
  62 + /**
  63 + * Read stream, find MIME headers.
  64 + */
93 65 data: function (data) {
94   -// debug('CHUNK ' + data + "\n\n");
95 66 if (!this.identified) {
96 67 // Swallow data until we encounter the MIME header delimiter.
97 68 this.lookahead += data.toString('ascii');
@@ -104,14 +75,13 @@ exports.reader.prototype = {
104 75 // Notify context of headers, receive handler.
105 76 this.identified = true;
106 77 this.handler = this.begin(this.headers);
107   - this.buffered = this.handler.begin(this.headers);
  78 + this.buffered = this.handler.begin && this.handler.begin(this.headers);
108 79
109 80 // See if size is known ahead of time.
110 81 var length = this.length = parseInt(this.headers.get('Content-Length'));
111 82 if (this.buffered && !isNaN(length)) {
112 83 // Allocate large buffer.
113 84 this.buffer = new Buffer(length);
114   - //debug('allocated ' + this.length+" got " + this.buffer.length + "\n");