Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Larry Gordon committed Jul 7, 2014
1 parent 488d8ba commit 2121591
Show file tree
Hide file tree
Showing 8 changed files with 656 additions and 0 deletions.
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# nodetree

> list contents of directories in a tree-like format similar to the [Tree Command](http://mama.indstate.edu/users/ice/tree/).

## SYNOPSIS

`nodetree` \[`-ad`\] \[`-L` \<level>] \[`--noreport`] \[`--version`] \[`--help`] \[`--prune`] \[\<directory> ...]


## DESCRIPTION

_Nodetree_ is a recursive directory listing program that produces a depth indented listing of files. With no arguments, _nodetree_ lists the files in the current directory. When directory arguments are given, _nodetree_ lists all the files and/or directories found in the given directories each in turn. Upon completion of listing all files/directories found, _nodetree_ returns the total number of files and/or directories listed. _Nodetree_ is heavily inspired by the [Tree Command](http://mama.indstate.edu/users/ice/tree/).

## INSTALL

```sh
$ npm install --save nodetree
```

## NODE

With defaults:
```js
var nodetree = require('nodetree');
nodetree(process.cwd());
```

With all options set:
```js
var nodetree = require('nodetree');
nodetree(process.cwd(), {
all: false,
directories: false,
level: 2,
prune: false,
noreport: false
});
```


## CLI

```sh
$ npm install -g nodetree
```

```sh
$ man nodetree
```

## OPTIONS

_Nodetree_ understands the following command line switches:


## NODE OPTIONS

### nodetree(basepath, options)

#### options.all
Type: `Boolean`
Default: `false`
See cli option `-a` below.

#### options.directories ####
Type: `Boolean`
Default: `false`
See cli option `-d` below.

#### options.level
Type: `int`
Default: `null`
See cli option `-L` below.

#### options.prune
Type: `Boolean`
Default: `false`
See cli option `--prune` below.

#### options.noreport
Type: `Boolean`
Default: `false`
See cli option `--noreport` below.

## CLI OPTIONS

* `-a`:
All files are printed. By default tree does not print hidden files (those beginning with a dot '.'). In no event does tree print the file system constructs '.' (current directory) and '..' (previous directory).

* `-d`:
List directories only.

* `-L` <level>:
Max display depth of the directory tree.

* `--prune`:
Makes tree prune empty directories from the output.

* `--noreport`:
Omits printing of the file and directory report at the end of the tree listing.

* `--version`:
Outputs the version of nodetree.

## AUTHOR

Written by Larry Gordon


## COPYRIGHT

[The MIT License (MIT)](http://psyrendust.mit-license.org/2014)
63 changes: 63 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env node
'use strict';

var _ = require('lodash');
var nodetree = require('./');
var nopt = require('nopt');
var pkg = require('./package.json');
var stdin = require('get-stdin');

var knownOpts = {
'all': Boolean,
'directories': Boolean,
'help': Boolean,
'level': Number,
'noreport': Boolean,
'version': Boolean
};
var shortHands = {
'a': ['--all'],
'd': ['--directories'],
'h': ['--help'],
'L': ['--level'],
'v': ['--version']
};

var parsed = nopt(knownOpts, shortHands, process.argv, 2);
var basepath = parsed.argv.remain[0];
var options = _.extend({}, parsed);
options = _.extend(options, { cwd: process.cwd() });
delete options.argv;

function help() {
console.log([
'man nodetree'
].join('\n'));
}

function init(basepath, options) {
if (!basepath) {
// Default to cwd if no path is given.
basepath = '.';
}
return nodetree(basepath, options);
}

if (options.help) {
help();
return;
}

if (options.version) {
console.log(pkg.version);
return;
}

if (process.stdin.isTTY) {
init(basepath, options);
} else {
// Accept basepath from stdin.
// Example:
// $ echo $(pwd) | nodetree
stdin(init);
}
206 changes: 206 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#!/usr/bin/env node
'use strict';

var _ = require('lodash');
var fs = require('fs');
var path = require('path');

var chevron = {
'node': '├── ',
'pipe': '│   ',
'last': '└── ',
'indent': ' '
};
var defaults = {
all: false,
directories: false,
level: null,
prune: false,
noreport: false
};
var dotRegex = /^\.$/;
var hiddenRegex = /^\./;
var newlineRegex = /[\n\r]*/g;


// -----------------------------------------------------------------------------
/**
* List contents of directories in a tree-like format.
* @method Nodetree
*/
function Nodetree(basepath, opts) {
// Setup vars
// ---------------------------------------------------------------------------
var dirCount = 0;
var fileCount = 0;
var isWalking = 0;
var options = _.extend(defaults, opts);
var resultsTree = [];
var startpath = '';

normalizeStartpath(basepath);


// Start tree walking
// ---------------------------------------------------------------------------
if (fs.existsSync(startpath) && fs.statSync(startpath).isDirectory()) {
console.log(basepath);
walk(startpath, []);
} else {
console.log(basepath, '[error opening dir]');
}


// Helper functions
// ---------------------------------------------------------------------------
/**
* Outputs results when nodetree has completed it's recursive walk.
* @method checkIfComplete
*/
function checkIfComplete() {
isWalking -= 1;
if (isWalking <= 0) {
printResults();
}
}


/**
* Log to stdout the current branch of the tree walk.
* @method log
* @param {String} file The file or folder name of this branch.
* @param {Array} depth The current depth relative to the startpath.
* @param {Boolean} parentHasNextSibling Does the parent folder have a next sibling.
*/
function log(file, depth, parentHasNextSibling) {
if (!parentHasNextSibling && depth.length > 1) {
// Replace a pipe with an indent if the parent does not have a next sibling.
depth[depth.length-2] = chevron.indent;
}
resultsTree.push(depth.join('') + file + '\n');
}


/**
* Normalizes the basepath.
* @method normalizeStartpath
*/
function normalizeStartpath() {
startpath = basepath;
if (typeof basepath !== 'string') {
throw new TypeError('Expected a string');
}
if (!!startpath.match(dotRegex)) {
// Set startpath to options.cwd if basepath is '.'
startpath = options.cwd;
}
// Remove newline characters
startpath = startpath.replace(newlineRegex, '');
}


/**
* Print to stdout the results of the tree walk and the total directories and files found.
* @method printResults
*/
function printResults() {
if (!options.noreport) {
resultsTree.push('\n' + dirCount + ' directories');
if (!options.directories) {
resultsTree.push(', ' + fileCount + ' files');
}
}
console.log(resultsTree.join(''));
}


// Walker functions
// ---------------------------------------------------------------------------
/**
* Filter out the direct children of basepath based on the configured options.
* @method filterChildren
* @param {String} basepath The parent directory to filter.
* @return {Array} An array of filtered file and folder names.
*/
function filterChildren(basepath) {
var children = fs.readdirSync(basepath);

if (!options.all) {
// Show hidden files
children = children.filter(function optionAll(child) {
return !child.match(hiddenRegex);
});
}

if (options.directories) {
// Only show directories
children = children.filter(function optionDirectories(child) {
return fs.statSync(path.join(basepath, child)).isDirectory();
});
}

if (options.prune) {
// Exclude empty directories
children = children.filter(function optionPrune(child) {
var childpath = path.join(basepath, child);
if (fs.statSync(childpath).isDirectory()) {
return (fs.readdirSync(childpath).length > 0);
}
return true;
});
}

return children;
}


/**
* Walk the basepath and log the results to stdout.
* @method walk
* @param {String} basepath The directory to walk.
* @param {Array} depth An array of chevrons that represets the current depth of this branch.
* @param {Boolean} parentHasNextSibling Does the parent folder have a next sibling.
*/
function walk(basepath, depth, parentHasNextSibling) {
isWalking += 1;
var children = filterChildren(basepath);
var childrenLen = children.length-1;
var shouldContinue = true;

if (!!options.level && depth.length >= options.level) {
// Only walk options.level deep.
shouldContinue = false;
}

if (shouldContinue) {
children.forEach(function walkChildren(child, index) {
var newdepth = !!depth ? depth.slice(0) : [];
var isLast = (index >= childrenLen);
var childpath = path.join(basepath, child);
var stats = fs.statSync(childpath);

if (isLast) {
newdepth.push(chevron.last);
} else {
newdepth.push(chevron.node);
}

log(child, newdepth, parentHasNextSibling);

if (stats.isDirectory()) {
dirCount += 1;
if (!isLast) {
newdepth.pop();
newdepth.push(chevron.pipe);
}
walk(childpath, newdepth, !isLast);
} else {
fileCount += 1;
}
});
}
checkIfComplete();
}
}

module.exports = Nodetree;
Loading

0 comments on commit 2121591

Please sign in to comment.