<commit_before>var child          = require('child_process');
var path           = require('path');
var through        = require('through2');
var throughPipes   = require('through-pipes');
var gulp           = require('gulp');
var gutil          = require('gulp-util');
var inject         = require('gulp-inject');
var slash          = require('gulp-slash');
var semiflat       = require('gulp-semiflat');
var trackFilenames = require('gulp-track-filenames');

/**
 * Create an instance.
 * @param {string} outputPath A directory in which to assemble library and perform compilation, usually temporary
 * @returns {{libraries: function, sources: function, transpile: function, jsHintReporter: function,
 *  traceurReporter: function, adjustSourceMaps: function, injectAppJS: function}}
 */
module.exports = function (outputPath) {
  'use strict';
  if (typeof outputPath !== 'string') {
    throw new Error('outputPath is required but was not specified');
  }
  var sourceTracking = new trackFilenames();
  return {

    /**
     * Copy library files from in the input stream to the temporary directory in preparation for transpile.
     * Outputs a stream of the same files, now found in the temp directory.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    libraries: function () {
      return throughPipes(function (readable) {
        var tracking = sourceTracking.create();
        return readable
          .pipe(tracking.before())
          .pipe(gulp.dest(outputPath))
          .pipe(tracking.after());
      });
    },

    /**
     * Define source files from the input stream but do not copy to the temporary directory.
     * Useful for enforcing correct path format in error messages.
     * Outputs a stream of the same files.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    sources: function () {
      return throughPipes(function (readable) {
	    var tracking = sourceTracking.create();
        return readable
          .pipe(tracking.before())
          .pipe(tracking.after());
      });
    },

    /**
     * Call traceur from the system shell to compile the source files int the stream.
     * Uses libraries that were copied to the temp directory by the sources operation.
     * Outputs a stream of compiled files and their source-maps, alternately.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    transpile: function () {
      return through.obj(function (file, encoding, done) {
        var stream = this;

        // get parameters platform non-specific
        var cwd      = file.cwd;
        var relative = file.relative;
        var filename = path.basename(file.path);
        var outBase  = path.resolve(outputPath);
        var outPath  = path.resolve(outputPath + '/' + relative.replace(filename, ''));
        var outTemp  = path.resolve(outputPath + '/' + filename);

        // call traceur from the shell
        //  at the time of writing there is no stable API for single file output
        var command  = [ 'traceur', '--source-maps', '--out', outTemp, file.path ].join(' ');
        child.exec(command, { cwd: cwd }, function (error) {

          // traceur error implies empty file with error property
          if (error) {
            var pending = new gutil.File();
            pending.cwd           = cwd;
            pending.base          = outBase;
            pending.path          = outPath;
            pending.traceurSource = file;
            pending.traceurError  = error.toString();
            stream.push(pending);
            done();

          // output JS and MAP files to the stream
          //  ensure that their paths are platform non-specific and relative to the outputPath
          //  also adjust .map to .js.map to avoid conflict with similarly named css files and their maps
          } else {
            gulp.src(outTemp.replace(/\.js$/, '.*'))
              .pipe(through.obj(function (file, encoding, done) {
                switch (path.extname(file.path)) {

                  // update the //#sourceMappingURL tag
                  case '.js':
                    var prefix   = path.basename(filename).replace(path.extname(filename), '');
                    var source   = '^\\s*(//#\\s*sourceMappingURL\\s*=\\s*' + prefix + ').map\\s*$';
                    var contents = file.contents.toString().replace(new RegExp(source, 'im'), '$1.js.map');
                    file.contents = new Buffer(contents);
                    break;

                  // change the filename
                  case '.map':
                    file.path = file.path.replace(/\.map$/, '.js.map');
                    break;
                }
                this.push(file);
                done();
              }))
              .pipe(gulp.dest(outPath))
              .pipe(semiflat(outBase))
              .on('data', function (file) {
                stream.push(file);
              }).on('end', function () {
                done();
              });
          }
        });
      });
    },

    /**
     * A terse reporter for JSHint that uses the format as traceurReporter.
     * Outputs elements from the input stream without transformation.
     * @param {number?} bannerWidth The width of banner comment, zero or omitted for none
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    jsHintReporter: function (bannerWidth) {
      var output = [ ];
      var item   = '';
      var prevfile;

      // push each item to an output buffer
      return through.obj(function (file, encoding, done) {
        if (file.jshint && !file.jshint.success && !file.jshint.ignored) {
          (function reporter(results) {
            results.forEach(function (result) {
              var filename = sourceTracking.replace(result.file);
              var error    = result.error;
              if ((prevfile) && (prevfile !== filename) && (item) && (output.indexOf(item) < 0)) {
                output.push(item);
                item = '';
              }
              item    += filename + ':' + error.line + ':' +  error.character + ': ' + error.reason + '\n';
              prevfile = filename;
            });
          })(file.jshint.results, file.jshint.data);
        }

        // all elements to the output
        this.push(file);
        done();

      // display the output buffer with padding before and after and between each item
      }, function (done) {
        if ((item) && (output.indexOf(item) < 0)) {
          output.push(item);
        }
        if (output.length) {
          var width = Number(bannerWidth) || 0;
          var hr    = new Array(width + 1);   // this is a good trick to repeat a character N times
          var start = (width > 0) ? (hr.join('\u25BC') + '\n') : '';
          var stop  = (width > 0) ? (hr.join('\u25B2') + '\n') : '';
          process.stdout.write(start + '\n' + output.join('\n') + '\n' + stop);
        }
        done();
      });
    },

    /**
     * A reporter for the transpile step.
     * Strips from the stream files that failed compilation and displays their error message.
     * @param {number?} bannerWidth The width of banner comment, zero or omitted for none
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    traceurReporter: function (bannerWidth) {
      var output = [ ];

      // push each item to an output buffer
      return through.obj(function (file, encoding, done) {

        // unsuccessful element have a the correct properties
        var isError = (file.isNull) && (file.traceurError) && (file.traceurSource);
        if (isError) {

          // bad import statement
          var analysis = (/[^].*Specified as (.*)\.\nImported by \.{0,2}(.*)\.\n/m).exec(file.traceurError);
          var message;
          if (analysis) {
            var specified = analysis[1];
            var filename  = analysis[2] + '.js';
            var source    = file.traceurSource;
            var isSource  = (path.resolve(source.cwd + filename) === path.resolve(source.path));
            var absolute  = (isSource) ? source.path : path.resolve(file.base + '/' + filename);
            message = absolute + ':0:0: Import not found: ' + specified + '\n';

          // all other errors
          } else {
            message = file.traceurError
              .replace(/^Error\:\s*Command failed\:\s*(.*)$/gm, '$1')
              .replace(/^\[Error\:\s*([^]*)\s*\]$/gm, '$1');   // for windows (n.b. [^]* is .* multiline)
          }

          // report unique errors in original sources
          var original = sourceTracking.replace(message);
          if (output.indexOf(original) < 0) {
            output.push(original);
          }

        // only successful elements to the output
        } else {
          this.push(file);
        }
        done();

      // display the output buffer with padding before and after and between each item
      }, function (done) {
        if (output.length) {
          var width = Number(bannerWidth) || 0;
          var hr    = new Array(width + 1);   // this is a good trick to repeat a character N times
          var start = (width > 0) ? (hr.join('\u25BC') + '\n') : '';
          var stop  = (width > 0) ? (hr.join('\u25B2') + '\n') : '';
          process.stdout.write(start + '\n' + output.join('\n') + '\n' + stop);
        }
        done();
      });
    },

    /**
     * Correct the sources in .map source map files to point to their original sources (rather than
     * those in the temp directory that was used during compilation).
     * Outputs a stream of input files with possibly amended contents.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    adjustSourceMaps: function () {
      return through.obj(function (file, encoding, done) {

        // adjust a single value
        function adjust(candidate) {
          var original     = sourceTracking.replace(candidate);
          var rootRelative = '/' + slash(path.relative(file.cwd, original));
          return rootRelative;
        }

        // adjust map for arrays
        function mapAdjust(value, i, array) {
          array[i] = adjust(value);
        }

        // where the file is a MAP file
        if (path.extname(file.path) === '.map') {
          var sourceMap = JSON.parse(file.contents.toString());
          delete sourceMap.file;
          delete sourceMap.sourcesContent;
          for (var key in sourceMap) {
            if (typeof sourceMap[key] === typeof '') {
              sourceMap[key] = adjust(sourceMap[key]);
            } else if (sourceMap[key] instanceof Array) {
              sourceMap[key].forEach(mapAdjust);
            }
          }
          var text = JSON.stringify(sourceMap, null, '  ');
          file.contents = new Buffer(text);
        }

        // all elements to the output
        this.push(file);
        done();
      });
    },

    /**
     * Inject all JS files found in the same relative directory as the HTML file in the stream.
     * Where a jsBasePath is not given JS is presumed to be adjacent to HTML.
     * Outputs a stream of HTML files with amended content.
     * @param {string} jsBasePath An absolute or root relative base path for javascript files
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    injectAppJS: function (jsBasePath) {
      return through.obj(function (file, encoding, done) {
        var stream = this;

        // infer the html base path from the file.base and use this as a base to locate
        //  the corresponding javascript file
        var htmlName  = path.basename(file.path);
        var htmlPath  = path.resolve(file.path.replace(htmlName, ''));
        var htmlBase  = path.resolve(file.base);
        var jsBase    = (jsBasePath ) ? path.resolve(jsBasePath)  : htmlBase;
        var glob      = htmlPath.replace(htmlBase, jsBase)  + '/*.js';
        var sources = gulp.src(glob, { read: false })
          .pipe(semiflat(jsBase))
          .pipe(slash());

        // pass the html file into a stream that injects the given sources
        //  then add the resulting file to the output stream
        throughPipes(function (readable) {
          return readable
            .pipe(inject(sources));
        })
          .output(function (file) {
            stream.push(file);
            done();
          })
          .input(file)
          .end();
      });
    }
  };
};
<commit_msg>changed vinyl.File initialisation to options-style where possible<commit_after>var child          = require('child_process');
var path           = require('path');
var through        = require('through2');
var throughPipes   = require('through-pipes');
var gulp           = require('gulp');
var gutil          = require('gulp-util');
var inject         = require('gulp-inject');
var slash          = require('gulp-slash');
var semiflat       = require('gulp-semiflat');
var trackFilenames = require('gulp-track-filenames');

/**
 * Create an instance.
 * @param {string} outputPath A directory in which to assemble library and perform compilation, usually temporary
 * @returns {{libraries: function, sources: function, transpile: function, jsHintReporter: function,
 *  traceurReporter: function, adjustSourceMaps: function, injectAppJS: function}}
 */
module.exports = function (outputPath) {
  'use strict';
  if (typeof outputPath !== 'string') {
    throw new Error('outputPath is required but was not specified');
  }
  var sourceTracking = new trackFilenames();
  return {

    /**
     * Copy library files from in the input stream to the temporary directory in preparation for transpile.
     * Outputs a stream of the same files, now found in the temp directory.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    libraries: function () {
      return throughPipes(function (readable) {
        var tracking = sourceTracking.create();
        return readable
          .pipe(tracking.before())
          .pipe(gulp.dest(outputPath))
          .pipe(tracking.after());
      });
    },

    /**
     * Define source files from the input stream but do not copy to the temporary directory.
     * Useful for enforcing correct path format in error messages.
     * Outputs a stream of the same files.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    sources: function () {
      return throughPipes(function (readable) {
	    var tracking = sourceTracking.create();
        return readable
          .pipe(tracking.before())
          .pipe(tracking.after());
      });
    },

    /**
     * Call traceur from the system shell to compile the source files int the stream.
     * Uses libraries that were copied to the temp directory by the sources operation.
     * Outputs a stream of compiled files and their source-maps, alternately.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    transpile: function () {
      return through.obj(function (file, encoding, done) {
        var stream = this;

        // get parameters platform non-specific
        var cwd      = file.cwd;
        var relative = file.relative;
        var filename = path.basename(file.path);
        var outBase  = path.resolve(outputPath);
        var outPath  = path.resolve(outputPath + '/' + relative.replace(filename, ''));
        var outTemp  = path.resolve(outputPath + '/' + filename);

        // call traceur from the shell
        //  at the time of writing there is no stable API for single file output
        var command  = [ 'traceur', '--source-maps', '--out', outTemp, file.path ].join(' ');
        child.exec(command, { cwd: cwd }, function (error) {

          // traceur error implies empty file with error property
          if (error) {
            var pending = new gutil.File({
              cwd:  cwd,
              base: outBase,
              path: outPath
            });
            pending.traceurSource = file;
            pending.traceurError  = error.toString();
            stream.push(pending);
            done();

          // output JS and MAP files to the stream
          //  ensure that their paths are platform non-specific and relative to the outputPath
          //  also adjust .map to .js.map to avoid conflict with similarly named css files and their maps
          } else {
            gulp.src(outTemp.replace(/\.js$/, '.*'))
              .pipe(through.obj(function (file, encoding, done) {
                switch (path.extname(file.path)) {

                  // update the //#sourceMappingURL tag
                  case '.js':
                    var prefix   = path.basename(filename).replace(path.extname(filename), '');
                    var source   = '^\\s*(//#\\s*sourceMappingURL\\s*=\\s*' + prefix + ').map\\s*$';
                    var contents = file.contents.toString().replace(new RegExp(source, 'im'), '$1.js.map');
                    file.contents = new Buffer(contents);
                    break;

                  // change the filename
                  case '.map':
                    file.path = file.path.replace(/\.map$/, '.js.map');
                    break;
                }
                this.push(file);
                done();
              }))
              .pipe(gulp.dest(outPath))
              .pipe(semiflat(outBase))
              .on('data', function (file) {
                stream.push(file);
              }).on('end', function () {
                done();
              });
          }
        });
      });
    },

    /**
     * A terse reporter for JSHint that uses the format as traceurReporter.
     * Outputs elements from the input stream without transformation.
     * @param {number?} bannerWidth The width of banner comment, zero or omitted for none
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    jsHintReporter: function (bannerWidth) {
      var output = [ ];
      var item   = '';
      var prevfile;

      // push each item to an output buffer
      return through.obj(function (file, encoding, done) {
        if (file.jshint && !file.jshint.success && !file.jshint.ignored) {
          (function reporter(results) {
            results.forEach(function (result) {
              var filename = sourceTracking.replace(result.file);
              var error    = result.error;
              if ((prevfile) && (prevfile !== filename) && (item) && (output.indexOf(item) < 0)) {
                output.push(item);
                item = '';
              }
              item    += filename + ':' + error.line + ':' +  error.character + ': ' + error.reason + '\n';
              prevfile = filename;
            });
          })(file.jshint.results, file.jshint.data);
        }

        // all elements to the output
        this.push(file);
        done();

      // display the output buffer with padding before and after and between each item
      }, function (done) {
        if ((item) && (output.indexOf(item) < 0)) {
          output.push(item);
        }
        if (output.length) {
          var width = Number(bannerWidth) || 0;
          var hr    = new Array(width + 1);   // this is a good trick to repeat a character N times
          var start = (width > 0) ? (hr.join('\u25BC') + '\n') : '';
          var stop  = (width > 0) ? (hr.join('\u25B2') + '\n') : '';
          process.stdout.write(start + '\n' + output.join('\n') + '\n' + stop);
        }
        done();
      });
    },

    /**
     * A reporter for the transpile step.
     * Strips from the stream files that failed compilation and displays their error message.
     * @param {number?} bannerWidth The width of banner comment, zero or omitted for none
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    traceurReporter: function (bannerWidth) {
      var output = [ ];

      // push each item to an output buffer
      return through.obj(function (file, encoding, done) {

        // unsuccessful element have a the correct properties
        var isError = (file.isNull) && (file.traceurError) && (file.traceurSource);
        if (isError) {

          // bad import statement
          var analysis = (/[^].*Specified as (.*)\.\nImported by \.{0,2}(.*)\.\n/m).exec(file.traceurError);
          var message;
          if (analysis) {
            var specified = analysis[1];
            var filename  = analysis[2] + '.js';
            var source    = file.traceurSource;
            var isSource  = (path.resolve(source.cwd + filename) === path.resolve(source.path));
            var absolute  = (isSource) ? source.path : path.resolve(file.base + '/' + filename);
            message = absolute + ':0:0: Import not found: ' + specified + '\n';

          // all other errors
          } else {
            message = file.traceurError
              .replace(/^Error\:\s*Command failed\:\s*(.*)$/gm, '$1')
              .replace(/^\[Error\:\s*([^]*)\s*\]$/gm, '$1');   // for windows (n.b. [^]* is .* multiline)
          }

          // report unique errors in original sources
          var original = sourceTracking.replace(message);
          if (output.indexOf(original) < 0) {
            output.push(original);
          }

        // only successful elements to the output
        } else {
          this.push(file);
        }
        done();

      // display the output buffer with padding before and after and between each item
      }, function (done) {
        if (output.length) {
          var width = Number(bannerWidth) || 0;
          var hr    = new Array(width + 1);   // this is a good trick to repeat a character N times
          var start = (width > 0) ? (hr.join('\u25BC') + '\n') : '';
          var stop  = (width > 0) ? (hr.join('\u25B2') + '\n') : '';
          process.stdout.write(start + '\n' + output.join('\n') + '\n' + stop);
        }
        done();
      });
    },

    /**
     * Correct the sources in .map source map files to point to their original sources (rather than
     * those in the temp directory that was used during compilation).
     * Outputs a stream of input files with possibly amended contents.
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    adjustSourceMaps: function () {
      return through.obj(function (file, encoding, done) {

        // adjust a single value
        function adjust(candidate) {
          var original     = sourceTracking.replace(candidate);
          var rootRelative = '/' + slash(path.relative(file.cwd, original));
          return rootRelative;
        }

        // adjust map for arrays
        function mapAdjust(value, i, array) {
          array[i] = adjust(value);
        }

        // where the file is a MAP file
        if (path.extname(file.path) === '.map') {
          var sourceMap = JSON.parse(file.contents.toString());
          delete sourceMap.file;
          delete sourceMap.sourcesContent;
          for (var key in sourceMap) {
            if (typeof sourceMap[key] === typeof '') {
              sourceMap[key] = adjust(sourceMap[key]);
            } else if (sourceMap[key] instanceof Array) {
              sourceMap[key].forEach(mapAdjust);
            }
          }
          var text = JSON.stringify(sourceMap, null, '  ');
          file.contents = new Buffer(text);
        }

        // all elements to the output
        this.push(file);
        done();
      });
    },

    /**
     * Inject all JS files found in the same relative directory as the HTML file in the stream.
     * Where a jsBasePath is not given JS is presumed to be adjacent to HTML.
     * Outputs a stream of HTML files with amended content.
     * @param {string} jsBasePath An absolute or root relative base path for javascript files
     * @returns {stream.Through} A through stream that performs the operation of a gulp stream
     */
    injectAppJS: function (jsBasePath) {
      return through.obj(function (file, encoding, done) {
        var stream = this;

        // infer the html base path from the file.base and use this as a base to locate
        //  the corresponding javascript file
        var htmlName  = path.basename(file.path);
        var htmlPath  = path.resolve(file.path.replace(htmlName, ''));
        var htmlBase  = path.resolve(file.base);
        var jsBase    = (jsBasePath ) ? path.resolve(jsBasePath)  : htmlBase;
        var glob      = htmlPath.replace(htmlBase, jsBase)  + '/*.js';
        var sources = gulp.src(glob, { read: false })
          .pipe(semiflat(jsBase))
          .pipe(slash());

        // pass the html file into a stream that injects the given sources
        //  then add the resulting file to the output stream
        throughPipes(function (readable) {
          return readable
            .pipe(inject(sources));
        })
          .output(function (file) {
            stream.push(file);
            done();
          })
          .input(file)
          .end();
      });
    }
  };
};
<|endoftext|>


<commit_before>'use strict';

const constants = {
  'type': 'type',
  'direction': 'direction',
  'trigger': 'Trigger',
  'inDirection': 'in',
  'outDirection': 'out',
  'settings': 'settings',
  'name': 'name',
  'value': 'value',
  'resource': 'resource',
  'required': 'required',
  'storage': 'storage',
  'connection': 'connection',
  'enum': 'enum',
  'defaultValue': 'defaultValue',
  'webHookType': 'webHookType',
  'httpTrigger': 'httpTrigger',
  'queue': 'queue',
  'queueName': 'queueName',
  'displayName': 'displayName',
  'xAzureSettings': 'x-azure-settings',
  'entryPoint': 'entryPoint'
};

module.exports = {
  'getFunctionMetaData': function (functionName, parsedBindings, serverless) {
    const bindings = [];
    let bindingSettingsNames = [];
    let bindingSettings = [];
    let bindingUserSettings = {};
    let bindingType;
    const functionsJson = {'disabled': false, 'bindings': []};
    const functionObject = serverless.service.getFunction(functionName);
    const handler = functionObject.handler;
    const events = functionObject.events;
    const params = {};

    const bindingTypes = parsedBindings.bindingTypes;
    const bindingDisplayNames = parsedBindings.bindingDisplayNames;

    for (let eventsIndex = 0; eventsIndex < events.length; eventsIndex++) {
      bindingType = Object.keys(functionObject.events[eventsIndex])[0];

      if (eventsIndex === 0) {
        bindingType += constants.trigger;
      }

      const index = bindingTypes.indexOf(bindingType);

      if (index < 0) {
        throw new Error(`Binding  ${bindingType} not supported`);
      }

      serverless.cli.log(`Building binding for function: ${functionName} event: ${bindingType}`);

      bindingUserSettings = {};
      const azureSettings = events[eventsIndex][constants.xAzureSettings];
      let bindingTypeIndex = bindingTypes.indexOf(bindingType);
      const bindingUserSettingsMetaData = this.getBindingUserSettingsMetaData(azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames);

      bindingTypeIndex = bindingUserSettingsMetaData.index;
      bindingUserSettings = bindingUserSettingsMetaData.userSettings;

      if (bindingType.includes(constants.queue) && functionObject.events[eventsIndex].queue) {
        bindingUserSettings[constants.queueName] = functionObject.events[eventsIndex].queue;
      }

      if (bindingTypeIndex < 0) {
        throw new Error('Binding not supported');
      }

      bindingSettings = parsedBindings.bindingSettings[bindingTypeIndex];
      bindingSettingsNames = parsedBindings.bindingSettingsNames[bindingTypeIndex];

      if (azureSettings) {
        for (let azureSettingKeyIndex = 0; azureSettingKeyIndex < Object.keys(azureSettings).length; azureSettingKeyIndex++) {
          const key = Object.keys(azureSettings)[azureSettingKeyIndex];

          if (bindingSettingsNames.indexOf(key) >= 0) {
            bindingUserSettings[key] = azureSettings[key];
          }
        }
      }

      bindings.push(this.getBinding(bindingType, bindingSettings, bindingUserSettings, serverless));
    }

    if (bindingType === constants.httpTrigger && bindings.length === 1) {
      bindings.push(this.getHttpOutBinding(bindingUserSettings));
    }

    functionsJson.bindings = bindings;
    params.functionsJson = functionsJson;

    const entryPointAndHandlerPath = this.getEntryPointAndHandlerPath(handler);
    const metaData = {
      'entryPoint': entryPointAndHandlerPath[constants.entryPoint],
      'handlerPath': entryPointAndHandlerPath.handlerPath,
      'params': params
    };

return metaData;
  },

  'getBindingUserSettingsMetaData': function (azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames) {
    let bindingDisplayNamesIndex = bindingTypeIndex;
    const bindingUserSettings = {};

    if (azureSettings) {
      const directionIndex = Object.keys(azureSettings).indexOf(constants.direction);

      if (directionIndex >= 0) {
        const key = Object.keys(azureSettings)[directionIndex];
        const displayName = `$${bindingType}${azureSettings[key]}_displayName`;

        bindingDisplayNamesIndex = bindingDisplayNames.indexOf(displayName.toLowerCase());
        bindingUserSettings[constants.direction] = azureSettings[key];
      }
    }
    const bindingUserSettingsMetaData = {
      'index': bindingDisplayNamesIndex,
      'userSettings': bindingUserSettings
    };

return bindingUserSettingsMetaData;
  },

  'getEntryPointAndHandlerPath': function (handler) {
    let handlerPath = 'handler.js';
    let entryPoint = handler;
    const handlerSplit = handler.split('.');

    if (handlerSplit.length > 1) {
      entryPoint = handlerSplit[handlerSplit.length - 1];
      handlerPath = `${handler.substring(0, handler.lastIndexOf('.'))}.js`;
    }
    const metaData = {
      'entryPoint': entryPoint,
      'handlerPath': handlerPath
    };

return metaData;
  },

  'getHttpOutBinding': function (bindingUserSettings) {
    const binding = {};

    binding[constants.type] = 'http';
    binding[constants.direction] = constants.outDirection;
    binding[constants.name] = '$return';
    if (bindingUserSettings[constants.webHookType]) {
      binding[constants.name] = 'res';
    }

return binding;
  },

  'getBinding': function (bindingType, bindingSettings, bindingUserSettings, serverless) {
    const binding = {};

    binding[constants.type] = bindingType;
    if (bindingUserSettings && bindingUserSettings[constants.direction]) {
      binding[constants.direction] = bindingUserSettings[constants.direction];
    } else if (bindingType.includes(constants.trigger)) {
      binding[constants.direction] = constants.inDirection;
    } else {
      binding[constants.direction] = constants.outDirection;
    }

    for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings.length; bindingSettingsIndex++) {
      const name = bindingSettings[bindingSettingsIndex][constants.name];

      if (bindingUserSettings && bindingUserSettings[name]) {
        binding[name] = bindingUserSettings[name];
        continue;
      }
      const value = bindingSettings[bindingSettingsIndex][constants.value];
      const required = bindingSettings[bindingSettingsIndex][constants.required];
      const resource = bindingSettings[bindingSettingsIndex][constants.resource];

      if (required) {
        const defaultValue = bindingSettings[bindingSettingsIndex][constants.defaultValue];

        if (defaultValue) {
          binding[name] = defaultValue;
        } else if (name === constants.connection && resource.toLowerCase() === constants.storage) {
          binding[name] = 'AzureWebJobsStorage';
        } else {
          throw new Error(`Required property ${name} is missing for binding:${bindingType}`);
        }
      }

      if (value === constants.enum && name !== constants.webHookType) {
        const enumValues = bindingSettings[bindingSettingsIndex][constants.enum];

        binding[name] = enumValues[0][constants.value];
      }
    }

return binding;
  }
};
<commit_msg>Bug fix (#25)<commit_after>'use strict';

const constants = {
  'type': 'type',
  'direction': 'direction',
  'trigger': 'Trigger',
  'inDirection': 'in',
  'outDirection': 'out',
  'settings': 'settings',
  'name': 'name',
  'value': 'value',
  'resource': 'resource',
  'required': 'required',
  'storage': 'storage',
  'connection': 'connection',
  'enum': 'enum',
  'defaultValue': 'defaultValue',
  'webHookType': 'webHookType',
  'httpTrigger': 'httpTrigger',
  'queue': 'queue',
  'queueName': 'queueName',
  'displayName': 'displayName',
  'xAzureSettings': 'x-azure-settings',
  'entryPoint': 'entryPoint'
};

module.exports = {
  'getFunctionMetaData': function (functionName, parsedBindings, serverless) {
    const bindings = [];
    let bindingSettingsNames = [];
    let bindingSettings = [];
    let bindingUserSettings = {};
    let bindingType;
    const functionsJson = {'disabled': false, 'bindings': []};
    const functionObject = serverless.service.getFunction(functionName);
    const handler = functionObject.handler;
    const events = functionObject.events;
    const params = {};

    const bindingTypes = parsedBindings.bindingTypes;
    const bindingDisplayNames = parsedBindings.bindingDisplayNames;

    for (let eventsIndex = 0; eventsIndex < events.length; eventsIndex++) {
      bindingType = Object.keys(functionObject.events[eventsIndex])[0];

      if (eventsIndex === 0) {
        bindingType += constants.trigger;
      }

      const index = bindingTypes.indexOf(bindingType);

      if (index < 0) {
        throw new Error(`Binding  ${bindingType} not supported`);
      }

      serverless.cli.log(`Building binding for function: ${functionName} event: ${bindingType}`);

      bindingUserSettings = {};
      const azureSettings = events[eventsIndex][constants.xAzureSettings];
      let bindingTypeIndex = bindingTypes.indexOf(bindingType);
      const bindingUserSettingsMetaData = this.getBindingUserSettingsMetaData(azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames);

      bindingTypeIndex = bindingUserSettingsMetaData.index;
      bindingUserSettings = bindingUserSettingsMetaData.userSettings;

      if (bindingType.includes(constants.queue) && functionObject.events[eventsIndex].queue) {
        bindingUserSettings[constants.queueName] = functionObject.events[eventsIndex].queue;
      }

      if (bindingTypeIndex < 0) {
        throw new Error('Binding not supported');
      }

      bindingSettings = parsedBindings.bindingSettings[bindingTypeIndex];
      bindingSettingsNames = parsedBindings.bindingSettingsNames[bindingTypeIndex];

      if (azureSettings) {
        for (let azureSettingKeyIndex = 0; azureSettingKeyIndex < Object.keys(azureSettings).length; azureSettingKeyIndex++) {
          const key = Object.keys(azureSettings)[azureSettingKeyIndex];

          if (bindingSettingsNames.indexOf(key) >= 0) {
            bindingUserSettings[key] = azureSettings[key];
          }
        }
      }

      bindings.push(this.getBinding(bindingType, bindingSettings, bindingUserSettings, serverless));
    }

    if (bindingType === constants.httpTrigger) {
      bindings.push(this.getHttpOutBinding(bindingUserSettings));
    }

    functionsJson.bindings = bindings;
    params.functionsJson = functionsJson;

    const entryPointAndHandlerPath = this.getEntryPointAndHandlerPath(handler);
    const metaData = {
      'entryPoint': entryPointAndHandlerPath[constants.entryPoint],
      'handlerPath': entryPointAndHandlerPath.handlerPath,
      'params': params
    };

return metaData;
  },

  'getBindingUserSettingsMetaData': function (azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames) {
    let bindingDisplayNamesIndex = bindingTypeIndex;
    const bindingUserSettings = {};

    if (azureSettings) {
      const directionIndex = Object.keys(azureSettings).indexOf(constants.direction);

      if (directionIndex >= 0) {
        const key = Object.keys(azureSettings)[directionIndex];
        const displayName = `$${bindingType}${azureSettings[key]}_displayName`;

        bindingDisplayNamesIndex = bindingDisplayNames.indexOf(displayName.toLowerCase());
        bindingUserSettings[constants.direction] = azureSettings[key];
      }
    }
    const bindingUserSettingsMetaData = {
      'index': bindingDisplayNamesIndex,
      'userSettings': bindingUserSettings
    };

return bindingUserSettingsMetaData;
  },

  'getEntryPointAndHandlerPath': function (handler) {
    let handlerPath = 'handler.js';
    let entryPoint = handler;
    const handlerSplit = handler.split('.');

    if (handlerSplit.length > 1) {
      entryPoint = handlerSplit[handlerSplit.length - 1];
      handlerPath = `${handler.substring(0, handler.lastIndexOf('.'))}.js`;
    }
    const metaData = {
      'entryPoint': entryPoint,
      'handlerPath': handlerPath
    };

return metaData;
  },

  'getHttpOutBinding': function (bindingUserSettings) {
    const binding = {};

    binding[constants.type] = 'http';
    binding[constants.direction] = constants.outDirection;
    binding[constants.name] = '$return';
    if (bindingUserSettings[constants.webHookType]) {
      binding[constants.name] = 'res';
    }

return binding;
  },

  'getBinding': function (bindingType, bindingSettings, bindingUserSettings, serverless) {
    const binding = {};

    binding[constants.type] = bindingType;
    if (bindingUserSettings && bindingUserSettings[constants.direction]) {
      binding[constants.direction] = bindingUserSettings[constants.direction];
    } else if (bindingType.includes(constants.trigger)) {
      binding[constants.direction] = constants.inDirection;
    } else {
      binding[constants.direction] = constants.outDirection;
    }

    for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings.length; bindingSettingsIndex++) {
      const name = bindingSettings[bindingSettingsIndex][constants.name];

      if (bindingUserSettings && bindingUserSettings[name]) {
        binding[name] = bindingUserSettings[name];
        continue;
      }
      const value = bindingSettings[bindingSettingsIndex][constants.value];
      const required = bindingSettings[bindingSettingsIndex][constants.required];
      const resource = bindingSettings[bindingSettingsIndex][constants.resource];

      if (required) {
        const defaultValue = bindingSettings[bindingSettingsIndex][constants.defaultValue];

        if (defaultValue) {
          binding[name] = defaultValue;
        } else if (name === constants.connection && resource.toLowerCase() === constants.storage) {
          binding[name] = 'AzureWebJobsStorage';
        } else {
          throw new Error(`Required property ${name} is missing for binding:${bindingType}`);
        }
      }

      if (value === constants.enum && name !== constants.webHookType) {
        const enumValues = bindingSettings[bindingSettingsIndex][constants.enum];

        binding[name] = enumValues[0][constants.value];
      }
    }

return binding;
  }
};
<|endoftext|>


<commit_before>var templater = require('tiny-templater'),
    dotpather = require('dotpather'),
    regex = /{%\s*([\w\d\s\-\.]*)\s*%}/;

function Parser(tags, template) {

  this.tags = tags;
  this.finalParts = [];
  this.position = 0;
  this.template = template;

}

Parser.prototype.parse = function (obj) {

  var position = this.position;

  Object.keys(obj).forEach(function (tag) {
    var tagRegex = new RegExp('{%\\s*' + tag + '\\s*%}'),
        hasTag = tagRegex.exec(this.template.substring(position));
    if (hasTag) {
      var subParser = language(this.tags),
          tpl = subParser(this.template.substring(position, position + hasTag.index));
      if (position + hasTag.index > this.position) { this.position = position + hasTag.index + hasTag[0].length; }
      obj[tag].call(this, tpl);
    }
  }.bind(this));

}

Parser.prototype.lookup = dotpather;

Parser.prototype.render = function (obj) {

  var hasStatement = this.template.substring(this.position).match(regex);

  if (!hasStatement) {
    this.finalParts.push(this.template.substring(this.position));
    console.log(this.finalParts.join(''));
    var compiled = templater(this.finalParts.join(''));
    //return this.finalParts.join('');
    console.log(obj);
    return compiled(obj);
  }

  var nextStatement = hasStatement[1],
      nextTag = nextStatement.split(/\s+/)[0];

  if (this.tags[nextTag]) {
    if (this.position === 0 && this.template.indexOf(hasStatement[0]) > 0) { this.finalParts.push(this.template.substring(0, this.template.indexOf(hasStatement[0]))); }
    this.position += this.template.indexOf(hasStatement[0]) + hasStatement[0].length;
    var piece = this.tags[nextTag](this, nextStatement);
    this.finalParts.push(piece(obj));
  } else {
    this.position = this.template.indexOf(nextStatement[0]) + nextStatement[0].length;
  }

  this.render(obj);

}

function language(tags) {

  return function languageTemplate(template) {

    var parser = new Parser(tags, template);

    return function (obj) {

      return parser.render(obj);

    }

  }

}

module.exports = language;

<commit_msg>on to something possibly<commit_after>var templater = require('tiny-templater'),
    dotpather = require('dotpather'),
    regex = /{%\s*([\w\d\s\-\.]*)\s*%}/;

function Parser(tags, template) {

  this.tags = tags;
  this.finalParts = [];
  this.position = 0;
  this.template = template;

}

Parser.prototype.parse = function (obj) {

  var position = this.position;

  Object.keys(obj).forEach(function (tag) {
    var tagRegex = new RegExp('{%\\s*' + tag + '\\s*%}'),
        hasTag = tagRegex.exec(this.template.substring(position));
    if (hasTag) {
      var subParser = language(this.tags),
          tpl = subParser(this.template.substring(position, position + hasTag.index));
      if (position + hasTag.index > this.position) { this.position = position + hasTag.index + hasTag[0].length; }
      obj[tag].call(this, tpl);
    }
  }.bind(this));

}

Parser.prototype.lookup = dotpather;

Parser.prototype.render = function (obj) {

  var hasStatement = this.template.substring(this.position).match(regex);

  if (!hasStatement) {
    this.finalParts.push(this.template.substring(this.position));
    console.log(this.finalParts.join(''));
    var compiled = templater(this.finalParts.join(''));
    //return this.finalParts.join('');
    console.log(obj);
    return 1;
    //return compiled(obj);
  }

  var nextStatement = hasStatement[1],
      nextTag = nextStatement.split(/\s+/)[0];

  if (this.tags[nextTag]) {
    if (this.position === 0 && this.template.indexOf(hasStatement[0]) > 0) { this.finalParts.push(this.template.substring(0, this.template.indexOf(hasStatement[0]))); }
    this.position += this.template.indexOf(hasStatement[0]) + hasStatement[0].length;
    var piece = this.tags[nextTag](this, nextStatement);
    this.finalParts.push(piece(obj));
  } else {
    this.position = this.template.indexOf(nextStatement[0]) + nextStatement[0].length;
  }

  this.render(obj);

}

function language(tags) {

  return function languageTemplate(template) {

    var parser = new Parser(tags, template);

    return function (obj) {

      return parser.render(obj);

    }

  }

}

module.exports = language;

<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var extend = require('util')._extend;
var Handlebars = require('handlebars');
var CategoryParser = require('./lib/category-parser');

Handlebars.registerHelper('json', function(object) {
    var string = JSON.stringify(object);
    string = string.replace(/(?:\r\n|\r|\n)/g, ''); // Remove white space
    return string;
});

Handlebars.registerHelper('selected-class', function(a, b) {
    return a == b ? 'selected' : '';
});

module.exports = function(options){

    return {

        options: extend(
            {
                src: null,
                dest: null,
                template: null, // Path to handlebars template
                heading: null
            },
            options
        ),

        _categories: {},

        _menuItems: [],

        _template: null,

        _head: '',

        _init: function(){
            var parser = new CategoryParser(this.options.src);
            this._categories = parser.getCategories();
            this._menuItems = this._getMenuItems();
            this._template = Handlebars.compile(fs.readFileSync(this.options.template, 'utf-8'));
            this._createHead();
            return this;
        },

        _createHead: function(){
            this._head += '<style>';
            this._head += fs.readFileSync('./client.min.css', 'utf-8');
            this._head += '</style>';
            this._head += '<script>';
            this._head += fs.readFileSync(require.resolve('jquery'), 'utf-8');
            this._head += fs.readFileSync('./client.min.js', 'utf-8');
            this._head += '</script>';
        },

        save: function(dest, templatePath){
            for(var category in this._categories){
                this._saveHtml(dest, this._categories[category]);
            }
        },

        _getMenuItems: function(){
            var items = [];
            for(var category in this._categories){
                items.push({
                    href: category + '.html',
                    label: category
                });
            }
            return items;
        },

        _saveHtml: function(dest, category){
            var fileName = [dest, category.name + '.html'].join(path.sep);
            var viewData = extend(
                {
                    head: this._head,
                    category: category,
                    menuItems: this._menuItems
                },
                this.options
            );
            var html = this._template(viewData);
            fs.writeFileSync(fileName, html);
        }

    }._init();
};
<commit_msg>Fixed a path problem<commit_after>var fs = require('fs');
var path = require('path');
var extend = require('util')._extend;
var Handlebars = require('handlebars');
var CategoryParser = require('./lib/category-parser');

Handlebars.registerHelper('json', function(object) {
    var string = JSON.stringify(object);
    string = string.replace(/(?:\r\n|\r|\n)/g, ''); // Remove white space
    return string;
});

Handlebars.registerHelper('selected-class', function(a, b) {
    return a == b ? 'selected' : '';
});

module.exports = function(options){

    return {

        options: extend(
            {
                src: null,
                dest: null,
                template: null, // Path to handlebars template
                heading: null
            },
            options
        ),

        _categories: {},

        _menuItems: [],

        _template: null,

        _head: '',

        _init: function(){
            var parser = new CategoryParser(this.options.src);
            this._categories = parser.getCategories();
            this._menuItems = this._getMenuItems();
            this._template = Handlebars.compile(fs.readFileSync(this.options.template, 'utf-8'));
            this._createHead();
            return this;
        },

        _createHead: function(){
            this._head += '<style>';
            this._head += fs.readFileSync(__dirname +  '/client.min.css', 'utf-8');
            this._head += '</style>';
            this._head += '<script>';
            this._head += fs.readFileSync(require.resolve('jquery'), 'utf-8');
            this._head += fs.readFileSync(__dirname + '/client.min.js', 'utf-8');
            this._head += '</script>';
        },

        save: function(dest, templatePath){
            for(var category in this._categories){
                this._saveHtml(dest, this._categories[category]);
            }
        },

        _getMenuItems: function(){
            var items = [];
            for(var category in this._categories){
                items.push({
                    href: category + '.html',
                    label: category
                });
            }
            return items;
        },

        _saveHtml: function(dest, category){
            var fileName = [dest, category.name + '.html'].join(path.sep);
            var viewData = extend(
                {
                    head: this._head,
                    category: category,
                    menuItems: this._menuItems
                },
                this.options
            );
            var html = this._template(viewData);
            fs.writeFileSync(fileName, html);
        }

    }._init();
};
<|endoftext|>


<commit_before>var utils = require("belty");

var defaults = {
  resolve: [],
  fetch: [],
  transform: [],
  dependency: [],
  precompile: [],
  extensions: []
};

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

  this._configuration = merge(utils.merge({}, defaults), options);
}

