Skip to content

Commit a9a03b3

Browse files
committed
Merge pull request shelljs#47 from mstade/pushd-popd-dirs
Work in progress: pushd/popd/dirs
2 parents ac24cca + ab29588 commit a9a03b3

7 files changed

Lines changed: 632 additions & 1 deletion

File tree

scripts/run-tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env node
22
require('../global');
33

4+
var path = require('path');
5+
46
var failed = false;
57

68
//
@@ -34,7 +36,7 @@ if (!test('-f', JSHINT_BIN)) {
3436
cd(__dirname + '/../test');
3537
ls('*.js').forEach(function(file) {
3638
echo('Running test:', file);
37-
if (exec('node '+file).code !== 123) { // 123 avoids false positives (e.g. premature exit)
39+
if (exec('node ' + file, { cwd: path.resolve(__dirname, '/../test') }).code !== 123) { // 123 avoids false positives (e.g. premature exit)
3840
failed = true;
3941
echo('*** TEST FAILED! (missing exit code "123")');
4042
echo();

shell.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,199 @@ function _echo() {
862862
}
863863
exports.echo = _echo; // don't wrap() as it could parse '-options'
864864

865+
// Pushd/popd/dirs internals
866+
var _dirStack = [];
867+
868+
function _isStackIndex(index) {
869+
return (/^[\-+]\d+$/).test(index);
870+
}
871+
872+
function _parseStackIndex(index) {
873+
if (_isStackIndex(index)) {
874+
if (Math.abs(index) < _dirStack.length + 1) { // +1 for pwd
875+
return (/^-/).test(index) ? Number(index) - 1 : Number(index);
876+
} else {
877+
error(index + ': directory stack index out of range');
878+
}
879+
} else {
880+
error(index + ': invalid number');
881+
}
882+
}
883+
884+
function _actualDirStack() {
885+
return [process.cwd()].concat(_dirStack);
886+
}
887+
888+
//@
889+
//@ ### dirs([options | '+N' | '-N'])
890+
//@
891+
//@ Available options:
892+
//@ + `-c`: Clears the directory stack by deleting all of the elements.
893+
//@ + `-p`: Causes dirs to print the directory stack with one entry per line.
894+
//@ + `-v`: Causes dirs to print the directory stack with one entry per line, prefixing each entry with its index in the stack.
895+
//@
896+
//@ Arguments:
897+
//@ + `+N`: Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero.
898+
//@ + `-N`: Displays the Nth directory (counting from the right of the list printed by dirs when invoked without options), starting with zero.
899+
//@
900+
//@ Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified.
901+
//@
902+
//@ See also: pushd, popd
903+
function _dirs(options, index) {
904+
if (_isStackIndex(options)) {
905+
index = options;
906+
options = '';
907+
}
908+
909+
options = parseOptions(options, {
910+
'c' : 'clear',
911+
'v' : 'verbose',
912+
'p' : 'pretty'
913+
});
914+
915+
if (options['clear']) {
916+
return (_dirStack = []);
917+
}
918+
919+
var stack = _actualDirStack();
920+
921+
if (index) {
922+
index = _parseStackIndex(index);
923+
924+
if (index < 0) {
925+
index = stack.length + index;
926+
}
927+
928+
log(stack[index]);
929+
return stack[index];
930+
}
931+
932+
if (options['verbose'] || options['pretty']) {
933+
stack.forEach(function(dir, index) {
934+
if (options['verbose']) {
935+
log('', index, dir);
936+
} else {
937+
log(dir);
938+
}
939+
});
940+
} else {
941+
log(stack.join(' '));
942+
}
943+
944+
return stack;
945+
}
946+
exports.dirs = wrap("dirs", _dirs);
947+
948+
//@
949+
//@ ### pushd([options,] [dir | '-N' | '+N'])
950+
//@
951+
//@ Available options:
952+
//@ + `-n`: Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated.
953+
//@
954+
//@ Arguments
955+
//@ + `dir`: Makes the current working directory be the top of the stack, and then executes the equivalent of `cd dir`.
956+
//@ + `+N`: Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.
957+
//@ + `-N`: Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.
958+
//@
959+
//@ Examples:
960+
//@ ```javascript
961+
//@ // process.cwd() === '/usr'
962+
//@ pushd('/etc'); // Returns /etc /usr
963+
//@ pushd('+1'); // Returns /usr /etc
964+
//@ ```
965+
//@
966+
//@ Save the current directory on the top of the directory stack and then cd to `dir`. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack.
967+
function _pushd(options, dir) {
968+
if (_isStackIndex(options)) {
969+
dir = options;
970+
options = '';
971+
}
972+
973+
options = parseOptions(options, {
974+
'n' : 'no-cd'
975+
});
976+
977+
var dirs = _actualDirStack();
978+
979+
if (dir === '+0') {
980+
return dirs; // +0 is a noop
981+
} else if (!dir) {
982+
if (dirs.length > 1) {
983+
dirs = dirs.splice(1, 1).concat(dirs);
984+
} else {
985+
return error('no other directory');
986+
}
987+
} else if (_isStackIndex(dir)) {
988+
var n = _parseStackIndex(dir);
989+
dirs = dirs.slice(n).concat(dirs.slice(0, n));
990+
} else {
991+
if (options['no-cd']) {
992+
dirs.splice(1, 0, dir);
993+
} else {
994+
dirs.unshift(dir);
995+
}
996+
}
997+
998+
if (options['no-cd']) {
999+
dirs = dirs.slice(1);
1000+
} else {
1001+
dir = path.resolve(dirs.shift());
1002+
_cd('', dir);
1003+
}
1004+
1005+
_dirStack = dirs;
1006+
return _dirs('');
1007+
}
1008+
exports.pushd = wrap('pushd', _pushd);
1009+
1010+
//@
1011+
//@ ### popd([options,] ['-N' | '+N'])
1012+
//@
1013+
//@ Available options:
1014+
//@ + `-n`: Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated.
1015+
//@
1016+
//@ Arguments:
1017+
//@ + `+N`: Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero.
1018+
//@ + `-N`: Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero.
1019+
//@
1020+
//@ Examples:
1021+
//@ ```javascript
1022+
//@ echo(process.cwd()); // '/usr'
1023+
//@ pushd('/etc'); // '/etc /usr'
1024+
//@ echo(process.cwd()); // '/etc'
1025+
//@ popd(); // '/usr'
1026+
//@ echo(process.cwd()); // '/usr'
1027+
//@ ```
1028+
//@
1029+
//@ When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack.
1030+
function _popd(options, index) {
1031+
if (_isStackIndex(options)) {
1032+
index = options;
1033+
options = '';
1034+
}
1035+
1036+
options = parseOptions(options, {
1037+
'n' : 'no-cd'
1038+
});
1039+
1040+
if (!_dirStack.length) {
1041+
return error('directory stack empty');
1042+
}
1043+
1044+
index = _parseStackIndex(index || '+0');
1045+
1046+
if (options['no-cd'] || index > 0 || _dirStack.length + index === 0) {
1047+
index = index > 0 ? index - 1 : index;
1048+
_dirStack.splice(index, 1);
1049+
} else {
1050+
var dir = path.resolve(_dirStack.shift());
1051+
_cd('', dir);
1052+
}
1053+
1054+
return _dirs('');
1055+
}
1056+
exports.popd = wrap("popd", _popd);
1057+
8651058
//@
8661059
//@ ### exit(code)
8671060
//@ Exits the current process with the given exit code.

test/dirs.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
var shell = require('..');
2+
3+
var assert = require('assert'),
4+
path = require('path'),
5+
fs = require('fs');
6+
7+
// Node shims for < v0.7
8+
fs.existsSync = fs.existsSync || path.existsSync;
9+
10+
shell.config.silent = true;
11+
12+
function record(test, assert) {
13+
var oldSilent = shell.config.silent;
14+
shell.config.silent = false;
15+
16+
var _outWrite = process.stdout.write,
17+
_errWrite = process.stderr.write,
18+
_stdout = '',
19+
_stderr = '';
20+
21+
process.stdout.write = function(data) {
22+
_stdout += data;
23+
};
24+
25+
process.stderr.write = function(data) {
26+
_stderr += data;
27+
};
28+
29+
test();
30+
process.stdout.write = _outWrite;
31+
process.stderr.write = _errWrite;
32+
shell.config.silent = oldSilent;
33+
assert(_stdout, _stderr);
34+
}
35+
36+
var root = path.resolve();
37+
38+
shell.pushd('resources/pushd');
39+
shell.pushd('a');
40+
41+
var trail = [
42+
path.resolve(root, 'resources/pushd/a'),
43+
path.resolve(root, 'resources/pushd'),
44+
root
45+
];
46+
47+
// One line listing
48+
record(function() {
49+
assert.deepEqual(shell.dirs(), trail);
50+
}, function(stdout, stderr) {
51+
assert(!stderr);
52+
assert.equal(stdout, shell.dirs().join(' ') + '\n');
53+
});
54+
55+
// Multi line listing
56+
record(function() {
57+
assert.deepEqual(shell.dirs('-p'), trail);
58+
}, function(stdout, stderr) {
59+
assert(!stderr);
60+
assert.equal(stdout, shell.dirs().join('\n') + '\n');
61+
});
62+
63+
// Verbose listing
64+
record(function() {
65+
assert.deepEqual(shell.dirs('-v'), trail);
66+
}, function(stdout, stderr) {
67+
assert(!stderr);
68+
69+
var format = function(dir, index) {
70+
return ' ' + index + ' ' + dir;
71+
};
72+
73+
assert.equal(stdout, shell.dirs().map(format).join('\n') + '\n');
74+
});
75+
76+
// Single items
77+
assert.equal(shell.dirs('+0'), trail[0]);
78+
assert.equal(shell.dirs('+1'), trail[1]);
79+
assert.equal(shell.dirs('+2'), trail[2]);
80+
assert.equal(shell.dirs('-0'), trail[2]);
81+
assert.equal(shell.dirs('-1'), trail[1]);
82+
assert.equal(shell.dirs('-2'), trail[0]);
83+
84+
// Clearing items
85+
assert.deepEqual(shell.dirs('-c'), []);
86+
assert(!shell.error());
87+
88+
shell.exit(123);

0 commit comments

Comments
 (0)