<commit_before>'use strict';

var pathUtil = require('path');
var fs = require('fs');
var Q = require('q');
var mkdirp = require('mkdirp');

var exists = require('./exists');
var matcher = require('./utils/matcher');
var mode = require('./utils/mode');
var inspector = require('./inspector');
var fileOps = require('./file_ops');

var parseOptions = function (options, from) {
  var parsedOptions = {};
  options = options || {};

  parsedOptions.overwrite = options.overwrite;

  if (options.matching) {
    parsedOptions.allowedToCopy = matcher.create(options.matching, from);
  } else {
    parsedOptions.allowedToCopy = function () {
      // Default behaviour - copy everything.
      return true;
    };
  }

  return parsedOptions;
};

var generateNoSourceError = function (path) {
  var err = new Error("Path to copy doesn't exist " + path);
  err.code = 'ENOENT';
  return err;
};

var generateDestinationExistsError = function (path) {
  var err = new Error('Destination path already exists ' + path);
  err.code = 'EEXIST';
  return err;
};

// ---------------------------------------------------------
// Sync
// ---------------------------------------------------------

var copySync = function (inspectData, to) {
  var mod = mode.normalizeFileMode(inspectData.mode);
  if (inspectData.type === 'dir') {
    mkdirp.sync(to, { mode: mod });
  } else if (inspectData.type === 'file') {
    var data = fs.readFileSync(inspectData.absolutePath);
    fileOps.write(to, data, { mode: mod });
  } else if (inspectData.type === 'symlink') {
    var symlinkPointsAt = fs.readlinkSync(inspectData.absolutePath);
    try {
      fs.symlinkSync(symlinkPointsAt, to);
    } catch (err) {
      // There is already file/symlink with this name on destination location.
      // Must erase it manually, otherwise system won't allow us to place symlink there.
      if (err.code === 'EEXIST') {
        fs.unlinkSync(to);
        // Retry...
        fs.symlinkSync(symlinkPointsAt, to);
      } else {
        throw err;
      }
    }
  }
};

var sync = function (from, to, options) {
  options = parseOptions(options, from);

  if (!exists.sync(from)) {
    throw generateNoSourceError(from);
  }

  if (exists.sync(to) && !options.overwrite) {
    throw generateDestinationExistsError(to);
  }

  var walker = inspector.createTreeWalkerSync(from);
  while (walker.hasNext()) {
    var inspectData = walker.getNext();
    var destPath = pathUtil.join(to, inspectData.relativePath);
    if (options.allowedToCopy(inspectData.absolutePath)) {
      copySync(inspectData, destPath);
    }
  }
};

// ---------------------------------------------------------
// Async
// ---------------------------------------------------------

var promisedReadFile = Q.denodeify(fs.readFile);
var promisedSymlink = Q.denodeify(fs.symlink);
var promisedReadlink = Q.denodeify(fs.readlink);
var promisedUnlink = Q.denodeify(fs.unlink);
var promisedMkdirp = Q.denodeify(mkdirp);

var copyAsync = function (inspectData, to) {
  var mod = mode.normalizeFileMode(inspectData.mode);
  if (inspectData.type === 'dir') {
    return promisedMkdirp(to, { mode: mod });
  } else if (inspectData.type === 'file') {
    return promisedReadFile(inspectData.absolutePath)
    .then(function (data) {
      return fileOps.writeAsync(to, data, { mode: mod });
    });
  } else if (inspectData.type === 'symlink') {
    return promisedReadlink(inspectData.absolutePath)
    .then(function (symlinkPointsAt) {
      var deferred = Q.defer();

      promisedSymlink(symlinkPointsAt, to)
      .then(deferred.resolve)
      .catch(function (err) {
        if (err.code === 'EEXIST') {
          // There is already file/symlink with this name on destination location.
          // Must erase it manually, otherwise system won't allow us to place symlink there.
          promisedUnlink(to)
          .then(function () {
            // Retry...
            return promisedSymlink(symlinkPointsAt, to);
          })
          .then(deferred.resolve, deferred.reject);
        } else {
          deferred.reject(err);
        }
      });

      return deferred.promise;
    });
  }
};