PluginBuilder.prototype.configure = function(options) {
  merge(this._configuration, options);
  return this;
};

PluginBuilder.prototype.build = function() {
  return utils.merge({}, this._configuration);
};

PluginBuilder.create = function(options) {
  return new PluginBuilder(options);
};

function merge(pluginConfig, options) {
  options = options || {};

  var updateKeys = Object
    .keys(options)
    .filter(function(option) {
      return defaults.hasOwnProperty(option);
    });

  utils.extend(pluginConfig, mergeState(pluginConfig, utils.pick(options, updateKeys)));

  var matches = options.match || options.matches;
  if (matches) {
    pluginConfig.matches = mergeState(pluginConfig.matches, matches);
  }

  var ignores = options.ignore || options.ignores;
  if (ignores) {
    pluginConfig.ignores = mergeState(pluginConfig.ignores, ignores);
  }

  return pluginConfig;
};

function mergeState(currentState, newState) {
  currentState = currentState || {};

  return Object
    .keys(newState)
    .reduce(function(result, key) {
      result[key] = (currentState[key] || []).concat(utils.toArray(newState[key]));
      return result;
    }, {});
}

module.exports = PluginBuilder;
<commit_msg>Added pretransform default so that plugins can configure it<commit_after>var utils = require("belty");

