function fileLineReader(filepath, opts){
    if(typeof arguments[0] !== 'string'){
        opts = arguments[0];
        filepath = opts.file || opts.filename || opts.filepath;
    }

    filepath = resolve(filepath).fullPath;

    opts = opts || {};
    var chunkSize = opts.chunkSize || 1;
    var hasHeader = opts.header || opts.headers || opts.hasHeader;
    var headLine;
    var instream = fs.createReadStream(filepath, opts);
    var outstream = new Stream();
    var rl = readline.createInterface(instream, outstream);
    var onData;
    
    var done;
    instream.on('error', function(err) {
        rl.closed = true;
        rl.close();
        if(done) done(err);
        else throw err;
    });
    
    opts.separator = opts.separator || opts.delimiter;

    var lineParser = opts.lineParser || function(line){
        if(!opts.separator) return line;
        return line.split(opts.separator);
    };

    var lines = [];
    var lineError;
    var index = -1;
    var limit = 1;
    
    function getLines(numOfLines, cb){
        if(arguments.length === 0) cb = arguments[0];
        if(typeof cb !== 'function') throw new Error('Wrong arguments');
        
        done = cb;
        
        index = index === -1 ? -1 : 0;
        limit = numOfLines || 1;
        var result;
        
        if(lines.length===0) index = -1; // fix - when paused on chunkSize
        if(lines.length >= limit) {
            result = lines.slice(0, limit);
            lines.splice(0,limit);
            return cb(null, result);
        }
        
        if(rl.closed){
            result = lines.slice();
            lines = [];
            return cb(null, result);
        }
        
        onData = cb;
        rl.resume();
    }

    rl.on('line', function(line) {
        index++;

        line = lineParser(line);
        
        if(headLine){
            var lineWithHeader = {};
            for(var i=0;i<headLine.length;i++){
                lineWithHeader[ headLine[i] ] = line[i];
            }
            line = lineWithHeader;
        }
        else if(index===0 && hasHeader){
            headLine = line;
            index--;
            return;
        }

        lines.push(line);

        if(limit <= index+1){
            if(onData){
                rl.pause();
                var result = lines.slice(0, limit);
                lines.splice(0, limit);
                onData(null, result);
                onData = null;
            }
        }
    });

    rl.on('close', function() {
        rl.closed = true;
        if(onData){
            onData(null, lines);
            onData = null;
        }
    });

    rl.pause();
    
    return {
        read: getLines,
        close: function(){
            rl.close();
        }
    };
}
function fileLineReader(filepath, opts){
    if(typeof arguments[0] !== 'string'){
        opts = arguments[0];
        filepath = opts.file || opts.filename || opts.filepath;
    }

    filepath = resolve(filepath).fullPath;

    opts = opts || {};
    var chunkSize = opts.chunkSize || 1;
    var hasHeader = opts.header || opts.headers || opts.hasHeader;
    var headLine;
    var instream = fs.createReadStream(filepath, opts);
    var outstream = new Stream();
    var rl = readline.createInterface(instream, outstream);
    var onData;
    
    var done;
    instream.on('error', function(err) {
        rl.closed = true;
        rl.close();
        if(done) done(err);
        else throw err;
    });
    
    opts.separator = opts.separator || opts.delimiter;

    var lineParser = opts.lineParser || function(line){
        if(!opts.separator) return line;
        return line.split(opts.separator);
    };

    var lines = [];
    var lineError;
    var index = -1;
    var limit = 1;
    
    function getLines(numOfLines, cb){
        if(arguments.length === 0) cb = arguments[0];
        if(typeof cb !== 'function') throw new Error('Wrong arguments');
        
        done = cb;
        
        index = index === -1 ? -1 : 0;
        limit = numOfLines || 1;
        var result;
        
        if(lines.length===0) index = -1; 
        if(lines.length >= limit) {
            result = lines.slice(0, limit);
            lines.splice(0,limit);
            return cb(null, result);
        }
        
        if(rl.closed){
            result = lines.slice();
            lines = [];
            return cb(null, result);
        }
        
        onData = cb;
        rl.resume();
    }

    rl.on('line', function(line) {
        index++;

        line = lineParser(line);
        
        if(headLine){
            var lineWithHeader = {};
            for(var i=0;i<headLine.length;i++){
                lineWithHeader[ headLine[i] ] = line[i];
            }
            line = lineWithHeader;
        }
        else if(index===0 && hasHeader){
            headLine = line;
            index--;
            return;
        }

        lines.push(line);

        if(limit <= index+1){
            if(onData){
                rl.pause();
                var result = lines.slice(0, limit);
                lines.splice(0, limit);
                onData(null, result);
                onData = null;
            }
        }
    });

    rl.on('close', function() {
        rl.closed = true;
        if(onData){
            onData(null, lines);
            onData = null;
        }
    });

    rl.pause();
    
    return {
        read: getLines,
        close: function(){
            rl.close();
        }
    };
}
function fileLineReader ( filepath , opts ) { if ( typeof arguments [ 0 ] !== 'string' ) { opts = arguments [ 0 ] ; filepath = opts . file || opts . filename || opts . filepath ; } filepath = resolve ( filepath ) . fullPath ; opts = opts || { } ; var chunkSize = opts . chunkSize || 1 ; var hasHeader = opts . header || opts . headers || opts . hasHeader ; var headLine ; var instream = fs . createReadStream ( filepath , opts ) ; var outstream = new Stream ( ) ; var rl = readline . createInterface ( instream , outstream ) ; var onData ; var done ; instream . on ( 'error' , function ( err ) { rl . closed = true ; rl . close ( ) ; if ( done ) done ( err ) ; else throw err ; } ) ; opts . separator = opts . separator || opts . delimiter ; var lineParser = opts . lineParser || function ( line ) { if ( ! opts . separator ) return line ; return line . split ( opts . separator ) ; } ; var lines = [ ] ; var lineError ; var index = - 1 ; var limit = 1 ; function getLines ( numOfLines , cb ) { if ( arguments . length === 0 ) cb = arguments [ 0 ] ; if ( typeof cb !== 'function' ) throw new Error ( 'Wrong arguments' ) ; done = cb ; index = index === - 1 ? - 1 : 0 ; limit = numOfLines || 1 ; var result ; if ( lines . length === 0 ) index = - 1 ; // fix - when paused on chunkSize if ( lines . length >= limit ) { result = lines . slice ( 0 , limit ) ; lines . splice ( 0 , limit ) ; return cb ( null , result ) ; } if ( rl . closed ) { result = lines . slice ( ) ; lines = [ ] ; return cb ( null , result ) ; } onData = cb ; rl . resume ( ) ; } rl . on ( 'line' , function ( line ) { index ++ ; line = lineParser ( line ) ; if ( headLine ) { var lineWithHeader = { } ; for ( var i = 0 ; i < headLine . length ; i ++ ) { lineWithHeader [ headLine [ i ] ] = line [ i ] ; } line = lineWithHeader ; } else if ( index === 0 && hasHeader ) { headLine = line ; index -- ; return ; } lines . push ( line ) ; if ( limit <= index + 1 ) { if ( onData ) { rl . pause ( ) ; var result = lines . slice ( 0 , limit ) ; lines . splice ( 0 , limit ) ; onData ( null , result ) ; onData = null ; } } } ) ; rl . on ( 'close' , function ( ) { rl . closed = true ; if ( onData ) { onData ( null , lines ) ; onData = null ; } } ) ; rl . pause ( ) ; return { read : getLines , close : function ( ) { rl . close ( ) ; } } ; }
read file by specified amount of lines, parse head line, perfect for CSV-like data files
@param   {String} file path
@param   {Object} opts read options and callbacks
@returns {Readline} nodejs readline interface
read file by specified amount of lines parse head line perfect for CSV - like data files