<commit_before>/**
 *  FsTools
 *
 *  Collection of FS related tools, that stdlib lack of.
 **/

'use strict';

// stdlib
var fs = require('fs'),
    path_join = require('path').join,
    path_exists = require('path').exists,
    path_normalize = require('path').normalize,
    dirname = require('path').dirname;

// 3rd-party
var Promise = require('simple-promise'),
    _ = require('underscore');

// epxorts: walk, mkdir, copy, remove
var fstools = module.exports = {};

// INTERNAL HELPERS
////////////////////////////////////////////////////////////////////////////////

//  walk_flat(path, iterator, callback) -> void
//  - path (String): Path to iterate through
//  - iterator (Function): Will be fired on each element within `path`
//  - callback (Function): Will be fired once all files were processed
//
//  Walks through given `path` and calls `iterator(path, stats, callback)` for
//  each foun entry (regardless to it's type) and waits for all `callback`s
//  (passed to iterator) to be fired. After all callbacks were fired, fires
//  `callback` (given to walk_flat itself).
//
//  NOTICE: It walks through single dimension of file system - it won't go into
//          found sub-directories. See `walk_recursive` for this puprpose.
//
//  Example:
//
//    walk_flat('/home/nodeca', function (path, stats, callback) {
//      if ('/home/nodeca/secrets.yml' === path) {
//        callback(Error("There is secrets file."));
//        return;
//      }
//
//      if ('/home/nodeca/xxx' === path && stats.isDirectory()) {
//        callback(Error("Path contains xxx directory."));
//        return;
//      }
//
//      callback();
//    }, function (err) {
//      if (err) console.error(err);
//      console.log('Done');
//    });
function walk_flat(path, iterator, callback) {
  fs.readdir(path, function (err, files) {
    var all;

    if (err) {
      callback(err);
      return;
    }

    all = new Promise.Joint(callback);

    _(files).chain().map(function(file) {
      return path_join(path, file);
    }).each(function (path) {
      var promise = all.promise();

      fs.lstat(path, function (err, stats) {
        if (err) {
          all.reject(err);
          return;
        }

        iterator(path, stats, function (err) {
          if (err) {
            all.reject(err);
            return;
          }

          promise.resolve();
        });
      });
    });

    all.wait();
  });
};

//  walk_recursive(path, iterator, callback) -> void
//
//  Proxies call to `walk_flat`, but wraps `iterator` so that it will
//  recursively go deeper inside each found directoy instead of calling
//  iterator on it.
function walk_recursive(path, iterator, callback) {
  // walk through given path
  walk_flat(path, function (path, stats, callback) {
    // go deeper inside
    if (stats.isDirectory()) {
      walk_recursive(path, iterator, callback);
      return;
    }

    // call iterator if not directory
    iterator(path, stats, callback);
  }, callback);
};

function copy_file(src, dst, mode, callback) {
  var ifd, ofd;

  // create streams
  ifd = fs.createReadStream(src, {bufferSize: 64 * 1024}).on('error', callback);
  ofd = fs.createWriteStream(dst).on('error', callback).on('close', callback);

  // pipe src to dst
  ifd.pipe(ofd);
};

function copy_symlink(src, dst, callback) {
  // read where symlinks points to
  fs.readlink(src, function (err, linkpath) {
    if (err) {
      callback(err);
      return;
    }

    // create symlink
    fs.symlink(linkpath, dst, callback);
  });
};

function copy_directory(src, dst, callback) {
  fs.mkdir(dst, '0755', function (err) {
    if (err) {
      callback(err);
      return;
    }

    walk_flat(src, function (sub_src, sub_stats, next) {
      copy(sub_src, dst + sub_src.replace(src, ''), sub_stats, next);
    }, callback);
  });
};

// copy src to dst recursively
function copy(src, dst, stats, callback) {
  function _callback(err) {
    if (err) {
      callback(err);
      return;
    }

    // the only thing we really care about is permission mode
    chmod(dst, stats.mode.toString(8).slice(-4), callback);
  }

  // *** file
  if (stats.isFile()) {
    copy_file(src, dst, _callback);
    return;
  }

  // *** symbolic link
  if (stats.isSymbolicLink()) {
    copy_symlink(src, dst, _callback);
    return;
  }

  // *** directory
  if (stats.isDirectory()) {
    copy_directory(src, dst, _callback);
    return;
  }

  // *** unsupported src
  callback(new Error("Unsupported type of the source"));
}

function remove(path, stats, callback) {
  if (!stats.isDirectory()) {
    fs.unlink(path, callback);
    return;
  }

  walk_flat(path, function (path, stats, next) {
    remove(path, stats, next);
  }, function (err) {
    if (err) {
      callback(err);
      return;
    }

    fs.rmdir(path, callback);
  });
}

// PUBLIC API
////////////////////////////////////////////////////////////////////////////////

/**
 *  FsTools.walk(path, pattern, iterator[, callback]) -> void
 *  FsTools.walk(path, iterator[, callback]) -> void
 *
 *  Walks throught all files withing `path` (including sub-dirs) and calls
 *  `iterator` on each found file (or block device etc.) matching `pattern`.
 *  If no `pattern` was given - will fire call `iterator` for every single
 *  path found. After all iterations will call `callback` (if it was specified)
 *  with passing `error` as first arguemtn if there was an error.
 *
 *  ##### Iterator
 *
 *  All iterations are running within promise. So `callback` given to the `walk`
 *  will fire only after all `iterator` callbacks willnotify they finished their
 *  work:
 *
 *      var iterator = function (path, stats, callback) {
 *        // ... do something
 *        if (err) {
 *          // ... if error occured we can "stop" walker
 *          callback(err);
 *          return;
 *        }
 *        // ... if everything is good and finished notify walker we're done
 *        callback();
 *      };
 *
 *  Iterator is called with following arguments:
 *
 *  - `path` (String): Full path of the found element (e.g. `/foo/bar.txt`)
 *  - `stats` (fs.Stats): Stats object of found path
 *  - `callback` (Function): Callback function to call after path processing
 *
 *
 *  ##### Example
 *
 *      fstools.walk('/home/nodeca', function (path, stats, callback) {
 *        if (stats.isBlockDevice()) {
 *          callback(Error("WTF? Block devices are not expetcted in my room"));
 *          return;
 *        }
 *
 *        if (stats.isSocket()) {
 *          console.log("Finally I found my socket");
 *        }
 *
 *        callback();
 *      }, function (err) {
 *        if (err) {
 *          // shit happens!
 *          console.error(err);
 *          process.exit(1);
 *        }
 *
 *        console.log("Hooray! We're done!");
 *      });
 *
 *
 *  ##### Example (using pattern matching)
 *
 *      fstools.walk('/home/nodeca', '\.yml$', function (path, stats, callback) {
 *        fs.readFile(path, 'utf-8', funtion (err, str) {
 *          if (err) {
 *            callback(err);
 *            return;
 *          }
 *
 *          console.log(str);
 *          callback();
 *        });
 *      }, function (err) {
 *        if (err) {
 *          console.error(err);
 *        }
 *
 *        console.log('Done!');
 *      });
 **/
fstools.walk = function (path, pattern, iterator, callback) {
  var match; // function that tells whenever iterator need to be called or not

  if (_.isFunction(pattern)) {
    // Scenario: FileWalker(path, iterator[, callback])
    callback = iterator;
    iterator = pattern;
    match = function () { return true; };
  } else {
    // Scenario: FileWalker(path, pattern, iterator[, callback])
    pattern = new RegExp(pattern);
    match = function (path) { return pattern.test(path); };
  }

  if (!_.isFunction(callback)) {
    // Scenario: FileWalker(path, pattern, iterator)
    //       or: FileWalker(path, iterator)
    callback = function () {};
  }

  // start walking
  walk_recursive(path_normalize(path), function(path, stats, callback) {
    // call iterator on
    if (match(path)) {
      iterator(path, stats, callback);
      return;
    }

    callback();
  }, callback);
};

/**
 *  FsTools.remove(path, callback) -> void
 *  - path (String): Path to remove
 *  - callback (Function): Fired after path was removed
 *
 *  Removes given `path`. If it was a directory will remove it recursively,
 *  similar to UNIX' `rm -rf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *  If given `path` was file - will proxy call to `fs.unlink`.
 *
 *
 *  ##### Example
 *
 *      fstools.remove('/home/nodeca/trash', function (err) {
 *        if (err) {
 *          console.log("U can't touch that");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("It's Hammer time");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.remove = function (path, callback) {
  path = path_normalize(path);
  fs.lstat(path, function (err, stats) {
    if (err) {
      // file/dir not exists - no need to do anything
      if ('ENOENT' === err.code) {
        callback(null);
        return;
      }

      // unknown error - can't continue
      callback(err);
      return;
    }

    remove(path, stats, callback);
  });
};

/**
 *  FsTools.mkdir(path, mode, callback) -> void
 *  FsTools.mkdir(path, callback) -> void
 *  - path (String): Path to create
 *  - mode (String|Number): Permission mode of new directory. See stdlib
 *    fs.mkdir for details. Default: '0755'.
 *  - callback (Function): Fired after path was created
 *
 *  Creates given path, creating parents recursively if needed.
 *  Similar to UNIX' `mkdir -pf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *
 *  ##### Example
 *
 *      fstools.mkdir('/home/nodeca/media/xxx', function (err) {
 *        if (err) {
 *          console.log("Can't' create directory");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("We can now store some romantic movies here");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.mkdir = function (path, mode, callback) {
  if (undefined === callback && _.isFunction(mode)) {
    callback = mode;
    mode = '0755';
  }

  path = path_normalize(path);
  path_exists(path, function (exists) {
    var parent;

    if (exists) {
      callback(null);
      return;
    }

    parent = dirname(path);
    fstools.mkdir(parent, function (err) {
      if (err) {
        callback(err);
        return;
      }

      fs.mkdir(path, mode, function (err) {
        // EEXIST is not error in our case
        // but a race condition :((
        if (err && 'EEXIST' === err.code) {
          callback(null);
          return;
        }

        // fallback to default behavior
        callback(err);
      });
    });
  });
};

/**
 *  FsTools.copy(src, dst, callback) -> void
 *  - src (String): Source file
 *  - dst (String): Destination file
 *  - callback (Function): Fired after path has been copied
 *
 *  Copies `src` to `dst`, creates directory for given `dst` with
 *  [[FsTools.mkdir]] if needed. Fires `callback(err)` upon
 *  completion.
 *
 *  ##### Example
 *
 *      var src = '/home/nodeca/secrets.yml',
 *          dst = '/home/nodeca/very/deep/secrets/main.yml';
 *
 *      fstools.copy(src, dst, function (err) {
 *        if (err) {
 *          console.log("Failed copy " + src + " into " + dst);
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("Done!");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.copy = function (src, dst, callback) {
  src = path_normalize(src);
  dst = path_normalize(dst);

  // sad but true - people make mistakes...
  if (src === dst) {
    callback(null);
    return;
  }

  fs.lstat(src, function (err, stats) {
    if (err) {
      callback(err);
      return;
    }

    fstools.mkdir(dirname(dst), function (err) {
      if (err) {
        callback(err);
        return;
      }

      copy(src, dst, stats, callback);
    });
  });
};

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<commit_msg>Fixes lib<commit_after>/**
 *  FsTools
 *
 *  Collection of FS related tools, that stdlib lack of.
 **/