var defaults = {
  resolve: [],
  fetch: [],
  pretransform: [],
  transform: [],
  dependency: [],
  precompile: [],
  extensions: []
};

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

  this._configuration = merge(utils.merge({}, defaults), options);
}

PluginBuilder.prototype.configure = function(options) {
  merge(this._configuration, options);
  return this;
};

PluginBuilder.prototype.build = function() {
  return utils.merge({}, this._configuration);
};

PluginBuilder.create = function(options) {
  return new PluginBuilder(options);
};

function merge(pluginConfig, options) {
  options = options || {};

  var updateKeys = Object
    .keys(options)
    .filter(function(option) {
      return defaults.hasOwnProperty(option);
    });

  utils.extend(pluginConfig, mergeState(pluginConfig, utils.pick(options, updateKeys)));

  var matches = options.match || options.matches;
  if (matches) {
    pluginConfig.matches = mergeState(pluginConfig.matches, matches);
  }

  var ignores = options.ignore || options.ignores;
  if (ignores) {
    pluginConfig.ignores = mergeState(pluginConfig.ignores, ignores);
  }

  return pluginConfig;
};

function mergeState(currentState, newState) {
  currentState = currentState || {};

  return Object
    .keys(newState)
    .reduce(function(result, key) {
      result[key] = (currentState[key] || []).concat(utils.toArray(newState[key]));
      return result;
    }, {});
}

