lib/fsExt.js
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; // 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