'use strict';

// stdlib
var fs = require('fs'),
    path_join = require('path').join,
    path_exists = require('path').exists,
    path_normalize = require('path').normalize,
    dirname = require('path').dirname;

// 3rd-party
var Promise = require('simple-promise'),
    _ = require('underscore');

// epxorts: walk, mkdir, copy, remove
var fstools = module.exports = {};

// INTERNAL HELPERS
////////////////////////////////////////////////////////////////////////////////

//  walk_flat(path, iterator, callback) -> void
//  - path (String): Path to iterate through
//  - iterator (Function): Will be fired on each element within `path`
//  - callback (Function): Will be fired once all files were processed
//
//  Walks through given `path` and calls `iterator(path, stats, callback)` for
//  each foun entry (regardless to it's type) and waits for all `callback`s
//  (passed to iterator) to be fired. After all callbacks were fired, fires
//  `callback` (given to walk_flat itself).
//
//  NOTICE: It walks through single dimension of file system - it won't go into
//          found sub-directories. See `walk_recursive` for this puprpose.
//
//  Example:
//
//    walk_flat('/home/nodeca', function (path, stats, callback) {
//      if ('/home/nodeca/secrets.yml' === path) {
//        callback(Error("There is secrets file."));
//        return;
//      }
//
//      if ('/home/nodeca/xxx' === path && stats.isDirectory()) {
//        callback(Error("Path contains xxx directory."));
//        return;
//      }
//
//      callback();
//    }, function (err) {
//      if (err) console.error(err);
//      console.log('Done');
//    });
function walk_flat(path, iterator, callback) {
  fs.readdir(path, function (err, files) {
    var all;

    if (err) {
      callback(err);
      return;
    }

    all = new Promise.Joint(callback);

    _(files).chain().map(function(file) {
      return path_join(path, file);
    }).each(function (path) {
      var promise = all.promise();

      fs.lstat(path, function (err, stats) {
        if (err) {
          all.reject(err);
          return;
        }

        iterator(path, stats, function (err) {
          if (err) {
            all.reject(err);
            return;
          }

          promise.resolve();
        });
      });
    });

    all.wait();
  });
};

//  walk_recursive(path, iterator, callback) -> void
//
//  Proxies call to `walk_flat`, but wraps `iterator` so that it will
//  recursively go deeper inside each found directoy instead of calling
//  iterator on it.
function walk_recursive(path, iterator, callback) {
  // walk through given path
  walk_flat(path, function (path, stats, callback) {
    // go deeper inside
    if (stats.isDirectory()) {
      walk_recursive(path, iterator, callback);
      return;
    }

    // call iterator if not directory
    iterator(path, stats, callback);
  }, callback);
};

function copy_file(src, dst, callback) {
  var ifd, ofd;

  // create streams
  ifd = fs.createReadStream(src, {bufferSize: 64 * 1024}).on('error', callback);
  ofd = fs.createWriteStream(dst).on('error', callback).on('close', callback);

  // pipe src to dst
  ifd.pipe(ofd);
};

function copy_symlink(src, dst, callback) {
  // read where symlinks points to
  fs.readlink(src, function (err, linkpath) {
    if (err) {
      callback(err);
      return;
    }

    // create symlink
    fs.symlink(linkpath, dst, callback);
  });
};

function copy_directory(src, dst, callback) {
  fs.mkdir(dst, '0755', function (err) {
    if (err) {
      callback(err);
      return;
    }

    walk_flat(src, function (sub_src, sub_stats, next) {
      copy(sub_src, dst + sub_src.replace(src, ''), sub_stats, next);
    }, callback);
  });
};

// copy src to dst recursively
function copy(src, dst, stats, callback) {
  var _callback = function _callback(err) {
    if (err) {
      callback(err);
      return;
    }

    // we need to call chmod in next tick, because symlink creation is sheduled
    // on next tick sometimes. so this guarantees we call chmod AFTER real fs
    // operation of copy was performed
    process.nextTick(function () {
      // the only thing we really care about is permission mode
      fs.chmod(dst, stats.mode.toString(8).slice(-4), callback);
    });
  }

  // *** file
  if (stats.isFile()) {
    copy_file(src, dst, _callback);
    return;
  }

  // *** symbolic link
  if (stats.isSymbolicLink()) {
    copy_symlink(src, dst, _callback);
    return;
  }

  // *** directory
  if (stats.isDirectory()) {
    copy_directory(src, dst, _callback);
    return;
  }

  // *** unsupported src
  callback(new Error("Unsupported type of the source"));
}

function remove(path, stats, callback) {
  if (!stats.isDirectory()) {
    fs.unlink(path, callback);
    return;
  }

  walk_flat(path, function (path, stats, next) {
    remove(path, stats, next);
  }, function (err) {
    if (err) {
      callback(err);
      return;
    }

    // avoid race conditions of fs operations
    // see copy() for details
    process.nextTick(function () {
      fs.rmdir(path, callback);
    });
  });
}

// PUBLIC API
////////////////////////////////////////////////////////////////////////////////

/**
 *  FsTools.walk(path, pattern, iterator[, callback]) -> void
 *  FsTools.walk(path, iterator[, callback]) -> void
 *
 *  Walks throught all files withing `path` (including sub-dirs) and calls
 *  `iterator` on each found file (or block device etc.) matching `pattern`.
 *  If no `pattern` was given - will fire call `iterator` for every single
 *  path found. After all iterations will call `callback` (if it was specified)
 *  with passing `error` as first arguemtn if there was an error.
 *
 *  ##### Iterator
 *
 *  All iterations are running within promise. So `callback` given to the `walk`
 *  will fire only after all `iterator` callbacks willnotify they finished their
 *  work:
 *
 *      var iterator = function (path, stats, callback) {
 *        // ... do something
 *        if (err) {
 *          // ... if error occured we can "stop" walker
 *          callback(err);
 *          return;
 *        }
 *        // ... if everything is good and finished notify walker we're done
 *        callback();
 *      };
 *
 *  Iterator is called with following arguments:
 *
 *  - `path` (String): Full path of the found element (e.g. `/foo/bar.txt`)
 *  - `stats` (fs.Stats): Stats object of found path
 *  - `callback` (Function): Callback function to call after path processing
 *
 *
 *  ##### Example
 *
 *      fstools.walk('/home/nodeca', function (path, stats, callback) {
 *        if (stats.isBlockDevice()) {
 *          callback(Error("WTF? Block devices are not expetcted in my room"));
 *          return;
 *        }
 *
 *        if (stats.isSocket()) {
 *          console.log("Finally I found my socket");
 *        }
 *
 *        callback();
 *      }, function (err) {
 *        if (err) {
 *          // shit happens!
 *          console.error(err);
 *          process.exit(1);
 *        }
 *
 *        console.log("Hooray! We're done!");
 *      });
 *
 *
 *  ##### Example (using pattern matching)
 *
 *      fstools.walk('/home/nodeca', '\.yml$', function (path, stats, callback) {
 *        fs.readFile(path, 'utf-8', funtion (err, str) {
 *          if (err) {
 *            callback(err);
 *            return;
 *          }
 *
 *          console.log(str);
 *          callback();
 *        });
 *      }, function (err) {
 *        if (err) {
 *          console.error(err);
 *        }
 *
 *        console.log('Done!');
 *      });
 **/
fstools.walk = function (path, pattern, iterator, callback) {
  var match; // function that tells whenever iterator need to be called or not

  if (_.isFunction(pattern)) {
    // Scenario: FileWalker(path, iterator[, callback])
    callback = iterator;
    iterator = pattern;
    match = function () { return true; };
  } else {
    // Scenario: FileWalker(path, pattern, iterator[, callback])
    pattern = new RegExp(pattern);
    match = function (path) { return pattern.test(path); };
  }

  if (!_.isFunction(callback)) {
    // Scenario: FileWalker(path, pattern, iterator)
    //       or: FileWalker(path, iterator)
    callback = function () {};
  }

  // start walking
  walk_recursive(path_normalize(path), function(path, stats, callback) {
    // call iterator on
    if (match(path)) {
      iterator(path, stats, callback);
      return;
    }

    callback();
  }, callback);
};

