diff --git a/README.md b/README.md index 66714f0..f937677 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,36 @@ containing the files if more than one file is given (a new line character is introduced between each file). +### head([{'-n', \},] file [, file ...]) +### head([{'-n', \},] file_array) + +Examples: + +```javascript +var str = head({'-n', 1}, 'file*.txt'); +var str = head('file1', 'file2'); +var str = head(['file1', 'file2']); // same as above +``` + +Output the first 10 lines of a file (or the first `` if `-n` is +specified) + + +### tail([{'-n', \},] file [, file ...]) +### tail([{'-n', \},] file_array) + +Examples: + +```javascript +var str = tail({'-n', 1}, 'file*.txt'); +var str = tail('file1', 'file2'); +var str = tail(['file1', 'file2']); // same as above +``` + +Output the last 10 lines of a file (or the last `` if `-n` is +specified) + + ### ShellString.prototype.to(file) Examples: @@ -345,7 +375,7 @@ using the given search regex and replacement string or function. Returns the new ### sort([options,] file [, file ...]) -### sed([options,] file_array) +### sort([options,] file_array) Available options: + `-r`: Reverse the result of comparisons diff --git a/shell.js b/shell.js index c760489..cbeb920 100644 --- a/shell.js +++ b/shell.js @@ -62,6 +62,10 @@ exports.cat = common.wrap('cat', _cat, {idx: 1}); var _head = require('./src/head'); exports.head = common.wrap('head', _head, {idx: 1}); +//@include ./src/tail +var _tail = require('./src/tail'); +exports.tail = common.wrap('tail', _tail, {idx: 1}); + // The below commands have been moved to common.ShellString(), and are only here // for generating the docs //@include ./src/to diff --git a/src/common.js b/src/common.js index e6d6011..dbca994 100644 --- a/src/common.js +++ b/src/common.js @@ -101,7 +101,7 @@ var ShellString = function (stdout, stderr, code) { that.code = code; that.to = function() {wrap('to', _to, {idx: 1}).apply(that.stdout, arguments); return that;}; that.toEnd = function() {wrap('toEnd', _toEnd, {idx: 1}).apply(that.stdout, arguments); return that;}; - ['cat', 'head', 'sed', 'sort', 'grep', 'exec'].forEach(function (cmd) { + ['cat', 'head', 'sed', 'sort', 'tail', 'grep', 'exec'].forEach(function (cmd) { that[cmd] = function() {return shell[cmd].apply(that.stdout, arguments);}; }); return that; diff --git a/src/sort.js b/src/sort.js index 32f24ae..514a90f 100644 --- a/src/sort.js +++ b/src/sort.js @@ -32,7 +32,7 @@ function numericalCmp(a, b) { //@ //@ ### sort([options,] file [, file ...]) -//@ ### sed([options,] file_array) +//@ ### sort([options,] file_array) //@ Available options: //@ //@ + `-r`: Reverse the result of comparisons diff --git a/src/tail.js b/src/tail.js new file mode 100644 index 0000000..c54daea --- /dev/null +++ b/src/tail.js @@ -0,0 +1,65 @@ +var common = require('./common'); +var fs = require('fs'); + +//@ +//@ ### tail([{'-n', \},] file [, file ...]) +//@ ### tail([{'-n', \},] file_array) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var str = tail({'-n', 1}, 'file*.txt'); +//@ var str = tail('file1', 'file2'); +//@ var str = tail(['file1', 'file2']); // same as above +//@ ``` +//@ +//@ Output the last 10 lines of a file (or the last `` if `-n` is +//@ specified) +function _tail(options, files) { + options = common.parseOptions(options, { + 'n': 'numLines' + }); + var tail = []; + var pipe = common.readFromPipe(this); + + if (!files && !pipe) + common.error('no paths given'); + + var idx = 1; + if (options.numLines === true) { + idx = 2; + options.numLines = Number(arguments[1]); + } else if (options.numLines === false) { + options.numLines = 10; + } + options.numLines = -1 * Math.abs(options.numLines); + files = [].slice.call(arguments, idx); + + if (pipe) + files.unshift('-'); + + var shouldAppendNewline = false; + files.forEach(function(file) { + if (!fs.existsSync(file) && file !== '-') { + common.error('no such file or directory: ' + file, true); + return; + } + + var contents = file === '-' ? pipe : fs.readFileSync(file, 'utf8'); + + var lines = contents.split('\n'); + if (lines[lines.length-1] === '') { + lines.pop(); + shouldAppendNewline = true; + } else { + shouldAppendNewline = false; + } + + tail = tail.concat(lines.slice(options.numLines)); + }); + + if (shouldAppendNewline) + tail.push(''); // to add a trailing newline once we join + return new common.ShellString(tail.join('\n'), common.state.error, common.state.errorCode); +} +module.exports = _tail; diff --git a/test/config.js b/test/config.js index 475591a..ea024a4 100644 --- a/test/config.js +++ b/test/config.js @@ -52,10 +52,11 @@ child.exec(JSON.stringify(process.execPath)+' '+file, function(err, stdout) { // Expands to directories by default var result = common.expand(['resources/*a*']); -assert.equal(result.length, 4); +assert.equal(result.length, 5); assert.ok(result.indexOf('resources/a.txt') > -1); assert.ok(result.indexOf('resources/badlink') > -1); assert.ok(result.indexOf('resources/cat') > -1); +assert.ok(result.indexOf('resources/head') > -1); assert.ok(result.indexOf('resources/external') > -1); // Check to make sure options get passed through (nodir is an example) diff --git a/test/tail.js b/test/tail.js new file mode 100644 index 0000000..de37f17 --- /dev/null +++ b/test/tail.js @@ -0,0 +1,105 @@ +var shell = require('..'); + +var assert = require('assert'); +var fs = require('fs'); + +shell.config.silent = true; + +shell.rm('-rf', 'tmp'); +shell.mkdir('tmp'); + +var result; + +// +// Invalids +// + +result = shell.tail(); +assert.ok(shell.error()); +assert.equal(result.code, 1); + +assert.equal(fs.existsSync('/asdfasdf'), false); // sanity check +result = shell.tail('/adsfasdf'); // file does not exist +assert.ok(shell.error()); +assert.equal(result.code, 1); + +// +// Valids +// + +var bottomOfFile1 = ['file1 50', 'file1 49', 'file1 48', 'file1 47', 'file1 46', + 'file1 45', 'file1 44', 'file1 43', 'file1 42', 'file1 41', + 'file1 40', 'file1 39', 'file1 38', 'file1 37', 'file1 36', + 'file1 35', 'file1 34', 'file1 33', 'file1 32', 'file1 31']; +var bottomOfFile2 = ['file2 50', 'file2 49', 'file2 48', 'file2 47', 'file2 46', + 'file2 45', 'file2 44', 'file2 43', 'file2 42', 'file2 41', + 'file2 40', 'file2 39', 'file2 38', 'file2 37', 'file2 36', + 'file2 35', 'file2 34', 'file2 33', 'file2 32', 'file2 31']; + +// simple +result = shell.tail('resources/head/file1.txt'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile1.slice(0, 10).reverse().join('\n')+'\n'); + +// multiple files +result = shell.tail('resources/head/file2.txt', 'resources/head/file1.txt'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile2.slice(0, 10).reverse().concat( + bottomOfFile1.slice(0, 10).reverse() + ).join('\n')+'\n'); + +// multiple files, array syntax +result = shell.tail(['resources/head/file2.txt', 'resources/head/file1.txt']); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile2.slice(0, 10).reverse().concat( + bottomOfFile1.slice(0, 10).reverse() + ).join('\n')+'\n'); + +// reading more lines than are in the file (no trailing newline) +result = shell.tail('resources/file2', 'resources/file1'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, 'test2\ntest1'); // these files only have one line (no \n) + +// reading more lines than are in the file (with trailing newline) +result = shell.tail('resources/head/shortfile2', 'resources/head/shortfile1'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, 'short2\nshort1\n'); // these files only have one line (with \n) + +// Globbed file +result = shell.tail('resources/head/file?.txt'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile1.slice(0, 10).reverse().concat( + bottomOfFile2.slice(0, 10).reverse() + ).join('\n')+'\n'); + +// With `'-n' ` option +result = shell.tail('-n', 4, 'resources/head/file2.txt', 'resources/head/file1.txt'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile2.slice(0, 4).reverse().concat( + bottomOfFile1.slice(0, 4).reverse() + ).join('\n')+'\n'); + +// With `{'-n': }` option +result = shell.tail({'-n': 4}, 'resources/head/file2.txt', 'resources/head/file1.txt'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile2.slice(0, 4).reverse().concat( + bottomOfFile1.slice(0, 4).reverse() + ).join('\n')+'\n'); + +// negative values are the same as positive values +result = shell.tail('-n', -4, 'resources/head/file2.txt', 'resources/head/file1.txt'); +assert.equal(shell.error(), null); +assert.equal(result.code, 0); +assert.equal(result, bottomOfFile2.slice(0, 4).reverse().concat( + bottomOfFile1.slice(0, 4).reverse() + ).join('\n')+'\n'); + +shell.exit(123);