var async = function (from, to, options) {
  var deferred = Q.defer();

  var startCopying = function () {
    var copyNext = function (walker) {
      if (walker.hasNext()) {
        var inspectData = walker.getNext();
        var destPath = pathUtil.join(to, inspectData.relativePath);
        if (options.allowedToCopy(inspectData.absolutePath)) {
          copyAsync(inspectData, destPath)
          .then(function () {
            copyNext(walker);
          })
          .catch(deferred.reject);
        } else {
          copyNext(walker);
        }
      } else {
        deferred.resolve();
      }
    };

    inspector.createTreeWalkerAsync(from).then(copyNext);
  };

  options = parseOptions(options, from);

  // Checks before copying
  exists.async(from)
  .then(function (srcPathExists) {
    if (!srcPathExists) {
      throw generateNoSourceError(from);
    } else {
      return exists.async(to);
    }
  })
  .then(function (destPathExists) {
    if (destPathExists && !options.overwrite) {
      throw generateDestinationExistsError(to);
    } else {
      startCopying();
    }
  })
  .catch(deferred.reject);

  return deferred.promise;
};

// ---------------------------------------------------------
// API
// ---------------------------------------------------------

module.exports.sync = sync;
module.exports.async = async;<commit_msg>More clear code for copy feature<commit_after>'use strict';

var pathUtil = require('path');
var fs = require('fs');
var Q = require('q');
var mkdirp = require('mkdirp');

var exists = require('./exists');
var matcher = require('./utils/matcher');
var fileMode = require('./utils/mode');
var inspector = require('./inspector');
var fileOps = require('./file_ops');

var parseOptions = function (options, from) {
  var opts = options || {};
  var parsedOptions = {};

  parsedOptions.overwrite = opts.overwrite;

  if (opts.matching) {
    parsedOptions.allowedToCopy = matcher.create(opts.matching, from);
  } else {
    parsedOptions.allowedToCopy = function () {
      // Default behaviour - copy everything.
      return true;
    };
  }

  return parsedOptions;
};

var generateNoSourceError = function (path) {
  var err = new Error("Path to copy doesn't exist " + path);
  err.code = 'ENOENT';
  return err;
};

var generateDestinationExistsError = function (path) {
  var err = new Error('Destination path already exists ' + path);
  err.code = 'EEXIST';
  return err;
};

// ---------------------------------------------------------
// Sync
// ---------------------------------------------------------

var copyFileSync = function (from, to, mode) {
  var data = fs.readFileSync(from);
  fileOps.write(to, data, { mode: mode });
};

var copySymlinkSync = function (from, to) {
  var symlinkPointsAt = fs.readlinkSync(from);
  try {
    fs.symlinkSync(symlinkPointsAt, to);
  } catch (err) {
    // There is already file/symlink with this name on destination location.
    // Must erase it manually, otherwise system won't allow us to place symlink there.
    if (err.code === 'EEXIST') {
      fs.unlinkSync(to);
      // Retry...
      fs.symlinkSync(symlinkPointsAt, to);
    } else {
      throw err;
    }
  }
};

var copyItemSync = function (inspectData, to) {
  var mode = fileMode.normalizeFileMode(inspectData.mode);
  if (inspectData.type === 'dir') {
    mkdirp.sync(to, { mode: mode });
  } else if (inspectData.type === 'file') {
    copyFileSync(inspectData.absolutePath, to, mode);
  } else if (inspectData.type === 'symlink') {
    copySymlinkSync(inspectData.absolutePath, to);
  }
};