module.exports = PluginBuilder;
<|endoftext|>


<commit_before>var _ = require('underscore'),
  Db = require('backbone-db'),
  debug = require('debug')('backbone-db-mongodb'),
  ObjectId = require('mongodb').BSONPure.ObjectID;

function MongoDB(client) {
  if (!client) throw new Error('Db.MongoDB requires a connected mongo client');
  this.client = client;
}

function convertSort(sortProp) {
  var sortOrder = 1;
  if (sortProp && sortProp[0] === "-") {
    sortOrder = -1;
    sortProp = sortProp.substr(1);
  }
  var ret = {};
  ret[sortProp] = sortOrder;
  return ret;
}

function toMongoDoc(doc) {
  if (doc._id) {
    if (typeof doc._id === "string" && doc._id.length === "24") {
      debug('converting _id to MongoID');
      doc._id = new ObjectId(doc._id);
    }
  }
  return doc;
}

function fromMongoDoc(doc) {
  if (doc._id) {
    if (typeof doc._id === "object" && doc._id.toString && doc._id.toString().length === "24") {
      debug('converting _id to MongoID');
      doc._id = doc._id.toString();
    }
  }
  return doc;
}

MongoDB.sync = Db.sync;
_.extend(MongoDB.prototype, Db.prototype, {
  _getCollection: function (model, options, callback) {
    if (options && options.mongo_collection) {
      this.client.collection(options.mongo_collection, callback);
    } else if (model && model.mongo_collection) {
      this.client.collection(model.mongo_collection, callback);
    } else if (model && model.model && model.model.mongo_collection) {
      this.client.collection(model.model.mongo_collection, callback);
    } else {
      throw new Error('Cannot get collection for ' + model.type);
    }
  },

  findAll: function (model, options, callback) {
    options = options || {};
    var query = options.where ||  {};
    var offset = options.offset ||  0;
    var limit = options.limit || this.limit || 50;
    var sort = options.sort ? convertSort(options.sort) : {
      $natural: 1
    };
    if (options.after_id) {
      query._id = {
        $gt: options.after_id
      };
    } else if (options.before_id) {
      query._id = {
        $lt: options.before_id
      };
    }
    debug('findAll', query, 'limit:', limit, 'offset:', offset, 'sort:', sort);
    this._getCollection(model, options, function (err, collection) {
      if (err) return callback(err);
      collection
        .find(query)
        .sort(sort)
        .skip(offset)
        .limit(limit)
        .toArray(function (err, res) {
          if (res && res.length > 0) {
            res = _.map(res, fromMongoDoc);
          }
          callback(err, res);
        });
    });
  },

  find: function (model, options, callback) {
    options = options || {};
    var query = options.where ||  {
      _id: model.get(model.idAttribute)
    };
    debug('find', query);
    this._getCollection(model, options, function (err, col) {
      if (err) return callback(err);
      col.findOne(query, function (err, res) {
        return callback(err, fromMongoDoc(res));
      });
    });
  },

  create: function (model, options, callback) {
    var self = this;
    var key = this._getCollection(model, options);

    debug('create: ' + key);
    if (model.isNew()) {
      this.createId(model, options, function (err) {
        if (err) callback(err);
        self.update(model, options, callback);
      });
    } else {
      self.update(model, options, callback);
    }
  },

  createId: function (model, options, callback) {
    var createIdFn = model.createId ? model.createId : this._createDefaultId;
    createIdFn(function (err, id) {
      model.set(model.idAttribute, id.toString());
      model.set('_id', id.toString());
      callback(err);
    });
  },

  _createDefaultId: function (callback) {
    callback(null, new ObjectId());
  },

  update: function (model, options, callback) {
    var self = this;
    debug('update:' + model.get(model.idAttribute));
    if (model.isNew()) {
      return this.create(model, options, callback);
    }
    if (!model.has('_id') && model.has(model.idAttribute)) {
      model.set('_id', model.get(model.idAttribute));
    }
    if (options.inc) {
      return this.inc(model, options, callback);
    }
    this._getCollection(model, options, function (err, collection) {
      if (err) return callback(err);
      collection.save(toMongoDoc(model.toJSON()), callback);
    });
  },

  destroy: function (model, options, callback) {
    var self = this;
    debug("destroy: " + model.get(model.idAttribute));
    if (model.isNew()) {
      return false;
    }

    this._getCollection(model, options, function (err, collection) {
      if (err) return callback(err);
      var data = toMongoDoc(model.toJSON());
      collection.remove({
        _id: data._id
      }, function (err, res) {
        callback(err, res || options.ignoreFailures ? 1 : res);
      });
    });
  },

  inc: function (model, options, callback) {
    if (!options || !options.inc || !options.inc.attribute) {
      throw new Error('inc settings must be defined');
    }
    var attribute = options.inc.attribute;
    var amount = options.inc.amount || 1;
    var inc = {};
    inc[attribute] = amount;
    debug('inc:' + JSON.stringify(inc));
    this._getCollection(model, options, function (err, col) {
      if (err) return callback(err);
      var data = toMongoDoc(model.toJSON());
      col.update({
          _id: data._id
        }, {
          $inc: inc
        }, {
          upsert: options.upsert || false
        },
        callback
      );
    });
  }
});

