github.com/orteth01/up@v0.2.0/internal/shim/byline.js (about) 1 // Copyright (C) 2011-2015 John Hewson 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to 5 // deal in the Software without restriction, including without limitation the 6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 // sell copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 // IN THE SOFTWARE. 20 21 var stream = require('stream'), 22 util = require('util'); 23 24 // convinience API 25 module.exports = function(readStream, options) { 26 return module.exports.createStream(readStream, options); 27 }; 28 29 // basic API 30 module.exports.createStream = function(readStream, options) { 31 if (readStream) { 32 return createLineStream(readStream, options); 33 } else { 34 return new LineStream(options); 35 } 36 }; 37 38 // deprecated API 39 module.exports.createLineStream = function(readStream) { 40 console.log('WARNING: byline#createLineStream is deprecated and will be removed soon'); 41 return createLineStream(readStream); 42 }; 43 44 function createLineStream(readStream, options) { 45 if (!readStream) { 46 throw new Error('expected readStream'); 47 } 48 if (!readStream.readable) { 49 throw new Error('readStream must be readable'); 50 } 51 var ls = new LineStream(options); 52 readStream.pipe(ls); 53 return ls; 54 } 55 56 // 57 // using the new node v0.10 "streams2" API 58 // 59 60 module.exports.LineStream = LineStream; 61 62 function LineStream(options) { 63 stream.Transform.call(this, options); 64 options = options || {}; 65 66 // use objectMode to stop the output from being buffered 67 // which re-concatanates the lines, just without newlines. 68 this._readableState.objectMode = true; 69 this._lineBuffer = []; 70 this._keepEmptyLines = options.keepEmptyLines || false; 71 this._lastChunkEndedWithCR = false; 72 73 // take the source's encoding if we don't have one 74 this.on('pipe', function(src) { 75 if (!this.encoding) { 76 // but we can't do this for old-style streams 77 if (src instanceof stream.Readable) { 78 this.encoding = src._readableState.encoding; 79 } 80 } 81 }); 82 } 83 util.inherits(LineStream, stream.Transform); 84 85 LineStream.prototype._transform = function(chunk, encoding, done) { 86 // decode binary chunks as UTF-8 87 encoding = encoding || 'utf8'; 88 89 if (Buffer.isBuffer(chunk)) { 90 if (encoding == 'buffer') { 91 chunk = chunk.toString(); // utf8 92 encoding = 'utf8'; 93 } 94 else { 95 chunk = chunk.toString(encoding); 96 } 97 } 98 this._chunkEncoding = encoding; 99 100 var lines = chunk.split(/\r\n|\r|\n/g); 101 102 // don't split CRLF which spans chunks 103 if (this._lastChunkEndedWithCR && chunk[0] == '\n') { 104 lines.shift(); 105 } 106 107 if (this._lineBuffer.length > 0) { 108 this._lineBuffer[this._lineBuffer.length - 1] += lines[0]; 109 lines.shift(); 110 } 111 112 this._lastChunkEndedWithCR = chunk[chunk.length - 1] == '\r'; 113 this._lineBuffer = this._lineBuffer.concat(lines); 114 this._pushBuffer(encoding, 1, done); 115 }; 116 117 LineStream.prototype._pushBuffer = function(encoding, keep, done) { 118 // always buffer the last (possibly partial) line 119 while (this._lineBuffer.length > keep) { 120 var line = this._lineBuffer.shift(); 121 // skip empty lines 122 if (this._keepEmptyLines || line.length > 0 ) { 123 if (!this.push(this._reencode(line, encoding))) { 124 // when the high-water mark is reached, defer pushes until the next tick 125 var self = this; 126 setImmediate(function() { 127 self._pushBuffer(encoding, keep, done); 128 }); 129 return; 130 } 131 } 132 } 133 done(); 134 }; 135 136 LineStream.prototype._flush = function(done) { 137 this._pushBuffer(this._chunkEncoding, 0, done); 138 }; 139 140 // see Readable::push 141 LineStream.prototype._reencode = function(line, chunkEncoding) { 142 if (this.encoding && this.encoding != chunkEncoding) { 143 return new Buffer(line, chunkEncoding).toString(this.encoding); 144 } 145 else if (this.encoding) { 146 // this should be the most common case, i.e. we're using an encoded source stream 147 return line; 148 } 149 else { 150 return new Buffer(line, chunkEncoding); 151 } 152 };