tunnckoCore/create-readdir-stream

View on GitHub
index.js

Summary

Maintainability
A
1 hr
Test Coverage
/*!
 * create-readdir-stream <https://github.com/tunnckoCore/create-readdir-stream>
 *
 * Copyright (c) 2016 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk)
 * Released under the MIT license.
 */

'use strict'

var path = require('path')
var utils = require('./utils')

/**
 * > Initialize `CreateReaddirStream` with default `options`.
 *
 * **Example**
 *
 * ```js
 * const inst = require('create-readdir-stream')
 *
 * console.log(inst.use) // => 'function'
 * console.log(inst.createReaddirStream) // => 'function'
 *
 * // or get constructor
 * const Readdir = require('create-readdir-stream').CreateReaddirStream
 * ```
 *
 * @param {Object} `[options]` one of them is `cwd`.
 * @api public
 */

function CreateReaddirStream (options) {
  if (!(this instanceof CreateReaddirStream)) {
    return new CreateReaddirStream(options)
  }

  utils.use(this)
  this.initDefaults(options)
}

var proto = CreateReaddirStream.prototype

/**
 * > Initial defaults and initializing of empty
 * and forced [through2][] object mode stream.
 *
 * @name   .initDefaults
 * @param  {Object} `[options]` optional
 * @return {CreateReaddirStream} this instance for chaining
 * @api private
 */

proto.initDefaults = function initDefaults (options) {
  this.options = utils.extend({
    cwd: process.cwd(),
    file: {
      include: true,
      exclude: false,
      options: {}
    }
  }, options, {
    objectMode: true
  })
  this.stream = utils.through2(this.options)
  return this
}

/**
 * > Smart plugins support using [use][]. It just calls
 * that `fn` immediately and if it returns function again
 * it is called (**only when** `.createReaddirStream` is called)
 * with `file` argument ([vinyl][] file) for
 * each item in the returned array by `fs.readdir`.
 *
 * **Example**
 *
 * ```js
 * const through2 = require('through2')
 * const readdir = require('create-readdir-stream')
 *
 * readdir.use(function (app) {
 *   // both `this` and `app` are the instance aka `readdir`
 *   // called immediately
 *
 *   // below function IS NOT called immediately it is
 *   // called only when `.createReaddirStream` is called
 *   return function (file) {
 *     // both `this` and `file` are Vinyl virtual file object
 *     // called on each filepath. Or in other words
 *     // this function is called on each item in
 *     // returned array by `fs.readdir`
 *
 *     // exclude `index.js` from been pushed to stream
 *     if (file.basename === 'index.js') {
 *       file.exclude = true
 *       // or file.include = false
 *     }
 *     console.log('from plugin', file.basename)
 *   }
 * })
 *
 * readdir
 *   .createReaddirStream('./')
 *   .once('error', console.error)
 *   .pipe(through2.obj(function (file, enc, cb) {
 *     // you should NOT expect to see `index.js` here :)
 *     console.log('from pipe', file.basename)
 *     cb()
 *   }))
 * ```
 *
 * @name   .use
 * @param  {Function} `<fn>` plugin to be called immediately
 * @return {CreateReadStream} this instance for chaining
 * @api public
 */

/**
 * > Reads a `dir` contents, creates [vinyl][] file
 * from each filepath, after that push them to stream.
 *
 * **Example**
 *
 * ```js
 * const th2 = require('through2')
 * const fs2 = require('create-readdir-stream')
 *
 * // same signature and api as `fs.createReadStream`
 * fs2.createReaddirStream('./')
 *   .once('error', console.error)
 *   .pipe(th2.obj(function (file, enc, cb) {
 *     console.log('from pipe', file.basename)
 *     cb()
 *   }))
 * ```
 *
 * @name   .createReaddirStream
 * @param  {String|Buffer} `<dir>` buffer or string folder/directory to read
 * @param  {Object} `[options]` options are [extend-shallow][]ed with `this.options`
 * @return {Stream} Transform Stream, [through2][]
 * @api public
 */

proto.createReaddirStream = function createReaddirStream (dir, options) {
  dir = utils.isBuffer(dir) ? dir.toString() : dir

  if (typeof dir !== 'string') {
    var msg = 'expect `dir` to be a string or Buffer'
    throw new TypeError('[create-readdir-stream] .readdir: ' + msg)
  }

  this.options = utils.extend(this.options, options)
  this.rootDir = path.resolve(this.options.cwd, dir)

  utils.fs.readdir(this.rootDir, function (err, paths) {
    if (err) {
      var message = err.message
      err.message = '[create-readdir-stream] .readdir: '
      err.message += message

      this.stream.emit('error', err)
      return
    }
    if (!paths.length) {
      var msg = 'directory is empty: ' + this.rootDir
      var er = new Error('[create-readdir-stream] .readdir: ' + msg)
      this.stream.emit('error', er)
      return
    }

    this.paths = paths

    // Should return paths!
    // Perfect place for globbing library
    // such as `micromatch`
    if (typeof this.options.plugin === 'function') {
      this.paths = this.options.plugin.call(this, this.paths)
    }

    // Change all paths to Vinyl files
    // and push them to stream.
    this.paths.forEach(function (fp, idx) {
      // Allow user to add to
      // each file what he want
      var config = utils.extend(this.options.file, {
        cwd: this.options.cwd,
        path: path.join(this.rootDir, fp)
      })

      // Write to instance intentionally and after
      // that pass it to each plugin.
      this.file = new utils.File(config)

      // Each plugin's `this` context is the File
      // So this allows to modify through using `this`
      // in the plugin, instead of only `file` argument.
      // For example `this.path = 'foobar'` or `file.path = 'foobar'`
      // both would work.
      this.run(this.file)

      // Allow users to choose which file should be pushed to stream.
      // For example:
      // pass `file.exclude = true` or `file.include = false` to some
      // file and it won't be pushed to the stream.
      if (this.file.include === true && this.file.exclude === false) {
        this.stream.push(this.file)
      }

      var shouldClose = (idx + 1) === this.paths.length
      if (shouldClose) {
        this.stream.push(null)
      }
    }, this)
  }.bind(this))

  return this.stream
}

/**
 * Expose `CreateReaddirStream` instance
 *
 * @type {Object}
 * @api private
 */

module.exports = new CreateReaddirStream()

/**
 * Expose `CreateReaddirStream` constructor
 *
 * @type {Function}
 * @api private
 */

module.exports.CreateReaddirStream = CreateReaddirStream