Skip to content

Commit

Permalink
Add support for file finding, ignoring
Browse files Browse the repository at this point in the history
* Add support for finding markdown and text files when
  given directories;
* Add support for `.alexignore` files to exclude
  files from being found.

Closes GH-49.
Closes GH-59.
  • Loading branch information
wooorm committed Nov 19, 2015
1 parent a826c0c commit 9646363
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 11 deletions.
7 changes: 7 additions & 0 deletions .alexignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Both `node_modules` and `bower_components` are
# ignored by default:
# node_modules/
# bower_components/

components/
example.md
204 changes: 194 additions & 10 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,56 @@
* Dependencies.
*/

var fs = require('fs');
var bail = require('bail');
var notifier = require('update-notifier');
var meow = require('meow');
var globby = require('globby');
var hasMagic = require('glob').hasMagic;
var minimatch = require('minimatch');
var getStdin = require('get-stdin');
var findDown = require('vfile-find-down');
var findUp = require('vfile-find-up');
var format = require('vfile-reporter');
var toFile = require('to-vfile');
var alex = require('./');
var pack = require('./package');

/*
* Methods.
*/

var readFile = fs.readFileSync;
var stat = fs.statSync;

/*
* Constants.
*/

var expextPipeIn = !process.stdin.isTTY;
var IGNORE = '.alexignore';
var ENCODING = 'utf-8';
var BACKSLASH = '\\';
var SLASH = '/';
var CD = './';
var HASH = '#';
var EMPTY = '';

var defaultIgnore = [
'node_modules/',
'bower_components/'
];

var extensions = [
'txt',
'text',
'md',
'markdown',
'mkd',
'mkdn',
'mkdown',
'ron'
];

/*
* Update messages.
Expand All @@ -46,7 +81,7 @@ notifier({

var cli = meow({
'help': [
'Usage: alex [<file> ...] [-w, --why] [-t, --text]',
'Usage: alex [<file> | <dir> ...] [-w, --why] [-t, --text]',
'',
'Options:',
'',
Expand All @@ -72,8 +107,8 @@ var exit = 0;
var result = [];
var why = Boolean(cli.flags.w || cli.flags.why);
var fn = Boolean(cli.flags.t || cli.flags.text) ? 'text' : 'markdown'
var input = cli.input.length ? cli.input : [
'{docs/**/,doc/**/,}*.{md,markdown,mkd,mkdn,mkdown,ron,txt,text}'
var globs = cli.input.length ? cli.input : [
'{docs/**/,doc/**/,}*.{' + extensions.join(',') + '}'
];

/*
Expand Down Expand Up @@ -119,18 +154,167 @@ if (!cli.input.length && expextPipeIn) {
return;
}

/*
* Handle patterns.
/**
* Check if `file` matches `pattern`.
*
* @example
* match('baz.md', '*.md'); // true
*
* @param {string} filePath - File location.
* @param {string} pattern - Glob pattern.
* @return {boolean}
*/
function match(filePath, pattern) {
return minimatch(filePath, pattern) ||
minimatch(filePath, pattern + '/**');
}

