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  };