Skip to content

Commit e445456

Browse files
committed
Add CLI implementation
1 parent d7f7aca commit e445456

File tree

28 files changed

+285
-209
lines changed

28 files changed

+285
-209
lines changed

__tests__/__fixtures__/single-level/alpha.txt

Whitespace-only changes.

__tests__/__fixtures__/single-level/beta/.gitkeep

Whitespace-only changes.

__tests__/__snapshots__/tree.test.js.snap

Lines changed: 151 additions & 153 deletions
Large diffs are not rendered by default.

__tests__/tree.test.js

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const junk = require('junk');
33
const path = require('path');
44
const tree = require('../tree');
55

6-
const PATH_TO_TEST = '__tests__/fixtures';
6+
const PATH_TO_TEST = '__tests__/__fixtures__';
77
const dirs = fs.readdirSync(PATH_TO_TEST).filter(junk.not);
88

99
describe('tree', () => {
@@ -13,58 +13,74 @@ describe('tree', () => {
1313
});
1414
});
1515

16-
test('directoriesOnly', () => {
16+
test('allFiles', () => {
1717
dirs.forEach(dir => {
18-
expect(tree(path.join(PATH_TO_TEST, dir), {
19-
directoriesOnly: true,
20-
})).toMatchSnapshot();
18+
expect(
19+
tree(path.join(PATH_TO_TEST, dir), {
20+
allFiles: true,
21+
}),
22+
).toMatchSnapshot();
2123
});
2224
});
2325

24-
test('directoriesTrailingSlash', () => {
26+
test('dirOnly', () => {
2527
dirs.forEach(dir => {
26-
expect(tree(path.join(PATH_TO_TEST, dir), {
27-
directoriesTrailingSlash: true,
28-
})).toMatchSnapshot();
28+
expect(
29+
tree(path.join(PATH_TO_TEST, dir), {
30+
dirOnly: true,
31+
}),
32+
).toMatchSnapshot();
2933
});
3034
});
3135

32-
test('excludeDirs', () => {
36+
test('exclude', () => {
3337
dirs.forEach(dir => {
34-
expect(tree(path.join(PATH_TO_TEST, dir), {
35-
excludeDirs: ['beta'],
36-
})).toMatchSnapshot();
38+
expect(
39+
tree(path.join(PATH_TO_TEST, dir), {
40+
exclude: ['beta'],
41+
}),
42+
).toMatchSnapshot();
3743
});
3844
dirs.forEach(dir => {
39-
expect(tree(path.join(PATH_TO_TEST, dir), {
40-
excludeDirs: ['charlie'],
41-
})).toMatchSnapshot();
45+
expect(
46+
tree(path.join(PATH_TO_TEST, dir), {
47+
exclude: ['charlie'],
48+
}),
49+
).toMatchSnapshot();
4250
});
4351
dirs.forEach(dir => {
44-
expect(tree(path.join(PATH_TO_TEST, dir), {
45-
excludeDirs: ['beta', 'charlie'],
46-
})).toMatchSnapshot();
52+
expect(
53+
tree(path.join(PATH_TO_TEST, dir), {
54+
exclude: ['beta', 'charlie'],
55+
}),
56+
).toMatchSnapshot();
4757
});
4858
});
4959

50-
test('hideHiddenFiles', () => {
60+
test('maxDepth', () => {
5161
dirs.forEach(dir => {
52-
expect(tree(path.join(PATH_TO_TEST, dir), {
53-
hideHiddenFiles: false,
54-
})).toMatchSnapshot();
62+
expect(
63+
tree(path.join(PATH_TO_TEST, dir), {
64+
maxDepth: 1,
65+
}),
66+
).toMatchSnapshot();
5567
});
56-
});
57-
58-
test('maxDepth', () => {
5968
dirs.forEach(dir => {
60-
expect(tree(path.join(PATH_TO_TEST, dir), {
61-
maxDepth: 1,
62-
})).toMatchSnapshot();
69+
expect(
70+
tree(path.join(PATH_TO_TEST, dir), {
71+
maxDepth: 2,
72+
}),
73+
).toMatchSnapshot();
6374
});
75+
});
76+
77+
test('trailingSlash', () => {
6478
dirs.forEach(dir => {
65-
expect(tree(path.join(PATH_TO_TEST, dir), {
66-
maxDepth: 2,
67-
})).toMatchSnapshot();
79+
expect(
80+
tree(path.join(PATH_TO_TEST, dir), {
81+
trailingSlash: true,
82+
}),
83+
).toMatchSnapshot();
6884
});
6985
});
7086
});

bin/tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env node
2+
3+
const program = require('commander');
4+
5+
const package = require('../package.json');
6+
const tree = require('../tree');
7+
8+
const PATTERN_SEPARATOR = '|';
9+
10+
program
11+
.version(package.version)
12+
.option('-a, --all-files', 'All files, include hidden files, are printed.')
13+
.option('-d, --dir-only', 'List directories only.')
14+
.option(
15+
'-I, --exclude [patterns]',
16+
'Exclude files that match the pattern. | separates alternate patterns. ' +
17+
'Wrap your pattern in double quotes.',
18+
)
19+
.option('-L, --max-depth', 'Max display depth of the directory tree.')
20+
.option('--trailing-slash', 'Add a trailing slash behind directories.');
21+
22+
program.parse(process.argv);
23+
const path = program.args[0];
24+
25+
const options = {
26+
allFiles: program.allFiles,
27+
dirOnly: program.dirOnly,
28+
exclude: program.exclude,
29+
maxDepth: program.maxDepth,
30+
trailingSlash: program.trailingSlash,
31+
};
32+
33+
Object.keys(options).forEach(key => {
34+
if (options[key] === undefined) {
35+
delete options[key];
36+
}
37+
});
38+
39+
if (options.exclude) {
40+
options.exclude = options.exclude.split(PATTERN_SEPARATOR);
41+
}
42+
43+
console.log(tree(path, options));

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module.exports = {
2-
testPathIgnorePatterns: ['fixtures'],
2+
testPathIgnorePatterns: ['__fixtures__'],
33
};

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@
1212
"devDependencies": {
1313
"jest": "^22.4.3",
1414
"junk": "^2.1.0"
15+
},
16+
"dependencies": {
17+
"commander": "^2.15.1"
1518
}
1619
}

tree.js

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ const fs = require('fs');
22
const nodePath = require('path');
33

44
const DEFAULT_OPTIONS = {
5-
directoriesOnly: false,
6-
directoriesTrailingSlash: false,
7-
excludeDirs: [],
8-
hideHiddenFiles: true,
5+
allFiles: false,
6+
dirOnly: false,
7+
trailingSlash: false,
8+
exclude: [],
99
maxDepth: Number.POSITIVE_INFINITY,
1010
};
1111

@@ -17,10 +17,7 @@ const SYMBOLS = {
1717
VERTICAL: '│ ',
1818
};
1919

20-
// Files we want to exclude no matter what.
21-
const EXCLUSIONS = [
22-
'.DS_Store',
23-
];
20+
const EXCLUDED_PATTERNS = [/\.DS_Store/];
2421

2522
function isHiddenFile(filename) {
2623
return filename[0] === '.';
@@ -36,19 +33,29 @@ function print(
3633
) {
3734
const isFile = fs.lstatSync(path).isFile();
3835
const isDir = !isFile;
36+
3937
const lines = [];
38+
// Do not show these regardless.
39+
for (let i = 0; i < EXCLUDED_PATTERNS.length; i++) {
40+
if (EXCLUDED_PATTERNS[i].test(path)) {
41+
return lines;
42+
}
43+
}
44+
4045
// Handle directories only.
41-
if (isFile && options.directoriesOnly) {
46+
if (isFile && options.dirOnly) {
4247
return lines;
4348
}
4449

45-
// Handle excluded dirs.
46-
if (isDir && options.excludeDirs.includes(filename)) {
47-
return lines;
50+
// Handle excluded patterns.
51+
for (let i = 0; i < options.exclude.length; i++) {
52+
if (options.exclude[i].test(path)) {
53+
return lines;
54+
}
4855
}
4956

50-
// Handle hidden files.
51-
if (options.hideHiddenFiles && isHiddenFile(filename)) {
57+
// Handle showing of all files.
58+
if (!options.allFiles && isHiddenFile(filename)) {
5259
return lines;
5360
}
5461

@@ -63,7 +70,7 @@ function print(
6370
line.push(isLast ? SYMBOLS.LAST_BRANCH : SYMBOLS.BRANCH);
6471
}
6572
line.push(filename);
66-
if (isDir && options.directoriesTrailingSlash) {
73+
if (isDir && options.trailingSlash) {
6774
line.push('/');
6875
}
6976
lines.push(line.join(''));
@@ -73,18 +80,15 @@ function print(
7380
}
7481

7582
// Handle directory files.
76-
let files = fs.readdirSync(path)
77-
.filter(
78-
file => !EXCLUSIONS.includes(file)
79-
);
80-
if (options.directoriesOnly) {
83+
let files = fs.readdirSync(path);
84+
if (options.dirOnly) {
8185
// We have to filter here instead of at the start of the function
8286
// because we need to know how many non-directories there are before
8387
// we even start recursing.
8488
files = files.filter(file => {
8589
const filePath = nodePath.join(path, file);
8690
return !fs.lstatSync(filePath).isFile();
87-
})
91+
});
8892
}
8993

9094
files.forEach((file, index) => {
@@ -107,9 +111,17 @@ function print(
107111

108112
function tree(path, options) {
109113
const combinedOptions = { ...DEFAULT_OPTIONS, ...options };
110-
return print(nodePath.basename(path), path, 0, '', combinedOptions).join(
111-
'\n',
114+
combinedOptions.exclude = combinedOptions.exclude.map(
115+
pattern => new RegExp(pattern),
112116
);
117+
118+
return print(
119+
nodePath.basename(nodePath.join(process.cwd(), path)),
120+
path,
121+
0,
122+
'',
123+
combinedOptions,
124+
).join('\n');
113125
}
114126

115127
module.exports = tree;

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,10 @@ [email protected], combined-stream@~1.0.5:
545545
dependencies:
546546
delayed-stream "~1.0.0"
547547

548+
commander@^2.15.1:
549+
version "2.15.1"
550+
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
551+
548552
compare-versions@^3.1.0:
549553
version "3.1.0"
550554
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5"

0 commit comments

Comments
 (0)