globby(input).then(function (filePaths) {
filePaths.forEach(function (filePath) {
toFile.read(filePath, function (err, file) {
bail(err);
/**
* Check if `filePath` is matched included in `patterns`.
*
* @example
* shouldIgnore(['node_modules/'], 'node_modules/foo'); // true
*
* @param {Array.<string>} patterns - Glob patterns.
* @param {string} filePath - File location.
* @return {boolean}
*/
function shouldIgnore(patterns, filePath) {
var normalized = filePath.replace(BACKSLASH, SLASH).replace(CD, EMPTY);

return patterns.reduce(function (isIgnored, pattern) {
var isNegated = pattern.charAt(0) === '!';

if (isNegated) {
pattern = pattern.slice(1);
}

if (pattern.indexOf(CD) === 0) {
pattern = pattern.slice(CD.length);
}

return match(normalized, pattern) ? !isNegated : isIgnored;
}, false);
}

/**
* Load an ignore file.
*
* @param {string} ignore - File location.
* @return {Array.<string>} - Patterns.
*/
function loadIgnore(ignore) {
return readFile(ignore, ENCODING).split(/\r?\n/).filter(function (value) {
var line = value.trim();

return line.length && line.charAt(0) !== HASH;
});
}

/**
* Factory to create a file filter based on bound ignore
* patterns.
*
* @param {Array.<string>} ignore - Ignore patterns.
* @param {Array.<string>} given - List of given file paths.
* @return {Function} - Filter.
*/
function filterFactory(ignore, given) {
/**
* Check whether a virtual file is applicable.
*
* @param {VFile} file - Virtual file.
*/
return function (file) {
var filePath = file.filePath();
var extension = file.extension;

if (shouldIgnore(ignore, filePath)) {
return findDown.SKIP;
}

return given.indexOf(filePath) !== -1 ||
(extension && extensions.indexOf(extension) !== -1)
}
}

/**
* Factory to create a file filter based on bound ignore
* patterns.
*
* @param {Array.<VFile>} failed - List of failed files.
* @return {Function} - Process callback.
*/
function processFactory(failed) {
/**
* Process all found files (and failed ones too).
*
* @param {Error} [err] - Finding error (not used by
* vfile-find-down).
* @param {Array.<VFile>} [files] - Virtual files.
*/
return function (err, files) {
failed.concat(files || []).forEach(function (file) {
file.quiet = true;

try {
file.contents = readFile(file.filePath(), ENCODING);
} catch (err) {
file.fail(err);
}

alex[fn](file);

log(file);
});
})
}
}

/*
* Handle patterns.
*/

globby(globs).then(function (filePaths) {
var given = [];
var failed = [];

/*
* Check whether files are given directly that either
* do not exist or which might not match the default
* search patterns (based on extensions).
*/

globs.forEach(function (glob) {
var stats;

if (hasMagic(glob)) {
return;
}

try {
stats = stat(glob);

if (stats.isFile()) {
given.push(glob);
}
} catch (err) {
failed.push(toFile(glob));
}
});

/*
* Search for an ignore file.
*/

findUp.one(IGNORE, function (err, file) {
var ignore = [];

try {
ignore = file && loadIgnore(file.filePath());
} catch (err) { /* Empty. */ }

ignore = defaultIgnore.concat(ignore);

findDown.all(filterFactory(ignore, given), filePaths, processFactory(failed));
});
}, bail);
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,20 @@
"dependencies": {
"bail": "^1.0.0",
"get-stdin": "^5.0.0",
"glob": "^6.0.1",
"globby": "^4.0.0",
"mdast": "^2.0.0",
"mdast-util-to-nlcst": "^1.0.0",
"meow": "^3.3.0",
"minimatch": "^3.0.0",
"retext": "^1.0.0",
"retext-english": "^1.0.0",
"retext-equality": "^1.5.0",
"to-vfile": "^1.0.0",
"update-notifier": "^0.5.0",
"vfile": "^1.1.0",
"vfile-find-down": "^1.0.0",
"vfile-find-up": "^1.0.0",
"vfile-reporter": "^1.2.0",
"vfile-sort": "^1.0.0"
},
Expand All @@ -73,7 +77,7 @@
"test": "npm run test-api",
"lint-api": "eslint .",
"lint-style": "jscs --reporter inline .",
"lint-text": "./cli.js *.md !example.md --why",
"lint-text": "./cli.js . --why",
"lint": "npm run lint-api && npm run lint-style && npm run lint-text",
"make": "npm run lint && npm run test-coverage",
"bundle": "browserify index.js -s alex > alex.js",
Expand Down
34 changes: 34 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ $ npm install alex --global

* [Support](#support)

* [File finding](#file-finding)

* [.alexignore](#alexignore)

* [Workflow](#workflow)

* [FAQ](#faq)
Expand Down Expand Up @@ -184,6 +188,36 @@ alex.text('The `boogeyman`.').messages;
**alex** ignores words meant literally, so `“he”`, `He — ...`, and [the like](https://github.com/wooorm/nlcst-is-literal#isliteralparent-index)
are not warned about

## File finding

**alex** CLI searches for files with a markdown or text extension when given
directories (e.g., `$ alex .` will find `readme.md` and `foo/bar/baz.txt`).
To prevent files from being found by **alex**, add an
[`.alexignore`](#alexignore) file.

## `.alexignore`

The **alex** CLI will sometimes [search for files](#file-finding). To prevent
files from being found, add a file named `.alexignore` in one of the
directories above the current working directory. The format of these files is
similar to [`.eslintignore`](http://eslint.org/docs/user-guide/configuring.html#ignoring-files-and-directories).

For example, when working in `~/alpha/bravo/charlie`, the ignore file can be
in `charlie`, but also in `~`.

The ignore files for [the project itself](https://github.com/wooorm/alex/blob/master/.alexignore)
looks as follows:

```txt
# Both `node_modules` and `bower_components` are
# ignored by default:
# node_modules/
# bower_components/
components/
example.md
```

## Workflow

The recommended workflow is to add **alex** locally and to run it with your
Expand Down

0 comments on commit 9646363

Please sign in to comment.