text: /**
  * @license Apache-2.0
  *
  * Copyright (c) 2018 The Stdlib Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *    http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
  
  'use strict';
  
  // MODULES //
  
  var resolve = require( 'path' ).resolve;
  var logger = require( 'debug' );
  var findPkgs = require( '@stdlib/_tools/pkgs/find' );
  var readJSON = require( '@stdlib/fs/read-json' );
  var readFile = require( '@stdlib/fs/read-file' );
  var readFileList = require( '@stdlib/fs/read-file-list' );
  var isFunction = require( '@stdlib/assert/is-function' );
  var contains = require( '@stdlib/assert/contains' );
  var removeFirst = require( '@stdlib/string/remove-first' );
  var startsWith = require( '@stdlib/string/starts-with' );
  var trim = require( '@stdlib/string/trim' );
  var cwd = require( '@stdlib/process/cwd' );
  var validate = require( './validate.js' );
  var toc = require( './toc.js' );
  var extractAliases = require( './extract_aliases.js' );
  
  
  // VARIABLES //
  
  var debug = logger( 'markdown-namespace-toc:async' );
  
  
  // MAIN //
  
  /**
  * Asynchronously creates a table of contents.
  *
  * @param {Options} [options] - function options
  * @param {string} [options.dir] - root directory from which to search for files
  * @param {string} [options.pattern='**\/*'] - package pattern
  * @param {StringArray} [options.ignore] - ignore pattern
  * @param {string} [options.keywords] - string of comma-separated keywords (preceded by `+` or `-` to indicate inclusion or exclusion, respectively)
  * @param {StringArray} [options.blacklist] - blacklisted packages
  * @param {Callback} clbk - callback to invoke after creating the table of contents
  * @throws {TypeError} options argument must be an object
  * @throws {TypeError} must provide valid options
  * @throws {TypeError} must provide a function
  *
  * @example
  * var resolve = require( 'path' ).resolve;
  *
  * var opts = {
  *     'dir': resolve( __dirname, '..' )
  * };
  * createTOC( opts, onCreate );
  *
  * function onCreate( error, toc ) {
  *   if ( error ) {
  *     throw error;
  *   }
  *   console.log( toc );
  * }
  */
  function createTOC() {
  	var forbidden;
  	var keywords;
  	var allowed;
  	var aliases;
  	var options;
  	var paths;
  	var clbk;
  	var opts;
  	var err;
  
  	opts = {};
  	if ( arguments.length < 2 ) {
  		clbk = arguments[ 0 ];
  	} else {
  		options = arguments[ 0 ];
  		clbk = arguments[ 1 ];
  		err = validate( opts, options );
  		if ( err ) {
  			throw err;
  		}
  	}
  	if ( !isFunction( clbk ) ) {
  		throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + clbk + '`.' );
  	}
  	if ( opts.dir ) {
  		opts.dir = resolve( cwd(), opts.dir );
  	} else {
  		opts.dir = cwd();
  	}
  	if ( opts.pattern ) {
  		opts.pattern += '/package.json';
  	}
  	readFile( resolve( opts.dir, 'lib', 'index.js' ), onFile );
  
  	/**
  	* Callback invoked after reading the `index.js` file of a namespace.
  	*
  	* @private
  	* @param {(Error|null)} error - error object
  	* @param {(EmptyArray|StringArray)} ns - code with all package exports
  	* @returns {void}
  	*/
  	function onFile( error, ns ) {
  		if ( error ) {
  			return clbk( error );
  		}
  		aliases = extractAliases( ns.toString() );
  		findPkgs( opts, onGlob );
  	}
  
  	/**
  	* Callback invoked after matching files.
  	*
  	* @private
  	* @param {(Error|null)} error - error object
  	* @param {(EmptyArray|StringArray)} files - list of matching files
  	* @returns {void}
  	*/
  	function onGlob( error, files ) {
  		var word;
  		var idx;
  		var i;
  
  		if ( error ) {
  			return clbk( error );
  		}
  		debug( 'Matched packages: %s', files.join( ', ' ) );
  
  		if ( files.length === 0 ) {
  			return clbk( null, {
  				'toc': '<div class="namespace-toc">\n\n\n</div>',
  				'links': []
  			});
  		}
  
  		if ( opts.keywords ) {
  			keywords = opts.keywords.split( ',' );
  			allowed = [];
  			forbidden = [];
  			for ( i = 0; i < keywords.length; i++ ) {
  				word = trim( keywords[ i ] );
  				if ( startsWith( word, '+' ) ) {
  					allowed.push( removeFirst( word ) );
  				} else if ( startsWith( word, '-' ) ) {
  					forbidden.push( removeFirst( word ) );
  				}
  			}
  			paths = [];
  			idx = 0;
  			readJSON( resolve( opts.dir, files[ idx ], 'package.json' ), onPackage );
  		} else {
  			paths = [];
  			for ( i = 0; i < files.length; i++ ) {
  				paths.push( resolve( opts.dir, files[ i ], 'README.md' ) );
  			}
  			readFileList( paths, onContents );
  		}
  
  		/**
  		* Callback invoked after loading the `package.json` file of the current package (with the file index given by `idx`).
  		*
  		* @private
  		* @param {(Error|null)} error - error object
  		* @param {Object} json - package.json object
  		* @returns {void}
  		*/
  		function onPackage( error, json ) {
  			var pkgKeywords;
  			var include;
  			var omit;
  			var j;
  
  			if ( error ) {
  				return clbk( error );
  			}
  			pkgKeywords = json.keywords;
  			omit = false;
  			include = false;
  			if ( allowed.length > 0 && forbidden.length > 0 ) {
  				for ( j = 0; j < pkgKeywords.length; j++ ) {
  					if ( contains( forbidden, pkgKeywords[ j ] ) ) {
  						omit = true;
  						break;
  					} else if ( contains( allowed, pkgKeywords[ j ] ) ) {
  						include = true;
  					}
  				}
  			} else if ( allowed.length === 0 && forbidden.length > 0 ) {
  				for ( j = 0; j < pkgKeywords.length; j++ ) {
  					if ( contains( forbidden, pkgKeywords[ j ] ) ) {
  						omit = true;
  						break;
  					}
  				}
  				include = true;
  			} else if ( allowed.length > 0 && forbidden.length === 0 ) {
  				for ( j = 0; j < pkgKeywords.length; j++ ) {
  					if ( contains( allowed, pkgKeywords[ j ] ) ) {
  						include = true;
  						break;
  					}
  				}
  			} else {
  				include = true;
  			}
  			if ( include && !omit ) {
  				paths.push( resolve( opts.dir, files[ idx ], 'README.md' ) );
  			}
  			idx += 1;
  			if ( idx === files.length ) {
  				readFileList( paths, onContents );
  			} else {
  				readJSON( resolve( opts.dir, files[ idx ], 'package.json' ), onPackage );
  			}
  		}
  	}
  
  	/**
  	* Callback invoked after loading `README.md` files.
  	*
  	* @private
  	* @param {(Error|null)} error - error object
  	* @param {(EmptyArray|StringArray)} contents - README.md contents
  	* @returns {void}
  	*/
  	function onContents( error, contents ) {
  		if ( error ) {
  			debug( 'Encountered an error: %s', error.message );
  			return clbk( error );
  		}
  		debug( 'Read %d README.md files', contents.length );
  		clbk( null, toc( contents, aliases, opts.blacklist ) );
  	}
  }
  
  
  // EXPORTS //
  
  module.exports = createTOC;
  