module.exports = Db.MongoDB = MongoDB;<commit_msg>check for existance of doc when converting _id:s<commit_after>var _ = require('underscore'),
  Db = require('backbone-db'),
  debug = require('debug')('backbone-db-mongodb'),
  ObjectId = require('mongodb').BSONPure.ObjectID;

function MongoDB(client) {
  if (!client) throw new Error('Db.MongoDB requires a connected mongo client');
  this.client = client;
}

function convertSort(sortProp) {
  var sortOrder = 1;
  if (sortProp && sortProp[0] === "-") {
    sortOrder = -1;
    sortProp = sortProp.substr(1);
  }
  var ret = {};
  ret[sortProp] = sortOrder;
  return ret;
}

function toMongoDoc(doc) {
  if (doc && doc._id) {
    if (typeof doc._id === "string" && doc._id.length === "24") {
      debug('converting _id to MongoID');
      doc._id = new ObjectId(doc._id);
    }
  }
  return doc;
}

function fromMongoDoc(doc) {
  if (doc && doc._id) {
    if (typeof doc._id === "object" && doc._id.toString && doc._id.toString().length === "24") {
      debug('converting _id to MongoID');
      doc._id = doc._id.toString();
    }
  }
  return doc;
}

MongoDB.sync = Db.sync;
_.extend(MongoDB.prototype, Db.prototype, {
  _getCollection: function (model, options, callback) {
    if (options && options.mongo_collection) {
      this.client.collection(options.mongo_collection, callback);
    } else if (model && model.mongo_collection) {
      this.client.collection(model.mongo_collection, callback);
    } else if (model && model.model && model.model.mongo_collection) {
      this.client.collection(model.model.mongo_collection, callback);
    } else {
      throw new Error('Cannot get collection for ' + model.type);
    }
  },

  findAll: function (model, options, callback) {
    options = options || {};
    var query = options.where ||  {};
    var offset = options.offset ||  0;
    var limit = options.limit || this.limit || 50;
    var sort = options.sort ? convertSort(options.sort) : {
      $natural: 1
    };
    if (options.after_id) {
      query._id = {
        $gt: options.after_id
      };
    } else if (options.before_id) {
      query._id = {
        $lt: options.before_id
      };
    }
    debug('findAll', query, 'limit:', limit, 'offset:', offset, 'sort:', sort);
    this._getCollection(model, options, function (err, collection) {
      if (err) return callback(err);
      collection
        .find(query)
        .sort(sort)
        .skip(offset)
        .limit(limit)
        .toArray(function (err, res) {
          if (res && res.length > 0) {
            res = _.map(res, fromMongoDoc);
          }
          callback(err, res);
        });
    });
  },

  find: function (model, options, callback) {
    options = options || {};
    var query = options.where ||  {
      _id: model.get(model.idAttribute)
    };
    debug('find', query);
    this._getCollection(model, options, function (err, col) {
      if (err) return callback(err);
      col.findOne(query, function (err, res) {
        return callback(err, fromMongoDoc(res));
      });
    });
  },

  create: function (model, options, callback) {
    var self = this;
    var key = this._getCollection(model, options);

    debug('create: ' + key);
    if (model.isNew()) {
      this.createId(model, options, function (err) {
        if (err) callback(err);
        self.update(model, options, callback);
      });
    } else {
      self.update(model, options, callback);
    }
  },

  createId: function (model, options, callback) {
    var createIdFn = model.createId ? model.createId : this._createDefaultId;
    createIdFn(function (err, id) {
      model.set(model.idAttribute, id.toString());
      model.set('_id', id.toString());
      callback(err);
    });
  },

  _createDefaultId: function (callback) {
    callback(null, new ObjectId());
  },

  update: function (model, options, callback) {
    var self = this;
    debug('update:' + model.get(model.idAttribute));
    if (model.isNew()) {
      return this.create(model, options, callback);
    }
    if (!model.has('_id') && model.has(model.idAttribute)) {
      model.set('_id', model.get(model.idAttribute));
    }
    if (options.inc) {
      return this.inc(model, options, callback);
    }
    this._getCollection(model, options, function (err, collection) {
      if (err) return callback(err);
      collection.save(toMongoDoc(model.toJSON()), callback);
    });
  },

  destroy: function (model, options, callback) {
    var self = this;
    debug("destroy: " + model.get(model.idAttribute));
    if (model.isNew()) {
      return false;
    }

    this._getCollection(model, options, function (err, collection) {
      if (err) return callback(err);
      var data = toMongoDoc(model.toJSON());
      collection.remove({
        _id: data._id
      }, function (err, res) {
        callback(err, res || options.ignoreFailures ? 1 : res);
      });
    });
  },

  inc: function (model, options, callback) {
    if (!options || !options.inc || !options.inc.attribute) {
      throw new Error('inc settings must be defined');
    }
    var attribute = options.inc.attribute;
    var amount = options.inc.amount || 1;
    var inc = {};
    inc[attribute] = amount;
    debug('inc:' + JSON.stringify(inc));
    this._getCollection(model, options, function (err, col) {
      if (err) return callback(err);
      var data = toMongoDoc(model.toJSON());
      col.update({
          _id: data._id
        }, {
          $inc: inc
        }, {
          upsert: options.upsert || false
        },
        callback
      );
    });
  }
});

