1 | (function() {
|
2 | "use strict";
|
3 |
|
4 | var fs = require('fs'),
|
5 | StringDecoder = require('string_decoder').StringDecoder;
|
6 |
|
7 | function createLineReader(fd, options, cb) {
|
8 | if (options instanceof Function) {
|
9 | cb = options;
|
10 | options = undefined;
|
11 | }
|
12 | if (!options) options = {};
|
13 |
|
14 | var filePosition = 0,
|
15 | encoding = options.encoding || 'utf8',
|
16 | separator = options.separator || /\r\n?|\n/,
|
17 | bufferSize = options.bufferSize || 1024,
|
18 | buffer = new Buffer(bufferSize),
|
19 | bufferStr = '',
|
20 | decoder = new StringDecoder(encoding),
|
21 | closed = false,
|
22 | eof = false,
|
23 | separatorIndex = -1,
|
24 | separatorLen;
|
25 |
|
26 | var findSeparator;
|
27 |
|
28 | if (separator instanceof RegExp) {
|
29 | findSeparator = function() {
|
30 | var result = separator.exec(bufferStr);
|
31 | if (result && (result.index + result[0].length < bufferStr.length || eof)) {
|
32 | separatorIndex = result.index;
|
33 | separatorLen = result[0].length;
|
34 | } else {
|
35 | separatorIndex = -1;
|
36 | }
|
37 | }
|
38 | } else {
|
39 | separatorLen = separator.length;
|
40 | findSeparator = function() {
|
41 | separatorIndex = bufferStr.indexOf(separator);
|
42 | }
|
43 | }
|
44 |
|
45 | function _fd() {
|
46 | return fd;
|
47 | }
|
48 |
|
49 | function close(cb) {
|
50 | if (!closed) {
|
51 | fs.close(fd, cb);
|
52 | closed = true;
|
53 | }
|
54 | }
|
55 |
|
56 | function isOpen() {
|
57 | return !closed;
|
58 | }
|
59 |
|
60 | function isClosed() {
|
61 | return closed;
|
62 | }
|
63 |
|
64 | function readToSeparator(cb) {
|
65 | function readChunk() {
|
66 | fs.read(fd, buffer, 0, bufferSize, filePosition, function(err, bytesRead) {
|
67 | if (err) {
|
68 | return cb(err);
|
69 | }
|
70 |
|
71 | if (bytesRead < bufferSize) {
|
72 | eof = true;
|
73 | }
|
74 |
|
75 | filePosition += bytesRead;
|
76 |
|
77 | bufferStr += decoder.write(buffer.slice(0, bytesRead));
|
78 |
|
79 | findSeparator();
|
80 |
|
81 | if (bytesRead && separatorIndex < 0 && !eof) {
|
82 | readChunk();
|
83 | } else {
|
84 | cb();
|
85 | }
|
86 | });
|
87 | }
|
88 |
|
89 | readChunk();
|
90 | }
|
91 |
|
92 | function hasNextLine() {
|
93 | return bufferStr.length > 0 || !eof;
|
94 | }
|
95 |
|
96 | function nextLine(cb) {
|
97 | if (closed) {
|
98 | return cb(new Error('LineReader has been closed'));
|
99 | }
|
100 |
|
101 | function getLine(err) {
|
102 | if (err) {
|
103 | return cb(err);
|
104 | }
|
105 |
|
106 | if (separatorIndex < 0 && eof) {
|
107 | separatorIndex = bufferStr.length;
|
108 | }
|
109 | var ret = bufferStr.substring(0, separatorIndex);
|
110 |
|
111 | bufferStr = bufferStr.substring(separatorIndex + separatorLen);
|
112 | separatorIndex = -1;
|
113 | cb(undefined, ret);
|
114 | }
|
115 |
|
116 | findSeparator();
|
117 |
|
118 | if (separatorIndex < 0) {
|
119 | if (eof) {
|
120 | if (hasNextLine()) {
|
121 | separatorIndex = bufferStr.length;
|
122 | getLine();
|
123 | } else {
|
124 | return cb(new Error('No more lines to read.'));
|
125 | }
|
126 | } else {
|
127 | readToSeparator(getLine);
|
128 | }
|
129 | } else {
|
130 | getLine();
|
131 | }
|
132 | }
|
133 |
|
134 | readToSeparator(function(err) {
|
135 | if (err) {
|
136 | return close(function(err2) {
|
137 | return cb(err || err2);
|
138 | });
|
139 | }
|
140 | return cb(undefined, {
|
141 | hasNextLine: hasNextLine,
|
142 | nextLine: nextLine,
|
143 | close: close,
|
144 | isOpen: isOpen,
|
145 | isClosed: isClosed,
|
146 | fd: _fd,
|
147 | });
|
148 | });
|
149 | }
|
150 |
|
151 | function open(filename, options, cb) {
|
152 | if (options instanceof Function) {
|
153 | cb = options;
|
154 | options = undefined;
|
155 | }
|
156 |
|
157 | fs.open(filename, 'r', parseInt('666', 8), function(err, fd) {
|
158 | if (err) {
|
159 | return cb(err);
|
160 | }
|
161 |
|
162 | createLineReader(fd, options, cb);
|
163 | });
|
164 | }
|
165 |
|
166 | function eachLine(filename, options, iteratee, cb) {
|
167 | if (options instanceof Function) {
|
168 | cb = iteratee;
|
169 | iteratee = options;
|
170 | options = undefined;
|
171 | }
|
172 | var finalFn,
|
173 | asyncIteratee = iteratee.length == 3;
|
174 |
|
175 | var theReader;
|
176 | var getReaderCb;
|
177 |
|
178 | open(filename, options, function(err, reader) {
|
179 | theReader = reader;
|
180 | if (getReaderCb) {
|
181 | getReaderCb(reader);
|
182 | }
|
183 |
|
184 | if (err) {
|
185 | if (cb) cb(err);
|
186 | return;
|
187 | }
|
188 |
|
189 | function finish(err) {
|
190 | reader.close(function(err2) {
|
191 | if (cb) cb(err || err2);
|
192 | });
|
193 | }
|
194 |
|
195 | function newRead() {
|
196 | if (reader.hasNextLine()) {
|
197 | setImmediate(readNext);
|
198 | } else {
|
199 | finish();
|
200 | }
|
201 | }
|
202 |
|
203 | function continueCb(continueReading) {
|
204 | if (continueReading !== false) {
|
205 | newRead();
|
206 | } else {
|
207 | finish();
|
208 | }
|
209 | }
|
210 |
|
211 | function readNext() {
|
212 | reader.nextLine(function(err, line) {
|
213 | if (err) {
|
214 | finish(err);
|
215 | }
|
216 |
|
217 | var last = !reader.hasNextLine();
|
218 |
|
219 | if (asyncIteratee) {
|
220 | iteratee(line, last, continueCb);
|
221 | } else {
|
222 | if (iteratee(line, last) !== false) {
|
223 | newRead();
|
224 | } else {
|
225 | finish();
|
226 | }
|
227 | }
|
228 | });
|
229 | }
|
230 |
|
231 | newRead();
|
232 | });
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | return {
|
238 | getReader: function(cb) {
|
239 | if (theReader) {
|
240 | cb(theReader);
|
241 | } else {
|
242 | getReaderCb = cb;
|
243 | }
|
244 | }
|
245 | };
|
246 | }
|
247 |
|
248 | module.exports.open = open;
|
249 | module.exports.eachLine = eachLine;
|
250 | }());
|