/**
 *  FsTools.remove(path, callback) -> void
 *  - path (String): Path to remove
 *  - callback (Function): Fired after path was removed
 *
 *  Removes given `path`. If it was a directory will remove it recursively,
 *  similar to UNIX' `rm -rf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *  If given `path` was file - will proxy call to `fs.unlink`.
 *
 *
 *  ##### Example
 *
 *      fstools.remove('/home/nodeca/trash', function (err) {
 *        if (err) {
 *          console.log("U can't touch that");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("It's Hammer time");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.remove = function (path, callback) {
  path = path_normalize(path);
  fs.lstat(path, function (err, stats) {
    if (err) {
      // file/dir not exists - no need to do anything
      if ('ENOENT' === err.code) {
        callback(null);
        return;
      }

      // unknown error - can't continue
      callback(err);
      return;
    }

    remove(path, stats, callback);
  });
};

/**
 *  FsTools.mkdir(path, mode, callback) -> void
 *  FsTools.mkdir(path, callback) -> void
 *  - path (String): Path to create
 *  - mode (String|Number): Permission mode of new directory. See stdlib
 *    fs.mkdir for details. Default: '0755'.
 *  - callback (Function): Fired after path was created
 *
 *  Creates given path, creating parents recursively if needed.
 *  Similar to UNIX' `mkdir -pf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *
 *  ##### Example
 *
 *      fstools.mkdir('/home/nodeca/media/xxx', function (err) {
 *        if (err) {
 *          console.log("Can't' create directory");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("We can now store some romantic movies here");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.mkdir = function (path, mode, callback) {
  if (undefined === callback && _.isFunction(mode)) {
    callback = mode;
    mode = '0755';
  }

  path = path_normalize(path);
  path_exists(path, function (exists) {
    var parent;

    if (exists) {
      callback(null);
      return;
    }

    parent = dirname(path);
    fstools.mkdir(parent, function (err) {
      if (err) {
        callback(err);
        return;
      }

      fs.mkdir(path, mode, function (err) {
        // EEXIST is not error in our case
        // but a race condition :((
        if (err && 'EEXIST' === err.code) {
          callback(null);
          return;
        }

        // fallback to default behavior
        callback(err);
      });
    });
  });
};

/**
 *  FsTools.copy(src, dst, callback) -> void
 *  - src (String): Source file
 *  - dst (String): Destination file
 *  - callback (Function): Fired after path has been copied
 *
 *  Copies `src` to `dst`, creates directory for given `dst` with
 *  [[FsTools.mkdir]] if needed. Fires `callback(err)` upon
 *  completion.
 *
 *  ##### Example
 *
 *      var src = '/home/nodeca/secrets.yml',
 *          dst = '/home/nodeca/very/deep/secrets/main.yml';
 *
 *      fstools.copy(src, dst, function (err) {
 *        if (err) {
 *          console.log("Failed copy " + src + " into " + dst);
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("Done!");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.copy = function (src, dst, callback) {
  src = path_normalize(src);
  dst = path_normalize(dst);

  // sad but true - people make mistakes...
  if (src === dst) {
    callback(null);
    return;
  }

  fs.lstat(src, function (err, stats) {
    if (err) {
      callback(err);
      return;
    }

    fstools.mkdir(dirname(dst), function (err) {
      if (err) {
        callback(err);
        return;
      }

      copy(src, dst, stats, callback);
    });
  });
};

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var fs = require('fs');
var argv = require('minimist')(process.argv.slice(2));
var tildify = require('tildify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var stringUtils = require('../utils/string');

function validateComponentName (filename) {
  if (filename.indexOf('-') === -1) {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(filename),
      gutil.colors.red(' must be a dashize string. ex: my-component')
    );
    gutil.log(
      gutil.colors.red('[-Error:]  Generate task has been canceled')
    );
    process.exit(0);
  }
}

function checkFileExisted (fullFilePath, fileName, ext, destPath) {
  if (fs.existsSync(fullFilePath)) {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(fileName + ext),
      gutil.colors.red('has existed at '),
      gutil.colors.magenta(tildify(destPath))
    );
    gutil.log(
      gutil.colors.red('[-Error:]  Generate task has been canceled')
    );
    process.exit(0);
  }
}

function generatorEngine (type, srcPath, moduleName, fileName, destPath) {
  var ext = (type === 'template') ? '.hbs' : '.js';
  var fullFilePath = destPath + '/' + fileName + ext;

  // if the file has existed, it will abort the task
  checkFileExisted(fullFilePath, fileName, ext, destPath);

  var dasherizeName = '';
  var classifyName = '';
  var matcher;

  // if generating any testing files, need to clean up moduleName without "Test"
  if (type.indexOf('test') > -1) {
    if (type.indexOf('model-test') > -1) {
      moduleName = moduleName.replace(/ModelTest(\s+)?$/, '');
      dasherizeName = stringUtils.dasherize(moduleName);
    } else {
      matcher = stringUtils.classify(type);
      var dasherizeModuleName = moduleName.replace(new RegExp(matcher), '');

      moduleName = moduleName.replace(/Test(\s+)?$/, '');
      dasherizeName = stringUtils.dasherize(dasherizeModuleName);
    }
  } else {
    matcher = stringUtils.capitalize(type);
    var localModuleName = moduleName.replace(new RegExp(matcher), '');

    dasherizeName = stringUtils.dasherize(localModuleName);
    classifyName = stringUtils.classify(localModuleName);
  }

  // @TODO when generate multiple files on certain type
  // moduleName is not being defined correctly.
  // fine for now, since multiple file generation are only template file
  return gulp.src(srcPath)
    .pipe(replace(/__NAMESPACE__/g, moduleName))
    // __DASHERIZE_NAMESPACE__  mainly used in `-test` generator
    .pipe(replace(/__DASHERIZE_NAMESPACE__/g, dasherizeName))
    // __CLASSIFY_NAMESPACE__ mainly used in regular generator
    .pipe(replace(/__CLASSIFY_NAMESPACE__/g, classifyName))
    .pipe(rename({
      basename: fileName,
      extname:  ext
    }))
    .on('end', function () {
      gutil.log(
        gutil.colors.green('[-done:] Generate'),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.green('at'),
        gutil.colors.magenta(tildify(destPath))
      );
    })
    .pipe(gulp.dest(destPath));
}

function setupTask (generator) {
  // task: gen
  // @describe	generate an model,view,store,controller from base template
  return gulp.task('gen', function () {
    var type = generator.type;
    var name = generator.name;
    var pathName = '';
    var moduleName = '';
    var i = 0;
    var pathNested; // Boolean
    var fileName; // setup the fileName which used for rename module

    // based on the passing name arguments, to determine it is an nested folder structure
    // or it is a simple file generation. assign a var `fileName` for current file name
    if (name.indexOf('/') > -1) {
      name = name.split('/');
      pathNested = true;
      fileName = name.pop();
    } else {
      pathNested = false;
      fileName = name;
    }

    // handle the error case when arg is `component:foo`
    // component name has to be dash separated string
    // case 1: `em g component:name`         <= simple case
    // case 2: `em g component:nested/name`  <= nested case
    if (type === 'component') {
      validateComponentName(fileName);
    }
    // when type is template, name[0] is component, name of nest path has to be dashized string
    // case 3: `em g template:component/name`  <= nested case in template
    if (type === 'template' && pathNested && name[0] === 'component') {
      validateComponentName(fileName);
    }

    // Setup `pathName`
    // `moduleName` would be used inside replacement of template placeholder
    if (pathNested) {
      // build up the nested path
      for (; i < name.length; i++) {
        // 'component' and 'components' resolve as a 'app/templates/components/'
        if (type === 'template' && name[0] === 'component') {
          name[i] = 'components';
        }
        pathName += '/' + name[i];
        moduleName += name[i] + '_';
      }
      // append fileName to the moduleName string
      moduleName += fileName;
    } else {
      pathName += name;
      moduleName = name;
    }
    // Classify the moduleName in format of `MattMaController`
    moduleName = stringUtils.classify(moduleName + '_' + type);

    // ignore the 'store' case, since it is already created
    var typeFolder = path.resolve('client/app', type + 's');

    // if client/app/[type](s) is not existed and it is not a test generator, simply create one
    if (!fs.existsSync(typeFolder) && type.indexOf('test') === -1) {
      fs.mkdirSync(typeFolder);
      gutil.log(
        gutil.colors.gray('[-log:] Created a new folder at '),
        gutil.colors.cyan('~/client/app/' + type + 's')
      );
    }

    // when generator type is route or component
    // it will also generate the template as well
    var srcPath =
      (type === 'route' || type === 'component') ?
        [{
          type:          type,
          generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '.js'
        }, {
          type:          'template',
          injection:     ( type === 'component' ) ? 'components' : void 0,
          generatorPath: path.join(__dirname, '..', 'skeletons/generators', 'template.js')
        }]
        : path.join(__dirname, '..', 'skeletons/generators', type) + '.js';

    var dirName, finalDirName, finalPath, destPath;

    // if type is test, or route-test or any sorts, it should append `-test` to the filename
    fileName = (type.indexOf('test') > -1) ? fileName + '-test' : fileName;

    // if it is a string, simple call generatorEngine once
    // else it is an object(array), repeat the generatorEngine call
    if (typeof srcPath === 'string') {
      dirName = (type === 'store') ? type : (type.slice(-1) === 's') ? type : type + 's';

      // Figure out the type is testing generator
      if (type.indexOf('test') > -1) {
        // Is it an Unit Test generator or Integration Test generator
        if (type.indexOf('-test') > -1) {
          var typeArray = type.split('-');
          finalDirName = 'tests/unit/' + typeArray[0] + 's';
        } else {
          finalDirName = dirName + '/integration';
        }
      } else {
        finalDirName = dirName;
      }

      finalPath = pathNested ? finalDirName + pathName : finalDirName;

      destPath = (type.indexOf('test') > -1) ?
      path.resolve('client') + '/' + finalPath :
      path.resolve('client/app') + '/' + finalPath;

      generatorEngine(type, srcPath, moduleName, fileName, destPath);
    } else {
      for (var j = 0, l = srcPath.length; j < l; j++) {
        var _type = srcPath[j].type;
        // when original type is 'component'
        // it will create a template file at 'templates/components' folder
        var injection = srcPath[j].injection;

        dirName = (_type === 'store') ? _type : (_type.slice(-1) === 's') ? _type : _type + 's';
        dirName = ( injection ) ? dirName + '/' + injection : dirName;

        finalPath = pathNested ? dirName + pathName : dirName;
        destPath = path.resolve('client/app') + '/' + finalPath;

        generatorEngine(
          _type, srcPath[j].generatorPath, moduleName, fileName, destPath
        );
      }
    }
  });
}

// Check the fullname attribute is correct or not
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;

function errorHandler (fullName) {
  gutil.log(
    gutil.colors.red('[-Error:] Invalid argument, expected: `type:name` got: '),
    gutil.colors.bold(fullName)
  );

  gutil.log(
    '[-Syntax:]',
    gutil.colors.cyan('type:name'), ' ex: em generate route:post'
  );

  gutil.log(
    gutil.colors.red('[-Error:]'),
    'See \'em generate --help\''
  );
  process.exit(0);
}

var generate = function (options) {
  // if the folder 'client/app' is not existed
  // can assume that the project may not be created by Ember Rocks
  if (!fs.existsSync('client') && !fs.existsSync('client/app')) {
    gutil.log(
      gutil.colors.red(
        '[-Error:] This project may not be created by \'Ember-Rocks\'\n'
      ),
      gutil.colors.red(
        '[-Error:] `em new [dirName]` does not install the NPM packages dependencies correctly'
      )
    );
    process.exit(1);
  }

  // Error out when user did not provide any argument
  if (argv._.length < 2) {
    gutil.log(gutil.colors.red('[-Error:] Missing type:name argument.'), 'ex: em new route:post');
    gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
    process.exit(0);
  }

  var typeAndName = argv._.slice()[1];

  if (!VALID_FULL_NAME_REGEXP.test(typeAndName)) {
    errorHandler(typeAndName);
  }

  var validTypes = [
    'adapter', 'component', 'controller', 'helper', 'initializer', 'mixin', 'model',
    'route', 'serializer', 'template', 'transform', 'util', 'view',
    'test', 'adapter-test', 'component-test', 'controller-test', 'helper-test',
    'initializer-test', 'mixin-test', 'model-test', 'route-test', 'serializer-test',
    'transform-test', 'util-test', 'view-test'
  ];
  var gen;
  var generatorAndTasks = typeAndName.split(':', 2);
  var type = generatorAndTasks[0];
  var name = generatorAndTasks[1];

  // type could be either route or routes
  type = (type.slice(-1) === 's') ? type.substring(0, type.length - 1) : type;

  // Type must be in the `validTypes` array
  if (validTypes.indexOf(type) > -1) {
    // Name must be a valid string
    if (name.length > 0) {
      gen = {
        type: type,
        name: name
      };
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(name),
        gutil.colors.red(' must be a valid string.')
      );
      gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
      process.exit(0);
    }
  } else {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(type),
      gutil.colors.red(' is not a valid type.')
    );
    gutil.log(
      gutil.colors.bold('[-note:] valid types are'),
      gutil.colors.cyan(validTypes.join(', '))
    );
    process.exit(0);
  }

  setupTask(gen);
  // Trigger the generator task
  gulp.start('gen');
};

module.exports = generate;
<commit_msg>fix the use case. if the template is existed, it won't abort the process when running generate route/component case<commit_after>'use strict';

var path = require('path');
var fs = require('fs');
var argv = require('minimist')(process.argv.slice(2));
var tildify = require('tildify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var stringUtils = require('../utils/string');

function validateComponentName (filename) {
  if (filename.indexOf('-') === -1) {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(filename),
      gutil.colors.red(' must be a dashize string. ex: my-component')
    );
    gutil.log(
      gutil.colors.red('[-Error:]  Generate task has been canceled')
    );
    process.exit(0);
  }
}

function checkFileExisted (fullFilePath, injection, fileName, ext, destPath) {
  if (fs.existsSync(fullFilePath)) {
    if (!!injection) {
      gutil.log(
        gutil.colors.red('[-Warning:] '),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.red('has existed at '),
        gutil.colors.magenta(tildify(destPath))
      );
      // Does not continue to generate file, but won't stop the process
      return true;
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.red('has existed at '),
        gutil.colors.magenta(tildify(destPath))
      );
      gutil.log(
        gutil.colors.red('[-Error:]  Generate task has been canceled')
      );
      // File is existed in the system, kill the process
      process.exit(0);
    }
  }
}

function generatorEngine (type, srcPath, injection, moduleName, fileName, destPath) {
  var ext = (type === 'template') ? '.hbs' : '.js';
  var fullFilePath = destPath + '/' + fileName + ext;

  // if the file has existed, it will abort the task
  // if return true, mean that it is an injection file, which already exist in the system
  // handle the case in the next condition
  var stopGenerateFile = checkFileExisted(fullFilePath, injection, fileName, ext, destPath);
  // check if template is existed or not, not going to kill the process
  // only stop the generator task on this operation
  if(stopGenerateFile) {
    return ;
  }

  var dasherizeName = '';
  var classifyName = '';
  var matcher;

  // if generating any testing files, need to clean up moduleName without "Test"
  if (type.indexOf('test') > -1) {
    if (type.indexOf('model-test') > -1) {
      moduleName = moduleName.replace(/ModelTest(\s+)?$/, '');
      dasherizeName = stringUtils.dasherize(moduleName);
    } else {
      matcher = stringUtils.classify(type);
      var dasherizeModuleName = moduleName.replace(new RegExp(matcher), '');

      moduleName = moduleName.replace(/Test(\s+)?$/, '');
      dasherizeName = stringUtils.dasherize(dasherizeModuleName);
    }
  } else {
    matcher = stringUtils.capitalize(type);
    var localModuleName = moduleName.replace(new RegExp(matcher), '');

    dasherizeName = stringUtils.dasherize(localModuleName);
    classifyName = stringUtils.classify(localModuleName);
  }

  // @TODO when generate multiple files on certain type
  // moduleName is not being defined correctly.
  // fine for now, since multiple file generation are only template file
  return gulp.src(srcPath)
    .pipe(replace(/__NAMESPACE__/g, moduleName))
    // __DASHERIZE_NAMESPACE__  mainly used in `-test` generator
    .pipe(replace(/__DASHERIZE_NAMESPACE__/g, dasherizeName))
    // __CLASSIFY_NAMESPACE__ mainly used in regular generator
    .pipe(replace(/__CLASSIFY_NAMESPACE__/g, classifyName))
    .pipe(rename({
      basename: fileName,
      extname:  ext
    }))
    .on('end', function () {
      gutil.log(
        gutil.colors.green('[-done:] Generate'),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.green('at'),
        gutil.colors.magenta(tildify(destPath))
      );
    })
    .pipe(gulp.dest(destPath));
}

function setupTask (generator) {
  // task: gen
  // @describe	generate an model,view,store,controller from base template
  return gulp.task('gen', function () {
    var type = generator.type;
    var name = generator.name;
    var pathName = '';
    var moduleName = '';
    var i = 0;
    var pathNested; // Boolean
    var fileName; // setup the fileName which used for rename module

    // based on the passing name arguments, to determine it is an nested folder structure
    // or it is a simple file generation. assign a var `fileName` for current file name
    if (name.indexOf('/') > -1) {
      name = name.split('/');
      pathNested = true;
      fileName = name.pop();
    } else {
      pathNested = false;
      fileName = name;
    }

    // handle the error case when arg is `component:foo`
    // component name has to be dash separated string
    // case 1: `em g component:name`         <= simple case
    // case 2: `em g component:nested/name`  <= nested case
    if (type === 'component') {
      validateComponentName(fileName);
    }
    // when type is template, name[0] is component, name of nest path has to be dashized string
    // case 3: `em g template:component/name`  <= nested case in template
    if (type === 'template' && pathNested && name[0] === 'component') {
      validateComponentName(fileName);
    }

    // Setup `pathName`
    // `moduleName` would be used inside replacement of template placeholder
    if (pathNested) {
      // build up the nested path
      for (; i < name.length; i++) {
        // 'component' and 'components' resolve as a 'app/templates/components/'
        if (type === 'template' && name[0] === 'component') {
          name[i] = 'components';
        }
        pathName += '/' + name[i];
        moduleName += name[i] + '_';
      }
      // append fileName to the moduleName string
      moduleName += fileName;
    } else {
      pathName += name;
      moduleName = name;
    }
    // Classify the moduleName in format of `MattMaController`
    moduleName = stringUtils.classify(moduleName + '_' + type);

    // ignore the 'store' case, since it is already created
    var typeFolder = path.resolve('client/app', type + 's');

    // if client/app/[type](s) is not existed and it is not a test generator, simply create one
    if (!fs.existsSync(typeFolder) && type.indexOf('test') === -1) {
      fs.mkdirSync(typeFolder);
      gutil.log(
        gutil.colors.gray('[-log:] Created a new folder at '),
        gutil.colors.cyan('~/client/app/' + type + 's')
      );
    }

    // when generator type is route or component
    // it will also generate the template as well
    var srcPath =
      (type === 'route' || type === 'component') ?
        [{
          type:          type,
          generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '.js'
        }, {
          type:          'template',
          injection:     ( type === 'component' ) ? 'components' : true,
          generatorPath: path.join(__dirname, '..', 'skeletons/generators/template.js')
        }]
        : path.join(__dirname, '..', 'skeletons/generators', type) + '.js';

    var dirName, finalDirName, finalPath, destPath;

    // if type is test, or route-test or any sorts, it should append `-test` to the filename
    fileName = (type.indexOf('test') > -1) ? fileName + '-test' : fileName;

    // if it is a string, simple call generatorEngine once
    // else it is an object(array), repeat the generatorEngine call
    if (typeof srcPath === 'string') {
      dirName = (type === 'store') ? type : (type.slice(-1) === 's') ? type : type + 's';

      // Figure out the type is testing generator
      if (type.indexOf('test') > -1) {
        // Is it an Unit Test generator or Integration Test generator
        if (type.indexOf('-test') > -1) {
          var typeArray = type.split('-');
          finalDirName = 'tests/unit/' + typeArray[0] + 's';
        } else {
          finalDirName = dirName + '/integration';
        }
      } else {
        finalDirName = dirName;
      }

      finalPath = pathNested ? finalDirName + pathName : finalDirName;

      destPath = (type.indexOf('test') > -1) ?
      path.resolve('client') + '/' + finalPath :
      path.resolve('client/app') + '/' + finalPath;

      generatorEngine(type, srcPath, null, moduleName, fileName, destPath);
    } else {
      for (var j = 0, l = srcPath.length; j < l; j++) {
        var _type = srcPath[j].type;
        // when original type is 'component'
        // it will create a template file at 'templates/components' folder
        var injection = srcPath[j].injection;

        dirName = (_type === 'store') ? _type : (_type.slice(-1) === 's') ? _type : _type + 's';
        dirName = (injection === 'components') ? dirName + '/' + injection : dirName;

        finalPath = pathNested ? dirName + pathName : dirName;
        destPath = path.resolve('client/app') + '/' + finalPath;

        generatorEngine(
          _type, srcPath[j].generatorPath, injection, moduleName, fileName, destPath
        );
      }
    }
  });
}

// Check the fullname attribute is correct or not
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;

function errorHandler (fullName) {
  gutil.log(
    gutil.colors.red('[-Error:] Invalid argument, expected: `type:name` got: '),
    gutil.colors.bold(fullName)
  );

  gutil.log(
    '[-Syntax:]',
    gutil.colors.cyan('type:name'), ' ex: em generate route:post'
  );

  gutil.log(
    gutil.colors.red('[-Error:]'),
    'See \'em generate --help\''
  );
  process.exit(0);
}

var generate = function (options) {
  // if the folder 'client/app' is not existed
  // can assume that the project may not be created by Ember Rocks
  if (!fs.existsSync('client') && !fs.existsSync('client/app')) {
    gutil.log(
      gutil.colors.red(
        '[-Error:] This project may not be created by \'Ember-Rocks\'\n'
      ),
      gutil.colors.red(
        '[-Error:] `em new [dirName]` does not install the NPM packages dependencies correctly'
      )
    );
    process.exit(1);
  }

  // Error out when user did not provide any argument
  if (argv._.length < 2) {
    gutil.log(gutil.colors.red('[-Error:] Missing type:name argument.'), 'ex: em new route:post');
    gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
    process.exit(0);
  }

  var typeAndName = argv._.slice()[1];

  if (!VALID_FULL_NAME_REGEXP.test(typeAndName)) {
    errorHandler(typeAndName);
  }

  var validTypes = [
    'adapter', 'component', 'controller', 'helper', 'initializer', 'mixin', 'model',
    'route', 'serializer', 'template', 'transform', 'util', 'view',
    'test', 'adapter-test', 'component-test', 'controller-test', 'helper-test',
    'initializer-test', 'mixin-test', 'model-test', 'route-test', 'serializer-test',
    'transform-test', 'util-test', 'view-test'
  ];
  var gen;
  var generatorAndTasks = typeAndName.split(':', 2);
  var type = generatorAndTasks[0];
  var name = generatorAndTasks[1];

  // type could be either route or routes
  type = (type.slice(-1) === 's') ? type.substring(0, type.length - 1) : type;

  // Type must be in the `validTypes` array
  if (validTypes.indexOf(type) > -1) {
    // Name must be a valid string
    if (name.length > 0) {
      gen = {
        type: type,
        name: name
      };
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(name),
        gutil.colors.red(' must be a valid string.')
      );
      gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
      process.exit(0);
    }
  } else {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(type),
      gutil.colors.red(' is not a valid type.')
    );
    gutil.log(
      gutil.colors.bold('[-note:] valid types are'),
      gutil.colors.cyan(validTypes.join(', '))
    );
    process.exit(0);
  }

  setupTask(gen);
  // Trigger the generator task
  gulp.start('gen');
};

module.exports = generate;
<|endoftext|>


<commit_before>'use strict'

//
// Code imported and modified from:
//
// https://github.com/atom/joanna/tree/0acd4d7a0ad0b23727e5dfb7f6e3f7cc88e9c085
//

const babylon = require('babylon')

module.exports = function generate (code) {
  return new Generator(code).generate()
}

const BABYLON_OPTIONS = {
  sourceType: 'module',
  plugins: ['asyncFunctions', 'jsx']
}

const API_STATUS_REGEX = /^(Private|Public|Essential|Extended|Section):/

class Generator {
  constructor (code) {
    this.code = code
  }

  generate () {
    this.objects = {}
    this.exports = {}
    this.classStack = []
    this.nodeStack = []
    this.visit(babylon.parse(this.code, BABYLON_OPTIONS))
    return {
      objects: this.objects,
      exports: this.exports
    }
  }

  visit (node) {
    this.nodeStack.push(node)
    try {
      switch (node.type) {
        case 'ExportDefaultDeclaration': return this.visitExportDefaultDeclaration(node)
        case 'ExportNamedDeclaration': return this.visitExportNamedDeclaration(node)
        case 'AssignmentExpression': return this.visitAssignmentExpression(node)
        case 'FunctionDeclaration': return this.visitFunctionDeclaration(node)
        case 'FunctionExpression': return this.visitFunctionDeclaration(node)
        case 'ClassDeclaration': return this.visitClassDeclaration(node)
        case 'ClassExpression': return this.visitClassDeclaration(node)
        case 'ClassMethod': return this.visitMethodDefinition(node)
        default: return this.visitNodeWithChildren(node)
      }
    } finally {
      this.nodeStack.pop()
    }
  }

  visitExportDefaultDeclaration (node) {
    const declaredObject = this.visit(node.declaration)
    if (declaredObject) {
      declaredObject.bindingType = 'exports'
      declaredObject.doc = this.getDocumentation()
      this.exports = declaredObject.range[0][0]
    }
  }

  visitExportNamedDeclaration (node) {
    if (node.declaration) {
      const declaredObject = this.visit(node.declaration)
      if (declaredObject) {
        this.expandObject(declaredObject, node.loc)
        declaredObject.doc = this.getDocumentation()
        declaredObject.bindingType = 'exportsProperty'
        this.exports[declaredObject.name] = declaredObject.range[0][0]
      }
    } else {
      for (var each of node.specifiers) {
        // export them
      }
    }
  }

  visitAssignmentExpression (node) {
    const left = node.left
    if (left.type === 'MemberExpression') {
      if (left.object.type === 'Identifier') {
        switch (left.object.name) {
          case 'exports': {
            const rightObject = this.visit(node.right)
            if (rightObject) {
              this.expandObject(rightObject, node.loc)
              rightObject.doc = this.getDocumentation()
              rightObject.bindingType = 'exportsProperty'
              this.exports[rightObject.name] = rightObject.range[0][0]
            }
            break
          }

          case 'module': {
            const rightObject = this.visit(node.right)
            if (rightObject) {
              rightObject.bindingType = 'exports'
              rightObject.doc = this.getDocumentation()
              this.exports = rightObject.range[0][0]
            }
            break
          }
        }
      }
    }
  }

  visitClassDeclaration (node) {
    this.classStack.push(this.addObject(node.loc, {
      type: 'class',
      name: node.id.name,
      superClass: node.superClass && node.superClass.name,
      bindingType: undefined,
      classProperties: [],
      prototypeProperties: [],
      doc: this.getDocumentation()
    }))
    this.visitNodeWithChildren(node)
    return this.classStack.pop()
  }

  visitMethodDefinition (node) {
    const currentClass = last(this.classStack)
    const currentMethod = this.addObject(node.loc, {
      type: 'function',
      kind: node.kind,
      async: node.async,
      generator: node.generator,
      name: node.key.name,
      doc: this.getDocumentation(),
      bindingType: node.static ? 'classProperty' : 'prototypeProperty',
      paramNames: node.params.map(paramNode => paramNode.name)
    })

    if (node.static) {
      currentMethod.bindingType = 'classProperty'
      currentClass.classProperties.push(currentMethod.range[0])
    } else {
      currentMethod.bindingType = 'prototypeProperty'
      currentClass.prototypeProperties.push(currentMethod.range[0])
    }

    return currentMethod
  }

  visitFunctionDeclaration (node) {
    return this.addObject(node.loc, {
      type: 'function',
      kind: node.kind,
      name: node.id && node.id.name,
      doc: this.getDocumentation(),
      paramNames: node.params.map(paramNode => paramNode.name),
      bindingType: 'variable'
    })
  }

  visitNodeWithChildren (node) {
    for (let key of Object.keys(node)) {
      const value = node[key]
      if (Array.isArray(value)) {
        for (let element of value) {
          if (element && typeof element.type === 'string') {
            this.visit(element)
          }
        }
      } else if (value && typeof value.type === 'string') {
        this.visit(value)
      }
    }
  }

  getDocumentation () {
    const leadingComments = this.getLeadingComments()
    if (!leadingComments) {
      return
    }

    let lastComment = null
    let groupedComments = []
    for (const comment of leadingComments) {
      if (lastComment && lastComment.loc.end.line === comment.loc.start.line - 1) {
        lastComment.value += '\n' + comment.value.replace(/^\s/, '')
        lastComment.loc.end = comment.loc.end
      } else {
        lastComment = {
          value: comment.value.replace(/^\s/, ''),
          loc: comment.loc
        }
        groupedComments.push(lastComment)
      }
    }

    // Add any previous comments as separate top-level items
    groupedComments.pop()
    for (const comment of groupedComments) {
      this.addObject(comment.loc, {
        type: 'comment',
        doc: comment.value
      })
    }

    return ensureAPIStatusTag(lastComment.value)
  }

  getLeadingComments () {
    let start = last(this.nodeStack).loc.start
    for (let i = this.nodeStack.length - 1; i >= 0; i--) {
      let node = this.nodeStack[i]
      if (node.loc.start.line < start.line || node.loc.start.column < start.column) {
        break
      }
      if (node.leadingComments) {
        return node.leadingComments
      }
    }
    return null
  }

  expandObject (object, newLocation) {
    delete this.objects[object.range[0][0]][object.range[0][1]]
    object.range = this.getRange(newLocation)
    this.objects[object.range[0][0]][object.range[0][1]] = object
  }

  addObject (location, object) {
    const range = this.getRange(location)
    if (!this.objects[range[0][0]]) {
      this.objects[range[0][0]] = {}
    }
    return (this.objects[range[0][0]][range[0][1]] = Object.assign(object, {range: range}))
  }

  getRange (location) {
    const line = location.start.line - 1
    const column = location.start.column
    const endLine = location.end.line - 1
    const endColumn = location.end.column
    return [[line, column], [endLine, endColumn]]
  }
}

function ensureAPIStatusTag (text) {
  if (API_STATUS_REGEX.test(text)) {
    return text
  } else {
    return 'Private: ' + text
  }
}

function last (stack) {
  return stack[stack.length - 1]
}
<commit_msg>Allow section definitions before uncommented methods.<commit_after>'use strict'

//
// Code imported and modified from:
//
// https://github.com/atom/joanna/tree/0acd4d7a0ad0b23727e5dfb7f6e3f7cc88e9c085
//

const babylon = require('babylon')

module.exports = function generate (code) {
  return new Generator(code).generate()
}

const BABYLON_OPTIONS = {
  sourceType: 'module',
  plugins: ['asyncFunctions', 'jsx']
}

const API_STATUS_REGEX = /^(Private|Public|Essential|Extended|Section):/
const SECTION_REGEX = /^\s*Section:/

class Generator {
  constructor (code) {
    this.code = code
  }

  generate () {
    this.objects = {}
    this.exports = {}
    this.classStack = []
    this.nodeStack = []
    this.visit(babylon.parse(this.code, BABYLON_OPTIONS))
    return {
      objects: this.objects,
      exports: this.exports
    }
  }

  visit (node) {
    this.nodeStack.push(node)
    try {
      switch (node.type) {
        case 'ExportDefaultDeclaration': return this.visitExportDefaultDeclaration(node)
        case 'ExportNamedDeclaration': return this.visitExportNamedDeclaration(node)
        case 'AssignmentExpression': return this.visitAssignmentExpression(node)
        case 'FunctionDeclaration': return this.visitFunctionDeclaration(node)
        case 'FunctionExpression': return this.visitFunctionDeclaration(node)
        case 'ClassDeclaration': return this.visitClassDeclaration(node)
        case 'ClassExpression': return this.visitClassDeclaration(node)
        case 'ClassMethod': return this.visitMethodDefinition(node)
        default: return this.visitNodeWithChildren(node)
      }
    } finally {
      this.nodeStack.pop()
    }
  }

  visitExportDefaultDeclaration (node) {
    const declaredObject = this.visit(node.declaration)
    if (declaredObject) {
      declaredObject.bindingType = 'exports'
      declaredObject.doc = this.getDocumentation()
      this.exports = declaredObject.range[0][0]
    }
  }

  visitExportNamedDeclaration (node) {
    if (node.declaration) {
      const declaredObject = this.visit(node.declaration)
      if (declaredObject) {
        this.expandObject(declaredObject, node.loc)
        declaredObject.doc = this.getDocumentation()
        declaredObject.bindingType = 'exportsProperty'
        this.exports[declaredObject.name] = declaredObject.range[0][0]
      }
    } else {
      for (var each of node.specifiers) {
        // export them
      }
    }
  }

  visitAssignmentExpression (node) {
    const left = node.left
    if (left.type === 'MemberExpression') {
      if (left.object.type === 'Identifier') {
        switch (left.object.name) {
          case 'exports': {
            const rightObject = this.visit(node.right)
            if (rightObject) {
              this.expandObject(rightObject, node.loc)
              rightObject.doc = this.getDocumentation()
              rightObject.bindingType = 'exportsProperty'
              this.exports[rightObject.name] = rightObject.range[0][0]
            }
            break
          }

          case 'module': {
            const rightObject = this.visit(node.right)
            if (rightObject) {
              rightObject.bindingType = 'exports'
              rightObject.doc = this.getDocumentation()
              this.exports = rightObject.range[0][0]
            }
            break
          }
        }
      }
    }
  }

  visitClassDeclaration (node) {
    this.classStack.push(this.addObject(node.loc, {
      type: 'class',
      name: node.id.name,
      superClass: node.superClass && node.superClass.name,
      bindingType: undefined,
      classProperties: [],
      prototypeProperties: [],
      doc: this.getDocumentation()
    }))
    this.visitNodeWithChildren(node)
    return this.classStack.pop()
  }

  visitMethodDefinition (node) {
    const currentClass = last(this.classStack)
    const currentMethod = this.addObject(node.loc, {
      type: 'function',
      kind: node.kind,
      async: node.async,
      generator: node.generator,
      name: node.key.name,
      doc: this.getDocumentation(),
      bindingType: node.static ? 'classProperty' : 'prototypeProperty',
      paramNames: node.params.map(paramNode => paramNode.name)
    })

    if (node.static) {
      currentMethod.bindingType = 'classProperty'
      currentClass.classProperties.push(currentMethod.range[0])
    } else {
      currentMethod.bindingType = 'prototypeProperty'
      currentClass.prototypeProperties.push(currentMethod.range[0])
    }

    return currentMethod
  }

  visitFunctionDeclaration (node) {
    return this.addObject(node.loc, {
      type: 'function',
      kind: node.kind,
      name: node.id && node.id.name,
      doc: this.getDocumentation(),
      paramNames: node.params.map(paramNode => paramNode.name),
      bindingType: 'variable'
    })
  }

  visitNodeWithChildren (node) {
    for (let key of Object.keys(node)) {
      const value = node[key]
      if (Array.isArray(value)) {
        for (let element of value) {
          if (element && typeof element.type === 'string') {
            this.visit(element)
          }
        }
      } else if (value && typeof value.type === 'string') {
        this.visit(value)
      }
    }
  }

  getDocumentation () {
    const leadingComments = this.getLeadingComments()
    if (!leadingComments) {
      return
    }

    let lastComment = null
    let groupedComments = []
    for (const comment of leadingComments) {
      if (lastComment && lastComment.loc.end.line === comment.loc.start.line - 1) {
        lastComment.value += '\n' + comment.value.replace(/^\s/, '')
        lastComment.loc.end = comment.loc.end
      } else {
        lastComment = {
          value: comment.value.replace(/^\s/, ''),
          loc: comment.loc
        }
        groupedComments.push(lastComment)
      }
    }

    let lastIsSection = SECTION_REGEX.test(lastComment.value)
    if (!lastIsSection) {
      groupedComments.pop()
    }

    // Add any previous comments as separate top-level items
    for (const comment of groupedComments) {
      this.addObject(comment.loc, {
        type: 'comment',
        doc: comment.value
      })
    }

    if (lastIsSection) {
      return
    } else {
      return ensureAPIStatusTag(lastComment.value)
    }
  }

  getLeadingComments () {
    let start = last(this.nodeStack).loc.start
    for (let i = this.nodeStack.length - 1; i >= 0; i--) {
      let node = this.nodeStack[i]
      if (node.loc.start.line < start.line || node.loc.start.column < start.column) {
        break
      }
      if (node.leadingComments) {
        return node.leadingComments
      }
    }
    return null
  }

  expandObject (object, newLocation) {
    delete this.objects[object.range[0][0]][object.range[0][1]]
    object.range = this.getRange(newLocation)
    this.objects[object.range[0][0]][object.range[0][1]] = object
  }

  addObject (location, object) {
    const range = this.getRange(location)
    if (!this.objects[range[0][0]]) {
      this.objects[range[0][0]] = {}
    }
    return (this.objects[range[0][0]][range[0][1]] = Object.assign(object, {range: range}))
  }

  getRange (location) {
    const line = location.start.line - 1
    const column = location.start.column
    const endLine = location.end.line - 1
    const endColumn = location.end.column
    return [[line, column], [endLine, endColumn]]
  }
}

function ensureAPIStatusTag (text) {
  if (API_STATUS_REGEX.test(text)) {
    return text
  } else {
    return 'Private: ' + text
  }
}

function last (stack) {
  return stack[stack.length - 1]
}
<|endoftext|>


<commit_before>exports.run = async function ({ client, msg }) {

    if (!msg.member.isAdmin) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'Insufficient Permissions',
        } });
    }

    if (client.voiceConnections.isConnected(msg.channel.guild.id)) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'Not Connected',
            description: 'I\'m not connected to any voicechannels'
        } });
    }

    if (!msg.member.voiceState.channelID) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'You need to be in a voicechannel.'
        } });
    }

    if (!client.getChannel(msg.member.voiceState.channelID).hasPermissions(client.user.id, 'voiceConnect', 'voiceSpeak')) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'Unable to Connect',
            description: 'This channel doesn\'t allow me to connect/speak.'
        } });
    }

    client.voiceConnections.get(msg.channel.guild.id).switchChannel(msg.member.voiceState.channelID);

};

exports.usage = {
    main: '{prefix}{command}',
    args: '',
    description: 'Moves the bot to the sender\'s voicechannel'
};

exports.aliases = ['mh'];
<commit_msg>check if NOT connected<commit_after>exports.run = async function ({ client, msg }) {

    if (!msg.member.isAdmin) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'Insufficient Permissions',
        } });
    }

    if (!client.voiceConnections.isConnected(msg.channel.guild.id)) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'Not Connected',
            description: 'I\'m not connected to any voicechannels'
        } });
    }

    if (!msg.member.voiceState.channelID) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'You need to be in a voicechannel.'
        } });
    }

    if (!client.getChannel(msg.member.voiceState.channelID).hasPermissions(client.user.id, 'voiceConnect', 'voiceSpeak')) {
        return msg.channel.createMessage({ embed: {
            color: client.config.options.embedColour,
            title: 'Unable to Connect',
            description: 'This channel doesn\'t allow me to connect/speak.'
        } });
    }

    client.voiceConnections.get(msg.channel.guild.id).switchChannel(msg.member.voiceState.channelID);

};

exports.usage = {
    main: '{prefix}{command}',
    args: '',
    description: 'Moves the bot to the sender\'s voicechannel'
};

exports.aliases = ['mh'];
<|endoftext|>


<commit_before>/*
 * NTwitBot - generate.js
 * Copyright (C) 2016-2017 Jordan Sne
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Tweet generator class.
 */