var sync = function (from, to, options) {
  var opts = parseOptions(options, from);
  var walker;
  var inspectData;
  var destPath;

  if (!exists.sync(from)) {
    throw generateNoSourceError(from);
  }

  if (exists.sync(to) && !opts.overwrite) {
    throw generateDestinationExistsError(to);
  }

  walker = inspector.createTreeWalkerSync(from);
  while (walker.hasNext()) {
    inspectData = walker.getNext();
    destPath = pathUtil.join(to, inspectData.relativePath);
    if (opts.allowedToCopy(inspectData.absolutePath)) {
      copyItemSync(inspectData, destPath);
    }
  }
};

// ---------------------------------------------------------
// Async
// ---------------------------------------------------------

var promisedReadFile = Q.denodeify(fs.readFile);
var promisedSymlink = Q.denodeify(fs.symlink);
var promisedReadlink = Q.denodeify(fs.readlink);
var promisedUnlink = Q.denodeify(fs.unlink);
var promisedMkdirp = Q.denodeify(mkdirp);

var copyFileAsync = function (from, to, mode) {
  return promisedReadFile(from)
  .then(function (data) {
    return fileOps.writeAsync(to, data, { mode: mode });
  });
};

var copySymlinkAsync = function (from, to) {
  return promisedReadlink(from)
  .then(function (symlinkPointsAt) {
    var deferred = Q.defer();

    promisedSymlink(symlinkPointsAt, to)
    .then(deferred.resolve)
    .catch(function (err) {
      if (err.code === 'EEXIST') {
        // There is already file/symlink with this name on destination location.
        // Must erase it manually, otherwise system won't allow us to place symlink there.
        promisedUnlink(to)
        .then(function () {
          // Retry...
          return promisedSymlink(symlinkPointsAt, to);
        })
        .then(deferred.resolve, deferred.reject);
      } else {
        deferred.reject(err);
      }
    });

    return deferred.promise;
  });
};

var copyItemAsync = function (inspectData, to) {
  var mode = fileMode.normalizeFileMode(inspectData.mode);
  if (inspectData.type === 'dir') {
    return promisedMkdirp(to, { mode: mode });
  } else if (inspectData.type === 'file') {
    return copyFileAsync(inspectData.absolutePath, to, mode);
  } else if (inspectData.type === 'symlink') {
    return copySymlinkAsync(inspectData.absolutePath, to);
  }
  // Ha! This is none of supported file system entities. What now?
  // Just continuing without actually copying sounds sane.
  return new Q();
};

var checksBeforeCopying = function (from, to, opts) {
  return exists.async(from)
  .then(function (srcPathExists) {
    if (!srcPathExists) {
      throw generateNoSourceError(from);
    } else {
      return exists.async(to);
    }
  })
  .then(function (destPathExists) {
    if (destPathExists && !opts.overwrite) {
      throw generateDestinationExistsError(to);
    }
  });
};

var runCopyLoop = function (from, to, opts) {
  var deferred = Q.defer();
  var inspectData;
  var destPath;

  var copyNext = function (walker) {
    if (walker.hasNext()) {
      inspectData = walker.getNext();
      destPath = pathUtil.join(to, inspectData.relativePath);
      if (opts.allowedToCopy(inspectData.absolutePath)) {
        copyItemAsync(inspectData, destPath)
        .then(function () {
          copyNext(walker);
        })
        .catch(deferred.reject);
      } else {
        copyNext(walker);
      }
    } else {
      deferred.resolve();
    }
  };

  inspector.createTreeWalkerAsync(from).then(copyNext);

  return deferred.promise;
};

var async = function (from, to, options) {
  var deferred = Q.defer();
  var opts = parseOptions(options, from);

  checksBeforeCopying(from, to, opts)
  .then(function () {
    return runCopyLoop(from, to, opts);
  })
  .then(deferred.resolve)
  .catch(deferred.reject);

  return deferred.promise;
};

// ---------------------------------------------------------
// API
// ---------------------------------------------------------

module.exports.sync = sync;
module.exports.async = async;
{'fraction_non_alphanumeric': 0.18828666837914204, 'fraction_numerical': 0.0, 'mean_word_length': 2.447461629279811, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '444753', 'n_tokens_mistral': 3533, 'n_tokens_neox': 3475, 'n_words': 810}