module.exports = Db.MongoDB = MongoDB;<|endoftext|>


<commit_before>// Пустая клетка
var B = false;
// Клетка с фигурой
var Q = true;

// Тип данных: Board
// Пустая доска с размерностью n=4
var n4emptBrd = [B, B, B, B,
                 B, B, B, B,
                 B, B, B, B,
                 B, B, B, B];
// Решение для доски с размерностью 4
var n4solBrd = [B, Q, B, B,
                B, B, B, Q,
                Q, B, B, B,
                B, B, Q, B];

// Конвертирует позицию вида ряд:столбец
// в индекс плоского массива
// Все индексы начинаются с нуля.
// @param  {Integer[0...]} r Ряд
// @param  {Integer[0...]} c Столбец
// @param  {Natural}       n Размерность доски
// @return {Natural} Позиция
function rcToPos(r,c,n){
  return r*n+c;
};

// Тесты для функции rcToPos
(function(){
  console.log("Тест1:", n4solBrd[rcToPos(0,1,4)]==Q);
  console.log("Тест2:", n4solBrd[rcToPos(3,3,4)]==B);
})();

// Создает пустую доску n*n элементов
// @param {Natural} n Размерность доски
// @return {Board}
function createEmptyBoard(n){
  var b = [];
  for(var i=0;i<n*n;i++){ b.push(B); }
  return b;
};