module.exports = class Generate {

    /**
     * Initialize the class.
     * @param {Util} utils - The utilities object needed for logging, etc.
     */
    constructor(utils) {
        // Minimum words for a message
        this.MIN_WORDS = 4;
        // Maximum characters a message can be
        this.MAX_CHARS = 140;

        this.utils = utils;
    }

    /**
     * Generates a tweet message.
     * @param {Object} wordDB - The database object of tweets.
     * @return {string} The generated message.
     */
    generateTweet(wordDB) {
        let wordStack, firstWord = 0;
        const firstWords = this.getFirstWords(wordDB);

        let done = false;
        while (!done) {
            // Initialize the word stack with two first words
            wordStack = firstWords[firstWord].split(' ').slice(0);

            // Construct the rest of the tweet
            /* eslint no-constant-condition: 'off' */
            while (true) {
                const numOfWords = wordStack.length;
                const key = this.keyOf(wordStack);
                const currentMessage = this.compileMessage(wordStack);

                // If the sentence naturally comes to an end with punctuation
                if (this.utils.endsWithPunc(wordStack[numOfWords - 1])) {
                    if (numOfWords >= this.MIN_WORDS) {
                        if (currentMessage.length <= this.MAX_CHARS) {
                            done = true;
                            break;
                        } else {
                            this.popWord(wordStack, wordDB);
                            continue;
                        }
                    } else {
                        this.popWord(wordStack, wordDB);
                        if (wordStack.length === 0) {
                            break;
                        } else {
                            continue;
                        }
                    }
                }

                // If there is an entry for the next word
                if (wordDB.hasOwnProperty(key)) {
                    const nextWord = this.getRandomWord(wordDB, key);

                    if (nextWord !== null) {
                        wordStack.push(nextWord);
                        continue;
                    } else {
                        this.popWord(wordStack, wordDB);

                        if (wordStack.length === 0) {
                            break;
                        } else {
                            continue;
                        }
                    }
                }

                // Otherwise end the message
                done = true;
                break;
            }

            firstWord++;
        }

        return this.compileMessage(wordStack);
    }

    /**
     * Returns the key for the next word to be added to the wordStack.
     * @private
     * @param {Stack} wordStack - The stack of words for the tweet.
     * @return {string} The key for the next word.
     */
    keyOf(wordStack) {
        return wordStack[wordStack.length - 2] + ' ' + wordStack[wordStack.length - 1];
    }

    /**
     * Get a list of possible first words.
     * @private
     * @param {Object} wordDB - The database object of tweets.
     * @return {Array} A shuffled array of possible first words.
     */
    getFirstWords(wordDB) {
        const first = [];

        for (const key in wordDB) {
            if (wordDB.hasOwnProperty(key)) {
                if (this.utils.isUpperCase(key.charAt(0))) {
                    first.push(key);
                }
            }
        }

        return this.utils.shuffleArray(first);
    }

    /**
     * Returns a random word from the wordDB that has the specified key.
     * @private
     * @param {Object} wordDB - The database object of tweets.
     * @param {string} key - The key to be used for the wordDB.
     * @return {string} A random word. Null if no word exists.
     */
    getRandomWord(wordDB, key) {
        const numOfWords = wordDB[key].length;
        const randWordArray = this.utils.generateShuffledArray(numOfWords);

        // Search for a new word entry that hasn't been popped
        for (let i = 0; i < numOfWords; i++) {
            const nextWord = randWordArray[i];
            const wordEntry = wordDB[key][nextWord];

            if (!wordEntry.beenPopped) {
                return wordEntry.word;
            }
        }

        return null;
    }

    /**
     * Compiles the word stack into a single string.
     * @private
     * @param {Stack} wordStack - The stack of words for the tweet.
     * @return {string} The compiled string.
     */
    compileMessage(wordStack) {
        let result = '';

        for (let i = 0; i < wordStack.length; i++) {
            result += wordStack[i];

            if (i < wordStack.length - 1) {
                result += ' ';
            }
        }

        if (!this.utils.endsWithPunc(result)) {
            result += '.';
        }

        return result;
    }

    /**
     * Pops the last word or the only two words off the stack and marks the entries as beenPopped.
     * @private
     * @param {Stack} wordStack - Current array of strings (words).
     * @param {Object} wordDB - The database object of tweets.
     */
    popWord(wordStack, wordDB) {
        const numOfWords = wordStack.length;

        if (numOfWords >= 3) {
            const key = wordStack[numOfWords - 3] + ' ' + wordStack[numOfWords - 2];
            const word = wordStack[numOfWords - 1];

            this.getWordObject(wordDB[key], word).beenPopped = true;
            wordStack.pop();
        } else {
            wordStack.splice(0);
        }
    }

    /**
     * Returns the object where the word attribute is equal to the specified word parameter.
     * @private
     * @param {Array} entryList - The array of database entries.
     * @param {string} word - The word to search for.
     * @return {Object} The entry that contains the word. Null if not found.
     */
    getWordObject(entryList, word) {
        for (const entry of entryList) {
            if (entry.word === word) {
                return entry;
            }
        }

        return null;
    }

};
<commit_msg>Rename getWordObject to getWordEntry in Retrieve<commit_after>/*
 * NTwitBot - generate.js
 * Copyright (C) 2016-2017 Jordan Sne
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Tweet generator class.
 */
