├── .gitignore ├── LICENSE.APACHE2 ├── LICENSE.MIT ├── README.markdown ├── examples ├── generate.js ├── read-forward.js └── read-reverse.js ├── index.js ├── package-lock.json ├── package.json └── test ├── destroy.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | npm_debug.log 4 | -------------------------------------------------------------------------------- /LICENSE.APACHE2: -------------------------------------------------------------------------------- 1 | Apache License, Version 2.0 2 | 3 | Copyright (c) 2011 Dominic Tarr 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 Dominic Tarr 4 | 5 | Permission is hereby granted, free of charge, 6 | to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, 10 | merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom 12 | the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 22 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # fs-reverse 2 | 3 | read a file backwards. 4 | 5 | ``` js 6 | // Usage: node 7 | var fsR = require('fs-reverse') 8 | , filename = process.argv[2] 9 | 10 | fsR(filename, {}) 11 | .pipe(process.stdout) 12 | ``` 13 | 14 | the file will be read from the tail end, split into lines, and emitted in reverse order. 15 | 16 | default options are: 17 | 18 | ``` js 19 | { flags: 'r' //may only be 'r' or 'rx' 20 | , mode: 0666 21 | , bufferSize: 64 * 1024 22 | , matcher: '\n' //may be a string or regular expression. 23 | } 24 | ``` 25 | 26 | except for `matcher`, the options are a subset of `createReadStream` in [fs](http://nodejs.org/docs/api/fs.html) 27 | 28 | If matcher is a regular expression with a group, like `/(\r?\n)/` 29 | then the split pattern will be perserved and emitted as a chunk. 30 | 31 | # License 32 | 33 | MIT / Apache 2 34 | -------------------------------------------------------------------------------- /examples/generate.js: -------------------------------------------------------------------------------- 1 | var from = require('from') 2 | var es = require('event-stream') 3 | var fs = require('fs') 4 | var probe = require('probe-stream')() 5 | 6 | var fsr = require('..') 7 | var expected = [] 8 | var actual = [] 9 | var assert = require('assert') 10 | 11 | from(function (i) { 12 | if(i > 100000) return this.emit('end') 13 | this.emit('data',{ 14 | row: i, 15 | timestamp: Date.now(), 16 | date: new Date(), 17 | array: [Math.random(), Math.random(), Math.random()], 18 | object: { 19 | A: Math.random(), 20 | B: Math.random(), 21 | C: Math.random() 22 | } 23 | }) 24 | return true 25 | }) 26 | .pipe(es.stringify()) 27 | .pipe(probe.createProbe()) 28 | .on('end', probe.end.bind(probe)) 29 | .pipe(fs.createWriteStream('/tmp/test-reverse')) 30 | 31 | probe.pipe(es.log('STATS')) 32 | -------------------------------------------------------------------------------- /examples/read-forward.js: -------------------------------------------------------------------------------- 1 | var es = require('event-stream') 2 | var fs = require('fs') 3 | var probe = require('probe-stream')() 4 | fs.createReadStream('/tmp/test-reverse') 5 | .pipe(es.split('\n')) 6 | //.pipe(es.mapSync(function (b) { 7 | // return ''+b 8 | //})) 9 | .pipe(probe.createProbe()) 10 | .on('end', probe.end.bind(probe)) 11 | // .pipe(es.log('>>')) 12 | 13 | probe.pipe(es.log('STATS')) 14 | -------------------------------------------------------------------------------- /examples/read-reverse.js: -------------------------------------------------------------------------------- 1 | var fsr = require('..') 2 | var probe = require('probe-stream')() 3 | var es = require('event-stream') 4 | 5 | fsr('/tmp/test-reverse') 6 | // .pipe(es.log('>>')) 7 | .pipe(probe.createProbe()) 8 | .on('end', probe.end.bind(probe)) 9 | 10 | probe.pipe(es.log('STATS')) 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var from = require('from') 3 | 4 | module.exports = function (file, opts) { 5 | var stream, soFar = '' 6 | var matcher = opts && opts.matcher || '\n' 7 | var bufferSize = opts && opts.bufferSize || 1024 * 64 8 | var mode = opts && opts.mode || 438 // 0666 9 | var flags = opts && opts.flags || 'r' 10 | 11 | function onError (err) { 12 | stream.emit('error', err) 13 | stream.destroy() 14 | } 15 | 16 | var stat, fd, position 17 | 18 | if(!/rx?/.test(flags)) throw new Error("only flags 'r' and 'rx' are allowed") 19 | 20 | stream = from(function (i, next) { 21 | if(stream.destroyed) return 22 | if(i === 0) { 23 | var c = 2 24 | fs.stat(file, function (err, _stat) { 25 | if(err) return onError(err) 26 | stat = _stat 27 | position = stat.size 28 | if(!--c) read() 29 | }) 30 | fs.open(file, flags, mode, function (err, _fd) { 31 | if(err) return onError(err) 32 | fd = _fd 33 | stream.emit('open') 34 | if(!--c) read() 35 | }) 36 | 37 | } else read() 38 | 39 | function read () { 40 | if(stream.destroyed) return 41 | var length = Math.min(bufferSize, position) 42 | var b = new Buffer(length) 43 | position = position - length 44 | fs.read(fd, b, 0, length, position, function (err) { 45 | if(err) return onError(err) 46 | data(b) 47 | if(position > 0) return next() 48 | stream.emit('data', soFar) 49 | stream.emit('end') 50 | stream.destroy() 51 | }) 52 | } 53 | 54 | function data (buffer) { 55 | soFar = buffer + soFar 56 | var array = soFar.split(matcher) 57 | soFar = array.shift() 58 | while(array.length) { 59 | if(stream.destroyed) return 60 | stream.emit('data', array.pop()) 61 | } 62 | } 63 | }) 64 | 65 | stream.destroy = function () { 66 | if(stream.destroyed) return 67 | stream.readable = false 68 | stream.destroyed = true 69 | stream.ended = true 70 | function close () { 71 | fs.close(fd, function (err) { 72 | if(err) onError(err) 73 | stream.emit('close') 74 | }) 75 | } 76 | if(!stream.started) return stream.emit('close')//allow for destroy on first tick. 77 | if(!fd) stream.once('open', close) //destroy before file opens. 78 | else close() //any other situation. 79 | } 80 | return stream 81 | } 82 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fs-reverse", 3 | "version": "0.0.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "event-stream": { 8 | "version": "2.1.9", 9 | "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-2.1.9.tgz", 10 | "integrity": "sha1-Z32Mq9Ta4Arwo40nZ48wwgYNLPo=", 11 | "dev": true, 12 | "requires": { 13 | "from": "0.1.7", 14 | "optimist": "0.2.8", 15 | "through": "0.0.4" 16 | }, 17 | "dependencies": { 18 | "through": { 19 | "version": "0.0.4", 20 | "resolved": "https://registry.npmjs.org/through/-/through-0.0.4.tgz", 21 | "integrity": "sha1-C/Lw//r6rEusvFM2Z+mKrQC1iMg=", 22 | "dev": true 23 | } 24 | } 25 | }, 26 | "from": { 27 | "version": "0.1.7", 28 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 29 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" 30 | }, 31 | "macgyver": { 32 | "version": "1.10.1", 33 | "resolved": "https://registry.npmjs.org/macgyver/-/macgyver-1.10.1.tgz", 34 | "integrity": "sha1-sJ0VmdizbtWxb1lYlRXZ0UvC/Yg=", 35 | "dev": true 36 | }, 37 | "optimist": { 38 | "version": "0.2.8", 39 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz", 40 | "integrity": "sha1-6YGrfiaLRXlIWTtVZ0wJmoFcrDE=", 41 | "dev": true, 42 | "requires": { 43 | "wordwrap": "0.0.3" 44 | } 45 | }, 46 | "stream-spec": { 47 | "version": "0.3.6", 48 | "resolved": "https://registry.npmjs.org/stream-spec/-/stream-spec-0.3.6.tgz", 49 | "integrity": "sha1-L92sSge/Pp+JY8Z3prWmzCEVJV4=", 50 | "dev": true, 51 | "requires": { 52 | "macgyver": "1.10.1" 53 | } 54 | }, 55 | "through": { 56 | "version": "0.1.4", 57 | "resolved": "https://registry.npmjs.org/through/-/through-0.1.4.tgz", 58 | "integrity": "sha1-gFlXbYMwibPBjszTdYC/nMW1kTA=", 59 | "dev": true 60 | }, 61 | "wordwrap": { 62 | "version": "0.0.3", 63 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 64 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 65 | "dev": true 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fs-reverse", 3 | "version": "0.0.3", 4 | "description": "stream the lines of a file in reverse order!", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "from": "~0.1" 11 | }, 12 | "devDependencies": { 13 | "through": "~0.1.2", 14 | "event-stream": "~2.1.8", 15 | "stream-spec": "~0.3.0" 16 | }, 17 | "scripts": { 18 | "test": "node test/index.js && node test/destroy.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/dominictarr/fs-reverse.git" 23 | }, 24 | "keywords": [ 25 | "stream", 26 | "streaming", 27 | "reverse", 28 | "file", 29 | "read" 30 | ], 31 | "author": "Dominic Tarr (dominictarr.com)", 32 | "license": [ 33 | "MIT", 34 | "Apache2" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /test/destroy.js: -------------------------------------------------------------------------------- 1 | var spec = require('stream-spec') 2 | 3 | var fsr = require('..') 4 | 5 | function r (file) { 6 | var reverse = fsr(file).on('end', function () { 7 | console.log('ENDAOENUTHOENU') 8 | }) 9 | spec(reverse).readable({end: false}).validateOnExit() 10 | return reverse 11 | } 12 | 13 | r('/tmp/read-file-reversed').destroy() 14 | 15 | var r2 = r('/tmp/read-this-reverse') 16 | process.nextTick(function () { 17 | r2.destroy() 18 | }) 19 | var l = 67 20 | var r3 = r('/tmp/read-this-reverse') 21 | .on('data', function () { 22 | if(!--l) 23 | r3.destroy() 24 | }) 25 | 26 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var from = require('from') 2 | var es = require('event-stream') 3 | var through = require('through') 4 | var fs = require('fs') 5 | var spec = require('stream-spec') 6 | 7 | var fsr = require('..') 8 | var expected = [] 9 | var actual = [] 10 | var assert = require('assert') 11 | 12 | from(function (i) { 13 | if(i > 1000) return this.emit('end') 14 | this.emit('data', i + ' -- MAKE THE LINE LONGER.......' + new Date()) 15 | return true 16 | }) 17 | .pipe(through().on('data', function (data) { 18 | expected.unshift(data) 19 | })) 20 | .pipe(es.stringify()) 21 | .pipe(fs.createWriteStream('/tmp/read-this-reverse')) 22 | .on('close', function () { 23 | 24 | var reverse = fsr('/tmp/read-this-reverse', {bufferSize: 1024}) 25 | spec(reverse).readable().validateOnExit() 26 | 27 | reverse 28 | .pipe(es.parse()) 29 | .pipe(es.log('>>')) 30 | .pipe(through().on('data', function (data) { 31 | actual.push(data) 32 | })) 33 | .on('end', function () { 34 | assert.equal(actual.length, expected.length) 35 | assert.deepEqual(actual, expected) 36 | console.log('PASSED') 37 | }) 38 | }) 39 | --------------------------------------------------------------------------------