// Тесты для функции createEmptyBoard
(function(){
  console.log("Тест3:", createEmptyBoard(4).length==16);
})();

// Решает проблемы n-ферзей
// @param {Board} b Доска
// @return {Board | false}:
//          Если задача имеет решение тогда доску
//          Если решения нет тогда false

// function solve(b){return false}; // заглушка

function solve(b){
  // Ищет решение для данной доски
  // @param {Board} b
  // @return {Board | false}
  function solveBrd(b){
    if(isSolved(b)){
      return b;
    }else{
      return solveChilds(nextBoards(b));
    }
  };

  // Ищет решения для списка потомков
  // @param {arrayof: Board} lob
  // @return {Board | false}
  function solveChilds(lob){
    if(lob.length==0){
      return false; // базовый случай
    }else{
      // рекурсивный случай
      var first = lob[0];
      var rest  = lob.slice(1,lob.length);
      var tryBrd = solveBrd(first);
      if(tryBrd!=false){
        return tryBrd;
      }else{
        return solveChilds(rest);
      }
    }
  };

  return solveBrd(b);
};

(function(){
  console.log("Тест4:", solve(createEmptyBoard(1))==[Q]);
  console.log("Тест5:", solve(createEmptyBoard(2))==false);
  console.log("Тест6:", solve(createEmptyBoard(4))==n4solBrd);
})();

// Является ли данная доска решением?
// @param  {Board} b
// @return {Boolean}
// !!!
function isSolved(b){ return false; } // Заглушка

// Создает массив потомков данной доски
// Все несостоятельные доски вырезаются
// @param  {Board} b
// @return {arrayof: Board}
// function nextBoards(b){ return []; } // Заглушка
function nextBoards(b){
  return keepOnlyValid(fillBlanks(b));
}

// Тесты для nextBoards
(function(){
  console.log("Тест7:", nextBoards(createEmptyBoard(1)).toString()==keepOnlyValid(fillBlanks(createEmptyBoard(1))).toString());
})();

// Создает массив досок в которых в каждой пустой клетке
// исходной доски стоит фигурка
// @param  {Board} b Исходная доска
// @return {arrayof: Board}

// function fillBlanks(b){ return []; } // заглушка

function fillBlanks(b){
  // @param {Board}   brd Итерируемая доска
  // @param {Natural} pos Позиция
  function iter(brd, pos){
    if(brd.length==0){
      return []; // базовый случай
    }else{
      var first = brd[0];
      var rest  = brd.slice(1,brd.length);
      if(first!=Q){
        return [fillSquare(b, pos, Q)].concat(iter(rest, pos+1));
      }else{
        return iter(rest, pos+1);
      }
    }
  }

  var clone = b.slice(0, b.length); // Клонируем доску
  return iter(clone, 0);
}

// Тесты для fillBlanks
(function(){
  console.log(
    "Тест8:",
    (function(){
      var x = fillBlanks(createEmptyBoard(1));
      return x.length==1 && x[0][0]==Q;
    })());
  console.log("тест9:", fillBlanks([Q]).length==0);
  console.log(
    "тест10:",
    (function(){
      var x = fillBlanks(createEmptyBoard(2));
      return x.length==4 &&
        x[0].toString()==[Q,B,B,B].toString() &&
        x[1].toString()==[B,Q,B,B].toString() &&
        x[2].toString()==[B,B,Q,B].toString() &&
        x[3].toString()==[B,B,B,Q].toString()
    })());
})();

// Ставит на доску в данную позицию данное значение (ферзь или пустая)
// @param {Board}   b Доска
// @param {Natural} p Позиция
// @param {Q | B}   v Ферзь или пустая?

// Тесты для fillSquare
(function(){
  console.log("Тест11:", fillSquare([B],0,Q).toString()==[Q].toString());
})()

function fillSquare(b,p,v){
  var clone = b.slice(0, b.length);
  clone[p]=v;
  return clone;
}

// Фильтрует массив досок оставляя только те доски
// в которых ни одна фигурка не атакует другую
// @param  {arrayof: Board} lob
// @return {arrayof: Board}
//function keepOnlyValid(lob){ return []; } // заглушка

(function(){
  console.log("Тест12:", keepOnlyValid([[B]]).toString()==[[B]].toString());
  console.log("Тест13:", keepOnlyValid([[Q]]).toString()==[[Q]].toString());
  console.log("Тест14:", function(){
    var lob = [
      [Q,Q,B,B],
      [B,Q,Q,B],
      [B,Q,B,Q]
    ];
    return keepOnlyValid(lob).length==0;
  }());
})();

function keepOnlyValid(lob){
  return lob.filter(function(b){ return isValid(b); });
}

// Проверяет валидна ли доска
// @param {Board} b Доска
// @return {Boolean} true - доска валидна, иначе false
// !!!
function isValid(b){ return false; }

(function(){
  // Не валидна если фигурки на одной горизонтали
  console.log("Тест16:", isValid([Q,Q,
                                  B,B])==false);
  // Не валидна если фигурки на одной вертикали
  console.log("Тест17:", isValid([Q,B,
                                  Q,B])==false);
  // Не валидна если фигурки на одной диагонали
  console.log("Тест18:", isValid([Q,B,
                                  B,Q])==false);
  console.log("Тест19:", isValid([B,Q,
                                  Q,B])==false);
  // Валидные доски
  console.log("Тест20:", isValid([Q])==true);
  console.log("Тест21:", isValid(n4emptBrd)==true);
  console.log("Тест22:", isValid(n4solBrd)==true);
})();

function isValid(b){
  // Атакует по горизонтали?
  // @return {Boolean}
  // !!!
  function isAttackByHorizontal(){
    return false;
  }

  // Атакует по вертикали?
  // @return {Boolean}
  // !!!
  function isAttackByVertical(){
    return false;
  }

  // Атакует по диагонали?
  // @return {Boolean}
  // !!!
  function isAttackByDiagonal(){
    return false;
  }
}

// Считает размерность доски исходя из количества элементов
// @param  {Board}   Доска
// @return {Natural} Размерность доски
(function(){
  console.log("Тест23:", getBoardSize(n4emptBrd)==4);
})();

function getBoardSize(board){
  return Math.sqrt(board.length);
}

// Переводит позицию фигурки в строку
// @param  {Integer} Позиция доски
// @param  {Board}   Доска
// @return {Integer} Строка на которой стоит фигурка. Zero-base
(function(){
  console.log("Тест24:", posToRow(1, n4emptBrd)==0);
})();

function posToRow(pos, board){
  // Тут важно округлить в меньшую сторону
  return Math.floor(pos/getBoardSize(board));
}

// Переводит позицию фигурки в столбец
// @param  {Integer} Позиция доски
// @param  {Board}   Доска
// @return {Integer} Колонка на которой стоит фигурка. Zero-base
(function(){
  console.log("Тест25:", posToCol(5, n4emptBrd)==1);
})();

function posToCol(pos, board){
  return pos%getBoardSize(board);
}
<commit_msg>Функции атаки<commit_after>// Пустая клетка
var B = false;
// Клетка с фигурой
var Q = true;

// Тип данных: Board
// Пустая доска с размерностью n=4
var n4emptBrd = [B, B, B, B,
                 B, B, B, B,
                 B, B, B, B,
                 B, B, B, B];
// Решение для доски с размерностью 4
var n4solBrd = [B, Q, B, B,
                B, B, B, Q,
                Q, B, B, B,
                B, B, Q, B];