module.exports = class Generate {

    /**
     * Initialize the class.
     * @param {Util} utils - The utilities object needed for logging, etc.
     */
    constructor(utils) {
        // Minimum words for a message
        this.MIN_WORDS = 4;
        // Maximum characters a message can be
        this.MAX_CHARS = 140;

        this.utils = utils;
    }

    /**
     * Generates a tweet message.
     * @param {Object} wordDB - The database object of tweets.
     * @return {string} The generated message.
     */
    generateTweet(wordDB) {
        let wordStack, firstWord = 0;
        const firstWords = this.getFirstWords(wordDB);

        let done = false;
        while (!done) {
            // Initialize the word stack with two first words
            wordStack = firstWords[firstWord].split(' ').slice(0);

            // Construct the rest of the tweet
            /* eslint no-constant-condition: 'off' */
            while (true) {
                const numOfWords = wordStack.length;
                const key = this.keyOf(wordStack);
                const currentMessage = this.compileMessage(wordStack);

                // If the sentence naturally comes to an end with punctuation
                if (this.utils.endsWithPunc(wordStack[numOfWords - 1])) {
                    if (numOfWords >= this.MIN_WORDS) {
                        if (currentMessage.length <= this.MAX_CHARS) {
                            done = true;
                            break;
                        } else {
                            this.popWord(wordStack, wordDB);
                            continue;
                        }
                    } else {
                        this.popWord(wordStack, wordDB);
                        if (wordStack.length === 0) {
                            break;
                        } else {
                            continue;
                        }
                    }
                }

                // If there is an entry for the next word
                if (wordDB.hasOwnProperty(key)) {
                    const nextWord = this.getRandomWord(wordDB, key);

                    if (nextWord !== null) {
                        wordStack.push(nextWord);
                        continue;
                    } else {
                        this.popWord(wordStack, wordDB);

                        if (wordStack.length === 0) {
                            break;
                        } else {
                            continue;
                        }
                    }
                }

                // Otherwise end the message
                done = true;
                break;
            }

            firstWord++;
        }

        return this.compileMessage(wordStack);
    }

    /**
     * Returns the key for the next word to be added to the wordStack.
     * @private
     * @param {Stack} wordStack - The stack of words for the tweet.
     * @return {string} The key for the next word.
     */
    keyOf(wordStack) {
        return wordStack[wordStack.length - 2] + ' ' + wordStack[wordStack.length - 1];
    }

    /**
     * Get a list of possible first words.
     * @private
     * @param {Object} wordDB - The database object of tweets.
     * @return {Array} A shuffled array of possible first words.
     */
    getFirstWords(wordDB) {
        const first = [];

        for (const key in wordDB) {
            if (wordDB.hasOwnProperty(key)) {
                if (this.utils.isUpperCase(key.charAt(0))) {
                    first.push(key);
                }
            }
        }

        return this.utils.shuffleArray(first);
    }

    /**
     * Returns a random word from the wordDB that has the specified key.
     * @private
     * @param {Object} wordDB - The database object of tweets.
     * @param {string} key - The key to be used for the wordDB.
     * @return {string} A random word. Null if no word exists.
     */
    getRandomWord(wordDB, key) {
        const numOfWords = wordDB[key].length;
        const randWordArray = this.utils.generateShuffledArray(numOfWords);

        // Search for a new word entry that hasn't been popped
        for (let i = 0; i < numOfWords; i++) {
            const nextWord = randWordArray[i];
            const wordEntry = wordDB[key][nextWord];

            if (!wordEntry.beenPopped) {
                return wordEntry.word;
            }
        }

        return null;
    }

    /**
     * Compiles the word stack into a single string.
     * @private
     * @param {Stack} wordStack - The stack of words for the tweet.
     * @return {string} The compiled string.
     */
    compileMessage(wordStack) {
        let result = '';

        for (let i = 0; i < wordStack.length; i++) {
            result += wordStack[i];

            if (i < wordStack.length - 1) {
                result += ' ';
            }
        }

        if (!this.utils.endsWithPunc(result)) {
            result += '.';
        }

        return result;
    }

    /**
     * Pops the last word or the only two words off the stack and marks the entries as beenPopped.
     * @private
     * @param {Stack} wordStack - Current array of strings (words).
     * @param {Object} wordDB - The database object of tweets.
     */
    popWord(wordStack, wordDB) {
        const numOfWords = wordStack.length;

        if (numOfWords >= 3) {
            const key = wordStack[numOfWords - 3] + ' ' + wordStack[numOfWords - 2];
            const word = wordStack[numOfWords - 1];

            this.getWordEntry(wordDB[key], word).beenPopped = true;
            wordStack.pop();
        } else {
            wordStack.splice(0);
        }
    }

    /**
     * Returns the entry where the word attribute is equal to the specified word parameter.
     * @private
     * @param {Array} entryList - The array of database entries.
     * @param {string} word - The word to search for.
     * @return {Object} The entry that contains the word. Null if not found.
     */
    getWordEntry(entryList, word) {
        for (const entry of entryList) {
            if (entry.word === word) {
                return entry;
            }
        }

        return null;
    }

};
<|endoftext|>


