tunnckoCore / fs-readdir

fs.readdir done right! Support sync, async and stream API, recursiveness and filters/globs.
https://github.com/tunnckoCore
MIT License
5 stars 1 forks source link

refactor #1

Open tunnckoCore opened 8 years ago

tunnckoCore commented 8 years ago

Move https://github.com/tunnckoCore/glob-fs here idea here. Actually, just rewrite it again. Recently I created https://github.com/tunnckoCore/create-readdir-stream and it is different take, oriented to not use recursion and without plugins, even it not reads and stats the files in directory - it is just stream of filepaths.

About tunnckoCore/glob-fs... it is pretty fast and awesome. Will try to redo all here and start the battle against gulp/vinyl-fs/glob-stream. Actually, no so battle, but want to show that things can be simpler and a lot lot faster. All these variants and proof of concepts for last 2 years are great and it's time to show one final and stable package to the world.

About jonschlinkert/glob-fs is okey too, really. But goes a bit further, and does things in much more abstraction.

fs-readdir will have sync, async, stream and promise api. Support for plugins and recursion (disabled by default? or it can be "smart" based on if you use glob patterns or plain string/dir). Battle tested against all available tests out there - node-glob, glob-stream, vinyl-fs. It's impossible to got all of them covered, because they do wrong things and introduced bugs (or strange behaviors) for bugfixes.
Or as I call it in short "fix-a-bug-with-a-bug"-shit.

tunnckoCore commented 8 years ago

Just for backup. Working copy of Constructor variant of create-readdir-stream

/*!
 * 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')

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

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

Readdir.prototype.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
}

Readdir.prototype.readdir = function readdir (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) {
      err.message = '[create-readdir-stream] .readdir: '
      err.message += err.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))

  // or `this.stream`
  return this
}

/**
 * > Sugar for `stream.pipe(through2.obj(fn))`, returning
 * this instance for chaining. The `fn`s `this` context
 * will be a transform stream. Arguments of the `fn` will
 * be just `file, cb`.
 *
 * @param  {Function} `fn` function to be called inside `through2.obj()`
 * @return {Readdir} `this` instance for chaining
 */
Readdir.prototype.pipe = function pipeSugar (fn) {
  if (typeof fn !== 'function') {
    var msg = 'expect `fn` to be a fucntion'
    throw new TypeError('[create-readdir-stream] .pipeline: ' + msg)
  }

  this.stream = this.stream.pipe(utils.through2.obj(function (file, enc, cb) {
    fn.call(this, file, cb)
  }))
  return this
}

module.exports = Readdir