// Конвертирует позицию вида ряд:столбец
// в индекс плоского массива
// Все индексы начинаются с нуля.
// @param  {Integer[0...]} r Ряд
// @param  {Integer[0...]} c Столбец
// @param  {Natural}       n Размерность доски
// @return {Natural} Позиция
function rcToPos(r,c,n){
  return r*n+c;
};

// Тесты для функции rcToPos
(function(){
  console.log("Тест1:", n4solBrd[rcToPos(0,1,4)]==Q);
  console.log("Тест2:", n4solBrd[rcToPos(3,3,4)]==B);
})();

// Создает пустую доску n*n элементов
// @param {Natural} n Размерность доски
// @return {Board}
function createEmptyBoard(n){
  var b = [];
  for(var i=0;i<n*n;i++){ b.push(B); }
  return b;
};

// Тесты для функции createEmptyBoard
(function(){
  console.log("Тест3:", createEmptyBoard(4).length==16);
})();

// Решает проблемы n-ферзей
// @param {Board} b Доска
// @return {Board | false}:
//          Если задача имеет решение тогда доску
//          Если решения нет тогда false

// function solve(b){return false}; // заглушка

function solve(b){
  // Ищет решение для данной доски
  // @param {Board} b
  // @return {Board | false}
  function solveBrd(b){
    if(isSolved(b)){
      return b;
    }else{
      return solveChilds(nextBoards(b));
    }
  };

  // Ищет решения для списка потомков
  // @param {arrayof: Board} lob
  // @return {Board | false}
  function solveChilds(lob){
    if(lob.length==0){
      return false; // базовый случай
    }else{
      // рекурсивный случай
      var first = lob[0];
      var rest  = lob.slice(1,lob.length);
      var tryBrd = solveBrd(first);
      if(tryBrd!=false){
        return tryBrd;
      }else{
        return solveChilds(rest);
      }
    }
  };

  return solveBrd(b);
};

(function(){
  console.log("Тест4:", solve(createEmptyBoard(1))==[Q]);
  console.log("Тест5:", solve(createEmptyBoard(2))==false);
  console.log("Тест6:", solve(createEmptyBoard(4))==n4solBrd);
})();

// Является ли данная доска решением?
// @param  {Board} b
// @return {Boolean}
// !!!
function isSolved(b){ return false; } // Заглушка

// Создает массив потомков данной доски
// Все несостоятельные доски вырезаются
// @param  {Board} b
// @return {arrayof: Board}
// function nextBoards(b){ return []; } // Заглушка
function nextBoards(b){
  return keepOnlyValid(fillBlanks(b));
}

// Тесты для nextBoards
(function(){
  console.log("Тест7:", nextBoards(createEmptyBoard(1)).toString()==keepOnlyValid(fillBlanks(createEmptyBoard(1))).toString());
})();

// Создает массив досок в которых в каждой пустой клетке
// исходной доски стоит фигурка
// @param  {Board} b Исходная доска
// @return {arrayof: Board}

// function fillBlanks(b){ return []; } // заглушка

function fillBlanks(b){
  // @param {Board}   brd Итерируемая доска
  // @param {Natural} pos Позиция
  function iter(brd, pos){
    if(brd.length==0){
      return []; // базовый случай
    }else{
      var first = brd[0];
      var rest  = brd.slice(1,brd.length);
      if(first!=Q){
        return [fillSquare(b, pos, Q)].concat(iter(rest, pos+1));
      }else{
        return iter(rest, pos+1);
      }
    }
  }

  var clone = b.slice(0, b.length); // Клонируем доску
  return iter(clone, 0);
}

// Тесты для fillBlanks
(function(){
  console.log(
    "Тест8:",
    (function(){
      var x = fillBlanks(createEmptyBoard(1));
      return x.length==1 && x[0][0]==Q;
    })());
  console.log("тест9:", fillBlanks([Q]).length==0);
  console.log(
    "тест10:",
    (function(){
      var x = fillBlanks(createEmptyBoard(2));
      return x.length==4 &&
        x[0].toString()==[Q,B,B,B].toString() &&
        x[1].toString()==[B,Q,B,B].toString() &&
        x[2].toString()==[B,B,Q,B].toString() &&
        x[3].toString()==[B,B,B,Q].toString()
    })());
})();

// Ставит на доску в данную позицию данное значение (ферзь или пустая)
// @param {Board}   b Доска
// @param {Natural} p Позиция
// @param {Q | B}   v Ферзь или пустая?

// Тесты для fillSquare
(function(){
  console.log("Тест11:", fillSquare([B],0,Q).toString()==[Q].toString());
})()

function fillSquare(b,p,v){
  var clone = b.slice(0, b.length);
  clone[p]=v;
  return clone;
}

// Фильтрует массив досок оставляя только те доски
// в которых ни одна фигурка не атакует другую
// @param  {arrayof: Board} lob
// @return {arrayof: Board}
//function keepOnlyValid(lob){ return []; } // заглушка

(function(){
  console.log("Тест12:", keepOnlyValid([[B]]).toString()==[[B]].toString());
  console.log("Тест13:", keepOnlyValid([[Q]]).toString()==[[Q]].toString());
  console.log("Тест14:", function(){
    var lob = [
      [Q,Q,B,B],
      [B,Q,Q,B],
      [B,Q,B,Q]
    ];
    return keepOnlyValid(lob).length==0;
  }());
})();

function keepOnlyValid(lob){
  return lob.filter(function(b){ return isValid(b); });
}

// Проверяет валидна ли доска
// @param {Board} b Доска
// @return {Boolean} true - доска валидна, иначе false
// !!!
function isValid(b){ return false; }

(function(){
  // Не валидна если фигурки на одной горизонтали
  console.log("Тест16:", isValid([Q,Q,
                                  B,B])==false);
  // Не валидна если фигурки на одной вертикали
  console.log("Тест17:", isValid([Q,B,
                                  Q,B])==false);
  // Не валидна если фигурки на одной диагонали
  console.log("Тест18:", isValid([Q,B,
                                  B,Q])==false);
  console.log("Тест19:", isValid([B,Q,
                                  Q,B])==false);
  // Валидные доски
  console.log("Тест20:", isValid([Q])==true);
  console.log("Тест21:", isValid(n4emptBrd)==true);
  console.log("Тест22:", isValid(n4solBrd)==true);
})();

function isValid(b){
  // TODO: вот эту штуку надо развернуть так, чтобы были формулы для подсчета атак а рекурсия отдельно
  // Атакует по горизонтали?
  // @param {Integer} thisPos   Позиция этой фигурки
  // @param {Integer} othersPos Позиция остальных фигурок
  // @return {Boolean}
  function isAttackByHorizontal(thisPos, othersPos){
    if(othersPos.length==0){
      return true;
    }else{
      var othersFirst = othersPos[0];
      var othersRest  = othersPos.slice(1, othersPos.length);
      return !(posToRow(thisPos)==posToRow(othersFirst)) &&
        isAttackByHorizontal(thisPos, othersRest);
    }
  }

  // Атакует по вертикали?
  // @param {Integer} thisPos   Позиция этой фигурки
  // @param {Integer} othersPos Позиция остальных фигурок
  // @return {Boolean}
  function isAttackByVertical(thisPos, othersPos){
    if(othersPos.length==0){
      return true;
    }else{
      var othersFirst = othersPos[0];
      var othersRest  = othersPos.slice(1, othersPos.length);
      return !(posToCol(thisPos)==posToCol(othersFirst)) &&
        isAttackByVertical(thisPos, othersRest);
    }
  }

  // Атакует по диагонали?
  // @param {Integer} thisPos   Позиция этой фигурки
  // @param {Integer} othersPos Позиция остальных фигурок
  // @return {Boolean}
  function isAttackByDiagonal(thisPos, othersPos){
    if(othersPos.length==0){
      return true;
    }else{
      var othersFirst = othersPos[0];
      var othersRest  = othersPos.slice(1, othersPos.length);
      var rowDiff     = posToRow(thisPos) - posToRow(othersFirst);
      var colDiff     = posToCol(thisPos) - posToRow(othersFirst);
      return !(Math.abs(rowDiff/colDiff)==1) &&
        isAttackByDiagonal(othersRest);
    }
  }
}

// Считает размерность доски исходя из количества элементов
// @param  {Board}   Доска
// @return {Natural} Размерность доски
(function(){
  console.log("Тест23:", getBoardSize(n4emptBrd)==4);
})();

function getBoardSize(board){
  return Math.sqrt(board.length);
}

// Переводит позицию фигурки в строку
// @param  {Integer} Позиция доски
// @param  {Board}   Доска
// @return {Integer} Строка на которой стоит фигурка. Zero-base
(function(){
  console.log("Тест24:", posToRow(1, n4emptBrd)==0);
})();

function posToRow(pos, board){
  // Тут важно округлить в меньшую сторону
  return Math.floor(pos/getBoardSize(board));
}

// Переводит позицию фигурки в столбец
// @param  {Integer} Позиция доски
// @param  {Board}   Доска
// @return {Integer} Колонка на которой стоит фигурка. Zero-base
(function(){
  console.log("Тест25:", posToCol(5, n4emptBrd)==1);
})();

function posToCol(pos, board){
  return pos%getBoardSize(board);
}
<|endoftext|>