<commit_before>const { against, when, isString } = require('match-iz')
const { pipe, flow, memo } = require('./fp')

const makeRegExpFromWildcardString = memo(str => {
  if (!isString(str) || !str.length) {
    throw new TypeError('Please pass a non-empty string')
  }
  return pipe(
    str
      .replace(rxConsecutiveWildcards, '*')
      .split('*')
      .map(x => x.trim())
      .map(escapeStringForRegExp),

    against(
      when(hasNoWildcards)(templateMatchExact),
      when(hasNoWildcardAtStart)(flow(insertWildcards, templateMatchStart)),
      when(hasNoWildcardAtEnd)(flow(insertWildcards, templateMatchEnd))
    ),

    $ => new RegExp($, 'i')
  )
})

//
// Helpers
//

const rxEscape = /[.*+?^${}()|[\]\\]/g
const rxConsecutiveWildcards = /\*{2,}/g

const hasNoWildcards = x => x.length === 1
const hasNoWildcardAtStart = x => x.at(0) !== ''
const hasNoWildcardAtEnd = x => x.at(-1) !== ''

const insertWildcards = x => x.join('(.*)')
const templateMatchExact = x => `^${x}$`
const templateMatchStart = x => `^${x}`
const templateMatchEnd = x => `${x}$`

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeStringForRegExp(str) {
  if (!isString(str)) {
    throw new TypeError('Please pass a string')
  }
  // $& means the whole matched string
  return str.replace(rxEscape, '\\$&')
}

module.exports = {
  makeRegExpFromWildcardString,
  escapeStringForRegExp
}
<commit_msg>Revert makeRegExpFromWildcardString() refactor<commit_after>const { memo } = require('./fp')

//
// Wildcard helpers
//

const makeRegExpFromWildcardString = memo(str => {
  if (!str.length) {
    throw new Error('String should not be empty')
  }
  const sanitized = str
    .split('*')
    .map(x => x.trim())
    .map(escapeStringForRegExp)

  // Allow matching of wildcards
  const rxString = sanitized.join('(.*)')

  switch (true) {
    // No wildcards? Match string exactly
    case sanitized.length === 1:
      return new RegExp(`^${rxString}$`)

    // No wildcard at the start? Match string-start exactly
    case sanitized[0] !== '':
      return new RegExp(`^${rxString}`)

    // No wildcard at the end? Match string-end exactly
    case sanitized[sanitized.length - 1] !== '':
      return new RegExp(`${rxString}$`)
  }
  return new RegExp(rxString)
})

function escapeStringForRegExp(string) {
  if (typeof string !== 'string') {
    throw new TypeError('Expected string to be passed-in')
  }
  // $& means the whole matched string
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

module.exports = {
  makeRegExpFromWildcardString,
  escapeStringForRegExp
}
<|endoftext|>


<commit_before>var JSGestureRecognizer,
    recognizers = {};

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        var fSlice = Array.prototype.slice,
            aArgs = fSlice.call(arguments, 1),
            fToBind = this,
            fNOP = function () {},
            fBound = function () {
                return fToBind.apply(this instanceof fNOP ? this : oThis || window, aArgs.concat(fSlice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

function extend() {
    function ext(destination, source) {
        var prop;
        for (prop in source) {
            if (source.hasOwnProperty(prop)) {
                destination[prop] = source[prop];
            }
        }
    }
    ext(arguments["0"], arguments["1"]);
}

JSGestureRecognizer = function (target, touchCount) {
    this.target = target;
    this.touchCount = touchCount;
    this.init();
};
JSGestureRecognizer.prototype = {
    timer: null,
    cancels: [],

    listeners: [],
    shouldFire: false,
    init: function () {},

    start: function () {},
    move: function () {},
    end: function () {},

    invalidate: function () {
        this.shouldFire = false;
        clearTimeout(this.timer);
        this.timer = null;
    },

    validate: function () {
        this.shouldFire = true;
    },

    addListeners: function (types) {
        var l = types.length,
            i = 0,
            type,
            cb;
        for (; i < l; i += 1) {
            type = types[i];
            cb = this[type.replace(/^touch/, '')].bind(this);
            this.listeners.push({ type: type, cb: cb });
            this.target.__addEventListener(type, cb, false);
        }
    },

    fire: function (e, custom, suffix) {
        if (!this.shouldFire) {
            return;
        }

        var evt = document.createEvent('Events'),
            name = (suffix) ? this.name + suffix : this.name,
            gesture,
            i = 0;

        for (gesture in this.target.__recognizers) {
            if (this.target.__recognizers.hasOwnProperty(gesture) && this.cancels.indexOf(gesture) !== -1) {
                gesture = this.target.__recognizers[gesture];
                i = gesture.length;
                while (i) {
                    i -= 1;
                    gesture[i].recognizer.invalidate();
                }
            }
        }

        evt.initEvent(name, true, true, e);
        extend(evt, custom || {});
        this.target.dispatchEvent(evt);
    },

    removeListeners: function () {
        var i = this.listeners.length,
            listener;
        while (i) {
            i -= 1;
            listener = this.listeners[i];
            this.target.__removeEventListener(listener.type, listener.cb, false);
        }
    }
};

window.CreateRecognizer = function (name, proto) {
    recognizers[name] = function (target, touchCount) {
        this.target = target;
        this.touchCount = (touchCount < this.minTouches) ? this.minTouches : touchCount;
        this.init();
        this.addListeners(['touchstart', 'touchmove', 'touchend']);
    };

    function noop() {}
    function inherit(ctor, superCtor) {
        if (Object.create) {
            ctor.prototype = Object.create(superCtor.prototype, {
                constructor: { value: ctor, enumerable: false }
            });
        } else {
            noop.prototype = superCtor.prototype;
            ctor.prototype = new noop();
            ctor.prototype.constructor = superCtor;
        }
    }

    inherit(recognizers[name], JSGestureRecognizer);
    extend(recognizers[name].prototype, proto);
    recognizers[name].prototype.name = name;
    return recognizers[name];
};

function findOthers(type, nType, touchCount, el) {
    var types = [nType, nType + 'start', nType + 'end'],
        l = types.length,
        i = 0,
        n,
        etype;

    for (; i < l; i += 1) {
        etype = types[i];

        if (type === etype) {
            continue;
        }

        if (el.__recognizers.hasOwnProperty(etype) && el.__recognizers[etype].length) {
            n = el.__recognizers[etype].length;
            while (n) {
                n -= 1;
                if (el.__recognizers[etype][n].touches === touchCount) {
                    return el.__recognizers[etype][n];
                }
            }
        }
    }
    return null;
}

function getType(type) {
    return type.replace(/\:\d+$/, '');
}

function getNormalizedType(type) {
    return getType(type.replace(/start(\:\d+)?$|end(\:\d+)?$/, ''));
}

function getTouchCount(type) {
    var match = type.match(/\:(\d+)$/);
    return (match && match.length) ? parseInt(match[1], 10) : 1;
}

HTMLElement.prototype.__addEventListener = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.__removeEventListener = HTMLElement.prototype.removeEventListener;
HTMLElement.prototype.addEventListener = function (type, listener, useCapture) {
    var nType = getNormalizedType(type),
        touchCount = getTouchCount(type),
        listened;

    type = getType(type);

    if (!this.__recognizers) {
        this.__recognizers = {};
    }

    if (!this.__recognizers.hasOwnProperty(nType)) {
        this.__recognizers[nType] = [];
    }
    if (!this.__recognizers.hasOwnProperty(type)) {
        this.__recognizers[type] = [];
    }

    if (recognizers.hasOwnProperty(nType)) {
        listened = findOthers(type, nType, touchCount, this);

        if (!listened) {
            this.__recognizers[type].push({ type: type, touches: touchCount, recognizer: new recognizers[nType](this, touchCount), cb: listener });
        } else {
            this.__recognizers[type].push({ type: type, touches: touchCount, recognizer: listened.recognizer, cb: listener });
        }
    }

    return this.__addEventListener(type, listener, useCapture);
};

HTMLElement.prototype.removeEventListener = function (type, listener, useCapture) {
    var nType = getNormalizedType(type),
        touchCount = getTouchCount(type),
        i,
        obj;

    type = getType(type);

    if (this.__recognizers && this.__recognizers.hasOwnProperty(nType)) {
        i = this.__recognizers[nType].length;
        while (i) {
            i -= 1;
            obj = this.__recognizers[nType][i];
            if ((!listener || (obj.cb === listener)) && !findOthers(type, nType, touchCount, this)) {
                this.__recognizers[type][i].recognizer.removeListeners();
                delete this.__recognizers[type][i];
            }
        }
    }

    return this.__removeEventListener(type, listener, useCapture);
};
<commit_msg>If browser does not have TouchEvents, emulate them for mouse events. closes gh-1<commit_after>var JSGestureRecognizer,
    recognizers = {};

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        var fSlice = Array.prototype.slice,
            aArgs = fSlice.call(arguments, 1),
            fToBind = this,
            fNOP = function () {},
            fBound = function () {
                return fToBind.apply(this instanceof fNOP ? this : oThis || window, aArgs.concat(fSlice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

function extend() {
    function ext(destination, source) {
        var prop;
        for (prop in source) {
            if (source.hasOwnProperty(prop)) {
                destination[prop] = source[prop];
            }
        }
    }
    ext(arguments["0"], arguments["1"]);
}

function normalizeEventName(name) {
    if (typeof document.createTouch !== 'undefined') {
        return name;
    }

    switch (name) {
    case 'touchstart':
        return 'mousedown';
    case 'touchmove':
        return 'mousemove';
    case 'touchend':
        return 'mouseup';
    default:
        return name;
    }
}

function normalizeEvent(e) {
    if (typeof document.createTouch !== 'undefined') {
        return;
    }

    e.changedTouches = [{ pageX: e.pageX, pageY: e.pageY }];
    e.touches = [{ pageX: e.pageX, pageY: e.pageY }];
}

JSGestureRecognizer = function (target, touchCount) {
    this.target = target;
    this.touchCount = touchCount;
    this.init();
};
JSGestureRecognizer.prototype = {
    timer: null,
    cancels: [],

    listeners: [],
    shouldFire: false,
    init: function () {},

    _start: function (e) {
        this.isTracking = true;
        normalizeEvent(e);
        this.start(e);
    },
    _move: function (e) {
        if (!this.isTracking) {
            return;
        }

        normalizeEvent(e);
        this.move(e);
    },
    _end: function (e) {
        this.isTracking = false;
        normalizeEvent(e);
        this.end(e);
    },

    start: function () {},
    move: function () {},
    end: function () {},

    invalidate: function () {
        this.shouldFire = false;
        clearTimeout(this.timer);
        this.timer = null;
    },

    validate: function () {
        this.shouldFire = true;
    },

    addListeners: function (types) {
        var l = types.length,
            i = 0,
            type,
            oType,
            cb;
        for (; i < l; i += 1) {
            oType = types[i];
            type = normalizeEventName(oType);
            cb = this['_' + oType.replace(/^touch/, '')].bind(this);
            this.listeners.push({ type: oType, cb: cb });
            this.target.__addEventListener(type, cb, false);
        }
    },

    fire: function (e, custom, suffix) {
        if (!this.shouldFire) {
            return;
        }

        var evt = document.createEvent('Events'),
            name = (suffix) ? this.name + suffix : this.name,
            gesture,
            i = 0;

        for (gesture in this.target.__recognizers) {
            if (this.target.__recognizers.hasOwnProperty(gesture) && this.cancels.indexOf(gesture) !== -1) {
                gesture = this.target.__recognizers[gesture];
                i = gesture.length;
                while (i) {
                    i -= 1;
                    gesture[i].recognizer.invalidate();
                }
            }
        }

        evt.initEvent(name, true, true, e);
        extend(evt, custom || {});
        this.target.dispatchEvent(evt);
    },

    removeListeners: function () {
        var i = this.listeners.length,
            listener;
        while (i) {
            i -= 1;
            listener = this.listeners[i];
            this.target.__removeEventListener(listener.type, listener.cb, false);
        }
    }
};

window.CreateRecognizer = function (name, proto) {
    recognizers[name] = function (target, touchCount) {
        this.target = target;
        this.touchCount = (touchCount < this.minTouches) ? this.minTouches : touchCount;
        this.init();
        this.addListeners(['touchstart', 'touchmove', 'touchend']);
    };

    function noop() {}
    function inherit(ctor, superCtor) {
        if (Object.create) {
            ctor.prototype = Object.create(superCtor.prototype, {
                constructor: { value: ctor, enumerable: false }
            });
        } else {
            noop.prototype = superCtor.prototype;
            ctor.prototype = new noop();
            ctor.prototype.constructor = superCtor;
        }
    }

    inherit(recognizers[name], JSGestureRecognizer);
    extend(recognizers[name].prototype, proto);
    recognizers[name].prototype.name = name;
    return recognizers[name];
};

function findOthers(type, nType, touchCount, el) {
    var types = [nType, nType + 'start', nType + 'end'],
        l = types.length,
        i = 0,
        n,
        etype;

    for (; i < l; i += 1) {
        etype = types[i];

        if (type === etype) {
            continue;
        }

        if (el.__recognizers.hasOwnProperty(etype) && el.__recognizers[etype].length) {
            n = el.__recognizers[etype].length;
            while (n) {
                n -= 1;
                if (el.__recognizers[etype][n].touches === touchCount) {
                    return el.__recognizers[etype][n];
                }
            }
        }
    }
    return null;
}

function getType(type) {
    return type.replace(/\:\d+$/, '');
}

function getNormalizedType(type) {
    return getType(type.replace(/start(\:\d+)?$|end(\:\d+)?$/, ''));
}

function getTouchCount(type) {
    var match = type.match(/\:(\d+)$/);
    return (match && match.length) ? parseInt(match[1], 10) : 1;
}

HTMLElement.prototype.__addEventListener = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.__removeEventListener = HTMLElement.prototype.removeEventListener;
HTMLElement.prototype.addEventListener = function (type, listener, useCapture) {
    var nType = getNormalizedType(type),
        touchCount = getTouchCount(type),
        listened;

    type = getType(type);

    if (!this.__recognizers) {
        this.__recognizers = {};
    }

    if (!this.__recognizers.hasOwnProperty(nType)) {
        this.__recognizers[nType] = [];
    }
    if (!this.__recognizers.hasOwnProperty(type)) {
        this.__recognizers[type] = [];
    }

    if (recognizers.hasOwnProperty(nType)) {
        listened = findOthers(type, nType, touchCount, this);

        if (!listened) {
            this.__recognizers[type].push({ type: type, touches: touchCount, recognizer: new recognizers[nType](this, touchCount), cb: listener });
        } else {
            this.__recognizers[type].push({ type: type, touches: touchCount, recognizer: listened.recognizer, cb: listener });
        }
    }

    return this.__addEventListener(type, listener, useCapture);
};

HTMLElement.prototype.removeEventListener = function (type, listener, useCapture) {
    var nType = getNormalizedType(type),
        touchCount = getTouchCount(type),
        i,
        obj;

    type = getType(type);

    if (this.__recognizers && this.__recognizers.hasOwnProperty(nType)) {
        i = this.__recognizers[nType].length;
        while (i) {
            i -= 1;
            obj = this.__recognizers[nType][i];
            if ((!listener || (obj.cb === listener)) && !findOthers(type, nType, touchCount, this)) {
                this.__recognizers[type][i].recognizer.removeListeners();
                delete this.__recognizers[type][i];
            }
        }
    }

    return this.__removeEventListener(type, listener, useCapture);
};
<|endoftext|>