<commit_before>var Promise = require('bluebird');
var _ = require('lodash');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs-extra');
var path = require('path');
var url = require('url');
var rcedit = Promise.promisify(require('rcedit'));
var spawn = require('child_process').spawn;
var semver = require('semver');

var NwVersions = require('./versions');
var Utils = require('./utils');
var Downloader = require('./downloader');

// We inherit from EventEmitter for logging
inherits(NwBuilder, EventEmitter);
module.exports = NwBuilder;
function NwBuilder(options) {
    var self = this;
    var defaults = {
        files: null,
        appName: false,
        appVersion: false,
        platforms: ['win','osx'],
        version: 'latest',
        buildDir: './build',
        cacheDir: './cache',
        downloadUrl: 'http://dl.node-webkit.org/',
        buildType: 'default',
        forceDownload: false,
        macCredits: false,
        macIcns: false,
        macZip: false,
        macPlist: false,
        winIco: null,
        packageJson: null // Overrides to the node webkit app package.json file.
    };

    // Assing options
    this.options = _.defaults(options, defaults);
    this._platforms = [{
        platform: 'win',
        needsZip: true,
        runable: 'nw.exe',
        files: { // First file must be the executable
            '<=0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak'],
            '>0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak']
        }
    },{
        platform: 'osx',
        runable: 'node-webkit.app/Contents/MacOS/node-webkit',
        files: {
            '*': ['node-webkit.app']
        }
    },{
        platform: 'linux32',
        needsZip: true,
        chmod: '0755',
        runable: 'nw',
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat']
        }
    },{
        platform: 'linux64',
        needsZip: true,
        chmod: '0755', // chmod file file to be executable
        runable: 'nw',
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat']
        }
    }];

    // Some Option checking
    if(!this.options.files) {
        throw new Error('Please specify some files');
    }

    // Check Platforms
    this._platforms.forEach(function (platform) {
        platform.active = (self.options.platforms.indexOf(platform.platform) === -1 ? false : true);
    });
    // Platforms
    this._platforms = _.where(this._platforms, {'active':true});

    if(this._platforms.length === 0 || this._platforms.length === undefined) {
        throw new Error('No platform to build!');
    }

    return this;
};

NwBuilder.prototype.build = function (callback) {
    var hasCallback = (typeof callback === 'function'),
        done = Promise.defer();

    // Let's create a node Webkit app
    this.checkFiles().bind(this)
        .then(this.resolveLatestVersion)
        .then(this.checkVersion)
        .then(this.platformFilesForVersion)
        .then(this.downloadNodeWebkit)
        .then(this.createReleaseFolder)
        .then(this.copyNodeWebkit)
        .then(this.zipAppFiles)
        .then(this.mergeAppFiles)
        .then(this.handleMacApp)
        .then(this.handleWinApp)
        .then(this.cleanUp)
        .then(function (info) {
            if(hasCallback) {
                callback(false, info);
            } else {
                done.resolve(info);
            }
        })
        .catch(function (error) {
            if(hasCallback) {
                callback(error);
            } else {
                done.reject(error);
            }
        });

    return hasCallback ? true : done.promise;
};

NwBuilder.prototype.run = function (callback) {
    var hasCallback = (typeof callback === 'function'),
        done = Promise.defer();

    // Let's create a node Webkit app
    this.checkFiles().bind(this)
        .then(this.resolveLatestVersion)
        .then(this.checkVersion)
        .then(this.platformFilesForVersion)
        .then(this.downloadNodeWebkit)
        .then(this.runApp)
        .then(function (info) {
            if(hasCallback) {
                callback(false, info);
            } else {
                done.resolve(info);
            }
        })
        .catch(function (error) {
            if(hasCallback) {
                callback(true, error);
            } else {
                done.reject(error);
            }
        });

    return hasCallback ? true : done.promise;
};

NwBuilder.prototype.checkFiles = function () {
    var self = this;

    return Utils.getFileList(this.options.files, this.options.buildDir, this.options.packageJson).then(function (data) {

        self._appPkg = data.json;
        self._files = data.files;

        if(!self.options.appName || self.options.appVersion) {
            return Utils.getPackageInfo(self._appPkg).then(function (appPkg) {
               self._appPkg = appPkg;
               self.options.appName = (self.options.appName ? self.options.appName : appPkg.name);
               self.options.appVersion = (self.options.appVersion ? self.options.appVersion : appPkg.version);
            });
        }
    });

};

NwBuilder.prototype.resolveLatestVersion = function () {
    var self = this;

    if(self.options.version !== 'latest') return Promise.resolve();

    return NwVersions.getLatestVersion().then(function(latestVersion){
        self.emit('log', 'Latest Version: v' + latestVersion);
        self.options.version = latestVersion;
    });
};

NwBuilder.prototype.checkVersion = function () {
    var version = this.options.version.replace('v', '');

    if(!semver.valid(this.options.version)){
        return Promise.reject('The version ' + this.options.version + ' is not valid.');
    } else {
        this._version = NwVersions.getVersionNames( version );
        this.emit('log', 'Using v' + this._version.version);
    }

    return Promise.resolve();
};

NwBuilder.prototype.platformFilesForVersion = function () {
    var self = this;

    this._platforms.forEach(function (single_platform) {
        var satisfied = !Object.keys(single_platform.files).every(function(range) {
            if (semver.satisfies(self._version.version, range)) {
                single_platform.files = single_platform.files[range];
                return false;
            }
            return true;
        });

        if (!satisfied) {
            throw new Error("Unsupported node-webkit version '" + this._version.version + "' for platform '" + platform.type + "'");
        }
    });

    return true;
};

NwBuilder.prototype.downloadNodeWebkit = function () {
    var self = this,
        downloads = [];

    this._platforms.map(function (single_platform) {
        single_platform.cache = path.resolve(self.options.cacheDir, self._version.version, single_platform.platform);
        single_platform.url = url.resolve(self.options.downloadUrl, self._version.platforms[single_platform.platform]);

        // Ensure that there is a cache folder
        if(self.options.forceDownload) {
            fs.removeSync(single_platform.cache);
        }

        fs.mkdirpSync(single_platform.cache);
        self.emit('log', 'Create cache folder in ' + path.resolve(self.options.cacheDir, self._version.version));

        if(!Downloader.checkCache(single_platform.cache, single_platform.files)) {
            downloads.push(
                Downloader.downloadAndUnpack(single_platform.cache, single_platform.url)
                    .catch(function(err){
                        if(err.statusCode === 404){
                            self.emit('log', 'ERROR: The version '+self._version.version+' does not have a corresponding build posted at http://dl.node-webkit.org/. Please choose a version from that list.')
                        } else {
                            self.emit('log', err.msg)
                        }

                        return Promise.reject('Unable to download nodewebkit.');
                    })
            );
            self.emit('log', 'Downloading: ' + single_platform.url);
        } else {
            self.emit('log', 'Using cache for: ' + single_platform.platform);
        }

        return single_platform;
    });

    return Promise.all(downloads)
        .then(function(data) {
            Downloader.clearProgressbar();
            return data;
        });
};

NwBuilder.prototype.buildGypModules = function () {
    // @todo
    // If we trigger a rebuild we have to copy
    // the node_modules to a tmp location because
    // we don't want to change the source files

};

NwBuilder.prototype.createReleaseFolder = function () {
    var self = this,
        releasePath;

    if (_.isFunction(self.options.buildType)) {
      releasePath = self.options.buildType.call(self.options);
    } else {
      // buildTypes
      switch(self.options.buildType) {
          case 'timestamped':
              releasePath = self.options.appName + ' - ' + Math.round(Date.now() / 1000).toString();
          break;

          case 'versioned':
              releasePath = self.options.appName + ' - v' + self.options.appVersion;
          break;

          default:
              releasePath = self.options.appName;
      }
    }

    this._platforms.forEach(function (single_platform) {
        single_platform.releasePath = path.resolve(self.options.buildDir, releasePath, single_platform.platform);

        // Ensure that there is a release Folder, delete and create it.
        fs.removeSync(single_platform.releasePath);
        fs.mkdirpSync(single_platform.releasePath);
        self.emit('log', 'Create release folder in ' + single_platform.releasePath);
    });

    return true;

};

NwBuilder.prototype.copyNodeWebkit = function () {
    var self = this,
        copiedFiles = [];

    this._platforms.forEach(function (single_platform) {
        var executable = single_platform.runable.split('/')[0];
        single_platform.files.forEach(function (file, i) {
            var destFile = file;
            if(i===0) {
                // rename executable to app name
                destFile = self.options.appName + path.extname(file);
                // save new filename back to files list
                single_platform.files[0] = destFile;
            }
            copiedFiles.push(Utils.copyFile(path.resolve(single_platform.cache, file), path.resolve(single_platform.releasePath, destFile)));
        });
    });

    return Promise.all(copiedFiles);
};

NwBuilder.prototype.zipAppFiles = function () {
    var self = this;

    // Check if zip is needed
    this._needsZip = (self.options.macZip || _.any(this._platforms, {'needsZip':true}));

    return new Promise(function(resolve, reject) {
        if(self._needsZip) {
            Utils.generateZipFile(self._files, self).then(function (nwFile) {
                self._nwFile = nwFile;
                resolve();
            }, reject);
        } else {
            self._nwFile = null;
            resolve();
        }
    });
};

NwBuilder.prototype.mergeAppFiles = function () {
    var self = this,
        copiedFiles = [];

    this._platforms.forEach(function (single_platform) {
        // We copy the app files if we are on mac and don't force zip
        if(single_platform.platform === 'osx') {
            // no zip, copy the files
            if(!self.options.macZip) {
                self._files.forEach(function (file) {
                    var dest = path.resolve(single_platform.releasePath, self.options.appName+'.app', 'Contents', 'Resources', 'app.nw', file.dest);
                    copiedFiles.push(Utils.copyFile(file.src, dest));
                });
            } else {
                // zip just copy the app.nw
                copiedFiles.push(Utils.copyFile(self._nwFile, path.resolve(single_platform.releasePath, self.options.appName+'.app', 'Contents', 'Resources', 'nw.icns')));
            }
        } else {
            // We cat the app.nw file into the .exe / nw
            copiedFiles.push(Utils.mergeFiles(path.resolve(single_platform.releasePath, _.first(single_platform.files)), self._nwFile), single_platform.chmod);
        }
    });

    return Promise.all(copiedFiles);
};

NwBuilder.prototype.handleMacApp = function () {
    var self = this, allDone = [];
    var macPlatform = _.findWhere(self._platforms, {'platform':'osx'});
    if(!macPlatform) return Promise.resolve();

    // Let's first handle the mac icon
    if(self.options.macIcns) {
        allDone.push(Utils.copyFile(self.options.macIcns, path.resolve(macPlatform.releasePath, self.options.appName+'.app', 'Contents', 'Resources', 'nw.icns')));
    }

    // Let handle the Plist
    var PlistPath = path.resolve(macPlatform.releasePath, self.options.appName+'.app', 'Contents', 'Info.plist');

    // If the macPlist is a string we just copy the file
    if(typeof self.options.macPlist === 'String') {
        allDone.push(Utils.copyFile(self.options.macPlist, PlistPath));
    } else {
        // Setup the Plst
        var defaultPlist = {
            appName: self.options.appName,
            appVersion: self.options.appVersion,
            copyright: self._appPkg.copyright || false
        };

        var plistOptions = (self.options.macPlist ? _.defaults(self.options.macPlist, defaultPlist) : defaultPlist);
        allDone.push(Utils.editPlist(PlistPath, PlistPath, plistOptions));
    }

    return Promise.all(allDone);

};

NwBuilder.prototype.handleWinApp = function () {
    var self = this,
        done = Promise.defer();
    var winPlatform = _.findWhere(self._platforms, {'platform':'win'});
    if(!winPlatform || !self.options.winIco) return Promise.resolve();

    // Set icon
    if (self.options.winIco) {
        self.emit('log', 'Update executable icon');
        rcedit(
            path.resolve(winPlatform.releasePath, _.first(winPlatform.files)),
            {
                icon: path.resolve(self.options.winIco)
            },
            function(err){
                if(err) {
                    done.reject('Error while updating the Windows icon.' +
                        (process.platform !== "win32" ? ' Wine (winehq.org) must be installed to add custom icons from Mac and Linux.' : '')
                    );
                }
                else {
                    done.resolve();
                }
            }
        );
    }

    return done.promise;
};

NwBuilder.prototype.cleanUp = function() {
    var self    = this,
        done    = Promise.defer(),
        tmpPath = path.normalize(path.join(self.options.buildDir, '/temp/'));

    if (self.options.packageJson && fs.existsSync(tmpPath)) {
        fs.readdirSync(tmpPath).forEach(function(file, index) {
          var curPath = tmpPath + "/" + file;
          if (fs.lstatSync(curPath).isDirectory()) { // recurse
            deleteFolderRecursive(curPath);
          } else { // delete file
            fs.unlinkSync(curPath);
          }
        });

        fs.rmdirSync(tmpPath);
    }

    return done.promise;
};

NwBuilder.prototype.runApp = function () {
    var self = this,
        platform = this._platforms[0],
        executable = path.resolve(platform.cache, platform.runable);

        self.emit('log', 'Launching App');
        return new Promise(function(resolve, reject) {
            var p = spawn(executable, ['--enable-logging', self.options.files.replace(/\*[\/\*]*/,"")]);

            p.stdout.on('data', function(data) {
                self.emit('stdout', data);
            });

            p.stderr.on('data', function(data) {
                self.emit('stderr', data);
            });

            p.on('close', function(code) {
                self.emit('log', 'App exited with code ' + code);
                resolve();
            });
        });
};
<commit_msg>Clean up the code that is used to remove the temp directory.<commit_after>var Promise = require('bluebird');
var _ = require('lodash');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs-extra');
var path = require('path');
var url = require('url');
var rcedit = Promise.promisify(require('rcedit'));
var spawn = require('child_process').spawn;
var semver = require('semver');

var NwVersions = require('./versions');
var Utils = require('./utils');
var Downloader = require('./downloader');

// We inherit from EventEmitter for logging
inherits(NwBuilder, EventEmitter);
module.exports = NwBuilder;
function NwBuilder(options) {
    var self = this;
    var defaults = {
        files: null,
        appName: false,
        appVersion: false,
        platforms: ['win','osx'],
        version: 'latest',
        buildDir: './build',
        cacheDir: './cache',
        downloadUrl: 'http://dl.node-webkit.org/',
        buildType: 'default',
        forceDownload: false,
        macCredits: false,
        macIcns: false,
        macZip: false,
        macPlist: false,
        winIco: null,
        packageJson: null // Overrides to the node webkit app package.json file.
    };

    // Assing options
    this.options = _.defaults(options, defaults);
    this._platforms = [{
        platform: 'win',
        needsZip: true,
        runable: 'nw.exe',
        files: { // First file must be the executable
            '<=0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak'],
            '>0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak']
        }
    },{
        platform: 'osx',
        runable: 'node-webkit.app/Contents/MacOS/node-webkit',
        files: {
            '*': ['node-webkit.app']
        }
    },{
        platform: 'linux32',
        needsZip: true,
        chmod: '0755',
        runable: 'nw',
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat']
        }
    },{
        platform: 'linux64',
        needsZip: true,
        chmod: '0755', // chmod file file to be executable
        runable: 'nw',
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat']
        }
    }];

    // Some Option checking
    if(!this.options.files) {
        throw new Error('Please specify some files');
    }

    // Check Platforms
    this._platforms.forEach(function (platform) {
        platform.active = (self.options.platforms.indexOf(platform.platform) === -1 ? false : true);
    });
    // Platforms
    this._platforms = _.where(this._platforms, {'active':true});

    if(this._platforms.length === 0 || this._platforms.length === undefined) {
        throw new Error('No platform to build!');
    }

    return this;
};

NwBuilder.prototype.build = function (callback) {
    var hasCallback = (typeof callback === 'function'),
        done = Promise.defer();

    // Let's create a node Webkit app
    this.checkFiles().bind(this)
        .then(this.resolveLatestVersion)
        .then(this.checkVersion)
        .then(this.platformFilesForVersion)
        .then(this.downloadNodeWebkit)
        .then(this.createReleaseFolder)
        .then(this.copyNodeWebkit)
        .then(this.zipAppFiles)
        .then(this.mergeAppFiles)
        .then(this.handleMacApp)
        .then(this.handleWinApp)
        .then(this.cleanUp)
        .then(function (info) {
            if(hasCallback) {
                callback(false, info);
            } else {
                done.resolve(info);
            }
        })
        .catch(function (error) {
            if(hasCallback) {
                callback(error);
            } else {
                done.reject(error);
            }
        });

    return hasCallback ? true : done.promise;
};

NwBuilder.prototype.run = function (callback) {
    var hasCallback = (typeof callback === 'function'),
        done = Promise.defer();

    // Let's create a node Webkit app
    this.checkFiles().bind(this)
        .then(this.resolveLatestVersion)
        .then(this.checkVersion)
        .then(this.platformFilesForVersion)
        .then(this.downloadNodeWebkit)
        .then(this.runApp)
        .then(function (info) {
            if(hasCallback) {
                callback(false, info);
            } else {
                done.resolve(info);
            }
        })
        .catch(function (error) {
            if(hasCallback) {
                callback(true, error);
            } else {
                done.reject(error);
            }
        });

    return hasCallback ? true : done.promise;
};

NwBuilder.prototype.checkFiles = function () {
    var self = this;

    return Utils.getFileList(this.options.files, this.options.buildDir, this.options.packageJson).then(function (data) {

        self._appPkg = data.json;
        self._files = data.files;

        if(!self.options.appName || self.options.appVersion) {
            return Utils.getPackageInfo(self._appPkg).then(function (appPkg) {
               self._appPkg = appPkg;
               self.options.appName = (self.options.appName ? self.options.appName : appPkg.name);
               self.options.appVersion = (self.options.appVersion ? self.options.appVersion : appPkg.version);
            });
        }
    });

};

NwBuilder.prototype.resolveLatestVersion = function () {
    var self = this;

    if(self.options.version !== 'latest') return Promise.resolve();

    return NwVersions.getLatestVersion().then(function(latestVersion){
        self.emit('log', 'Latest Version: v' + latestVersion);
        self.options.version = latestVersion;
    });
};

NwBuilder.prototype.checkVersion = function () {
    var version = this.options.version.replace('v', '');

    if(!semver.valid(this.options.version)){
        return Promise.reject('The version ' + this.options.version + ' is not valid.');
    } else {
        this._version = NwVersions.getVersionNames( version );
        this.emit('log', 'Using v' + this._version.version);
    }

    return Promise.resolve();
};

NwBuilder.prototype.platformFilesForVersion = function () {
    var self = this;

    this._platforms.forEach(function (single_platform) {
        var satisfied = !Object.keys(single_platform.files).every(function(range) {
            if (semver.satisfies(self._version.version, range)) {
                single_platform.files = single_platform.files[range];
                return false;
            }
            return true;
        });

        if (!satisfied) {
            throw new Error("Unsupported node-webkit version '" + this._version.version + "' for platform '" + platform.type + "'");
        }
    });

    return true;
};

NwBuilder.prototype.downloadNodeWebkit = function () {
    var self = this,
        downloads = [];

    this._platforms.map(function (single_platform) {
        single_platform.cache = path.resolve(self.options.cacheDir, self._version.version, single_platform.platform);
        single_platform.url = url.resolve(self.options.downloadUrl, self._version.platforms[single_platform.platform]);

        // Ensure that there is a cache folder
        if(self.options.forceDownload) {
            fs.removeSync(single_platform.cache);
        }

        fs.mkdirpSync(single_platform.cache);
        self.emit('log', 'Create cache folder in ' + path.resolve(self.options.cacheDir, self._version.version));

        if(!Downloader.checkCache(single_platform.cache, single_platform.files)) {
            downloads.push(
                Downloader.downloadAndUnpack(single_platform.cache, single_platform.url)
                    .catch(function(err){
                        if(err.statusCode === 404){
                            self.emit('log', 'ERROR: The version '+self._version.version+' does not have a corresponding build posted at http://dl.node-webkit.org/. Please choose a version from that list.')
                        } else {
                            self.emit('log', err.msg)
                        }

                        return Promise.reject('Unable to download nodewebkit.');
                    })
            );
            self.emit('log', 'Downloading: ' + single_platform.url);
        } else {
            self.emit('log', 'Using cache for: ' + single_platform.platform);
        }

        return single_platform;
    });

    return Promise.all(downloads)
        .then(function(data) {
            Downloader.clearProgressbar();
            return data;
        });
};

NwBuilder.prototype.buildGypModules = function () {
    // @todo
    // If we trigger a rebuild we have to copy
    // the node_modules to a tmp location because
    // we don't want to change the source files

};

NwBuilder.prototype.createReleaseFolder = function () {
    var self = this,
        releasePath;

    if (_.isFunction(self.options.buildType)) {
      releasePath = self.options.buildType.call(self.options);
    } else {
      // buildTypes
      switch(self.options.buildType) {
          case 'timestamped':
              releasePath = self.options.appName + ' - ' + Math.round(Date.now() / 1000).toString();
          break;

          case 'versioned':
              releasePath = self.options.appName + ' - v' + self.options.appVersion;
          break;

          default:
              releasePath = self.options.appName;
      }
    }

    this._platforms.forEach(function (single_platform) {
        single_platform.releasePath = path.resolve(self.options.buildDir, releasePath, single_platform.platform);

        // Ensure that there is a release Folder, delete and create it.
        fs.removeSync(single_platform.releasePath);
        fs.mkdirpSync(single_platform.releasePath);
        self.emit('log', 'Create release folder in ' + single_platform.releasePath);
    });

    return true;

};

NwBuilder.prototype.copyNodeWebkit = function () {
    var self = this,
        copiedFiles = [];

    this._platforms.forEach(function (single_platform) {
        var executable = single_platform.runable.split('/')[0];
        single_platform.files.forEach(function (file, i) {
            var destFile = file;
            if(i===0) {
                // rename executable to app name
                destFile = self.options.appName + path.extname(file);
                // save new filename back to files list
                single_platform.files[0] = destFile;
            }
            copiedFiles.push(Utils.copyFile(path.resolve(single_platform.cache, file), path.resolve(single_platform.releasePath, destFile)));
        });
    });

    return Promise.all(copiedFiles);
};

NwBuilder.prototype.zipAppFiles = function () {
    var self = this;

    // Check if zip is needed
    this._needsZip = (self.options.macZip || _.any(this._platforms, {'needsZip':true}));

    return new Promise(function(resolve, reject) {
        if(self._needsZip) {
            Utils.generateZipFile(self._files, self).then(function (nwFile) {
                self._nwFile = nwFile;
                resolve();
            }, reject);
        } else {
            self._nwFile = null;
            resolve();
        }
    });
};

NwBuilder.prototype.mergeAppFiles = function () {
    var self = this,
        copiedFiles = [];

    this._platforms.forEach(function (single_platform) {
        // We copy the app files if we are on mac and don't force zip
        if(single_platform.platform === 'osx') {
            // no zip, copy the files
            if(!self.options.macZip) {
                self._files.forEach(function (file) {
                    var dest = path.resolve(single_platform.releasePath, self.options.appName+'.app', 'Contents', 'Resources', 'app.nw', file.dest);
                    copiedFiles.push(Utils.copyFile(file.src, dest));
                });
            } else {
                // zip just copy the app.nw
                copiedFiles.push(Utils.copyFile(self._nwFile, path.resolve(single_platform.releasePath, self.options.appName+'.app', 'Contents', 'Resources', 'nw.icns')));
            }
        } else {
            // We cat the app.nw file into the .exe / nw
            copiedFiles.push(Utils.mergeFiles(path.resolve(single_platform.releasePath, _.first(single_platform.files)), self._nwFile), single_platform.chmod);
        }
    });

    return Promise.all(copiedFiles);
};

NwBuilder.prototype.handleMacApp = function () {
    var self = this, allDone = [];
    var macPlatform = _.findWhere(self._platforms, {'platform':'osx'});
    if(!macPlatform) return Promise.resolve();

    // Let's first handle the mac icon
    if(self.options.macIcns) {
        allDone.push(Utils.copyFile(self.options.macIcns, path.resolve(macPlatform.releasePath, self.options.appName+'.app', 'Contents', 'Resources', 'nw.icns')));
    }

    // Let handle the Plist
    var PlistPath = path.resolve(macPlatform.releasePath, self.options.appName+'.app', 'Contents', 'Info.plist');

    // If the macPlist is a string we just copy the file
    if(typeof self.options.macPlist === 'String') {
        allDone.push(Utils.copyFile(self.options.macPlist, PlistPath));
    } else {
        // Setup the Plst
        var defaultPlist = {
            appName: self.options.appName,
            appVersion: self.options.appVersion,
            copyright: self._appPkg.copyright || false
        };

        var plistOptions = (self.options.macPlist ? _.defaults(self.options.macPlist, defaultPlist) : defaultPlist);
        allDone.push(Utils.editPlist(PlistPath, PlistPath, plistOptions));
    }

    return Promise.all(allDone);

};

NwBuilder.prototype.handleWinApp = function () {
    var self = this,
        done = Promise.defer();
    var winPlatform = _.findWhere(self._platforms, {'platform':'win'});
    if(!winPlatform || !self.options.winIco) return Promise.resolve();

    // Set icon
    if (self.options.winIco) {
        self.emit('log', 'Update executable icon');
        rcedit(
            path.resolve(winPlatform.releasePath, _.first(winPlatform.files)),
            {
                icon: path.resolve(self.options.winIco)
            },
            function(err){
                if(err) {
                    done.reject('Error while updating the Windows icon.' +
                        (process.platform !== "win32" ? ' Wine (winehq.org) must be installed to add custom icons from Mac and Linux.' : '')
                    );
                }
                else {
                    done.resolve();
                }
            }
        );
    }

    return done.promise;
};

NwBuilder.prototype.cleanUp = function() {
    var self    = this,
        done    = Promise.defer(),
        tmpPath = path.normalize(path.join(self.options.buildDir, '/temp/'));

    if (self.options.packageJson && fs.existsSync(tmpPath)) {
        fs.removeSync(tmpPath);
    }

    return done.promise;
};

NwBuilder.prototype.runApp = function () {
    var self = this,
        platform = this._platforms[0],
        executable = path.resolve(platform.cache, platform.runable);

        self.emit('log', 'Launching App');
        return new Promise(function(resolve, reject) {
            var p = spawn(executable, ['--enable-logging', self.options.files.replace(/\*[\/\*]*/,"")]);

            p.stdout.on('data', function(data) {
                self.emit('stdout', data);
            });

            p.stderr.on('data', function(data) {
                self.emit('stderr', data);
            });

            p.on('close', function(code) {
                self.emit('log', 'App exited with code ' + code);
                resolve();
            });
        });
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import R from 'ramda';

import { PanResponder, View } from 'react-native';

// Utils
import { angle, distance } from './utils/math.js';
import {
  getAngle,
  getScale,
  getTouches,
  isMultiTouch,
} from './utils/events.js';

export default class Gestures extends Component {

  static propTypes = {
    children: PropTypes.element,
    // Behavior
    draggable: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({
        x: PropTypes.bool,
        y: PropTypes.bool,
      }),
    ]),
    rotatable: PropTypes.bool,
    scalable: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({
        min: PropTypes.number,
        max: PropTypes.number,
      }),
    ]),
    // Styles
    styles: PropTypes.object,
    // Callbacks
    onStart: PropTypes.func,
    onChange: PropTypes.func,
    onEnd: PropTypes.func,
    onMultyTouchStart: PropTypes.func,
    onMultyTouchChange: PropTypes.func,
    onMultyTouchEnd: PropTypes.func,
    onRelease: PropTypes.func, // Legacy
    onRotateStart: PropTypes.func,
    onRotateChange: PropTypes.func,
    onRotateEnd: PropTypes.func,
    onScaleStart: PropTypes.func,
    onScaleChange: PropTypes.func,
    onScaleEnd: PropTypes.func,
  };

  static defaultProps = {
    children: {},
    // Behavior
    draggable: true || {
      x: true,
      y: false,
    },
    rotatable: true,
    scalable: true || {
      min: 0.33,
      max: 2,
    },
    // Styles
    styles: {
      left: 0,
      top: 0,
      transform: [
        { rotate: '0deg' },
        { scale: 1 },
      ],
    },
    // Callbacks
    onStart: () => {},
    onChange: () => {},
    onEnd: () => {},
    onRelease: () => {}, // Legacy

    // New callbacks
    onMultyTouchStart: () => {},
    onMultyTouchChange: () => {},
    onMultyTouchEnd: () => {},
    onRotateStart: () => {},
    onRotateChange: () => {},
    onRotateEnd: () => {},
    onScaleStart: () => {},
    onScaleChange: () => {},
    onScaleEnd: () => {},
  }

  constructor(props) {
    super(props);

    this.state = {
      isMultyTouchingNow: false,
      isRotatingNow: false,
      isScalingNow: false,

      styles: {
        ...Gestures.defaultProps.styles,
        ...this.props.styles,
      },
    };
  }

  componentWillMount() {
    this.pan = PanResponder.create({
      onPanResponderGrant: this.onMoveStart,
      onPanResponderMove: this.onMove,
      onPanResponderEnd: this.onMoveEnd,

      onPanResponderTerminate: () => true,
      onShouldBlockNativeResponder: () => true,
      onStartShouldSetPanResponder: () => true,
      onPanResponderTerminationRequest: () => true,
      onMoveShouldSetPanResponderCapture: (event, { dx, dy }) =>
        dx !== 0 && dy !== 0,
    });
  }

  onDrag(event, gestureState) {
    const { initialStyles } = this;
    const { draggable } = this.props;

    const isObject = R.is(Object, draggable);

    const left = (isObject ? draggable.x : draggable)
      ? initialStyles.left + gestureState.dx
      : initialStyles.left;

    const top = (isObject ? draggable.y : draggable)
      ? initialStyles.top + gestureState.dy
      : initialStyles.top;

    this.dragStyles = { left, top };
  }

  onRotate = (event) => {
    const { onRotateStart, onRotateChange, rotatable } = this.props;
    const { isRotatingNow, styles } = this.state;

    const { initialTouches } = this;

    if (rotatable) {
      const currentAngle = angle(getTouches(event));
      const initialAngle = initialTouches.length > 1
        ? angle(initialTouches)
        : currentAngle;
      const newAngle = currentAngle - initialAngle;
      const diffAngle = this.prevAngle - newAngle;

      this.pinchStyles.transform.push({
        rotate: getAngle(event, styles, diffAngle),
      });

      this.prevAngle = newAngle;

      if (!isRotatingNow) {
        onRotateStart(event, styles);

        this.setState({ isRotatingNow: true });
      } else {
        onRotateChange(event, styles);
      }
    }
  }

  onScale = (event) => {
    const { onScaleStart, onScaleChange, scalable } = this.props;
    const { isScalingNow, styles } = this.state;
    const { initialTouches } = this;

    const isObject = R.is(Object, scalable);

    if (isObject || scalable) {
      const currentDistance = distance(getTouches(event));
      const initialDistance = distance(initialTouches);
      const increasedDistance = currentDistance - initialDistance;
      const diffDistance = this.prevDistance - increasedDistance;

      const min = isObject ? scalable.min : 0.33;
      const max = isObject ? scalable.max : 2;
      const scale = Math.min(Math.max(getScale(event, styles, diffDistance), min), max);

      this.pinchStyles.transform.push({ scale });
      this.prevDistance = increasedDistance;

      if (!isScalingNow) {
        onScaleStart(event, styles);

        this.setState({ isScalingNow: true });
      } else {
        onScaleChange(event, styles);
      }
    }
  }

  onMoveStart = (event) => {
    const { styles } = this.state;
    const { onMultyTouchStart, onStart } = this.props;

    const touches = getTouches(event);

    this.prevAngle = 0;
    this.prevDistance = 0;
    this.initialTouchesAngle = 0;
    this.pinchStyles = {};
    this.dragStyles = {};

    this.initialTouches = getTouches(event);
    this.initialStyles = styles;

    onStart(event, styles);

    if (touches.length > 1) {
      onMultyTouchStart(event, styles);

      this.setState({ isMultyTouchingNow: true });
    }
  }

  onMove = (event, gestureState) => {
    const { isMultyTouchingNow, styles } = this.state;
    const { onChange, onMultyTouchChange } = this.props;

    const { initialTouches } = this;

    const touches = getTouches(event);

    if (touches.length !== initialTouches.length) {
      this.initialTouches = touches;
    } else {
      this.onDrag(event, gestureState);
      this.onPinch(event);
    }

    if (isMultyTouchingNow) {
      onMultyTouchChange(event, styles);
    }

    this.updateStyles();

    onChange(event, styles);
  }

  onMoveEnd = (event) => {
    const {
      isMultyTouchingNow,
      isRotatingNow,
      isScalingNow,
      styles,
    } = this.state;
    const {
      onEnd,
      onMultyTouchEnd,
      onRelease, // Legacy
      onRotateEnd,
      onScaleEnd,
    } = this.props;

    onEnd(event, styles);
    onRelease(event, styles); // Legacy

    if (isRotatingNow) {
      onRotateEnd(event, styles);
    }

    if (isScalingNow) {
      onScaleEnd(event, styles);
    }

    if (isMultyTouchingNow) {
      onMultyTouchEnd(event, styles);
    }

    this.setState({
      isRotatingNow: false,
      isScalingNow: false,
    });
  }

  onPinch = (event) => {
    if (isMultiTouch(event)) {
      this.pinchStyles = { transform: [] };

      this.onScale(event);
      this.onRotate(event);
    }
  }

  updateStyles = () => {
    const styles = {
      ...this.state.styles,
      ...this.dragStyles,
      ...this.pinchStyles,
    };

    this.updateNativeStyles(styles);
    this.setState({ styles });
  }

  updateNativeStyles = (styles) => {
    this.view.setNativeProps({ styles });
  }

  render() {
    const { styles } = this.state;

    return (
      <View
        ref={(c) => { this.view = c; }}
        style={styles}
        {...this.pan.panHandlers}
      >
        {
          this.props.children
        }
      </View>
    );
  }
}
<commit_msg>reset to original position<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import R from 'ramda';

import { PanResponder, View } from 'react-native';

// Utils
import { angle, distance } from './utils/math.js';
import {
  getAngle,
  getScale,
  getTouches,
  isMultiTouch,
} from './utils/events.js';

export default class Gestures extends Component {

  static propTypes = {
    children: PropTypes.element,
    // Behavior
    draggable: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({
        x: PropTypes.bool,
        y: PropTypes.bool,
      }),
    ]),
    rotatable: PropTypes.bool,
    scalable: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({
        min: PropTypes.number,
        max: PropTypes.number,
      }),
    ]),
    // Styles
    styles: PropTypes.object,
    // Callbacks
    onStart: PropTypes.func,
    onChange: PropTypes.func,
    onEnd: PropTypes.func,
    onMultyTouchStart: PropTypes.func,
    onMultyTouchChange: PropTypes.func,
    onMultyTouchEnd: PropTypes.func,
    onRelease: PropTypes.func, // Legacy
    onRotateStart: PropTypes.func,
    onRotateChange: PropTypes.func,
    onRotateEnd: PropTypes.func,
    onScaleStart: PropTypes.func,
    onScaleChange: PropTypes.func,
    onScaleEnd: PropTypes.func,
  };

  static defaultProps = {
    children: {},
    // Behavior
    draggable: true || {
      x: true,
      y: false,
    },
    rotatable: true,
    scalable: true || {
      min: 0.33,
      max: 2,
    },
    // Styles
    styles: {
      left: 0,
      top: 0,
      transform: [
        { rotate: '0deg' },
        { scale: 1 },
      ],
    },
    // Callbacks
    onStart: () => {},
    onChange: () => {},
    onEnd: () => {},
    onRelease: () => {}, // Legacy

    // New callbacks
    onMultyTouchStart: () => {},
    onMultyTouchChange: () => {},
    onMultyTouchEnd: () => {},
    onRotateStart: () => {},
    onRotateChange: () => {},
    onRotateEnd: () => {},
    onScaleStart: () => {},
    onScaleChange: () => {},
    onScaleEnd: () => {},
  }

  constructor(props) {
    super(props);

    this.state = {
      isMultyTouchingNow: false,
      isRotatingNow: false,
      isScalingNow: false,

      styles: {
        ...Gestures.defaultProps.styles,
        ...this.props.styles,
      },
    };
  }

  componentWillMount() {
    this.pan = PanResponder.create({
      onPanResponderGrant: this.onMoveStart,
      onPanResponderMove: this.onMove,
      onPanResponderEnd: this.onMoveEnd,

      onPanResponderTerminate: () => true,
      onShouldBlockNativeResponder: () => true,
      onStartShouldSetPanResponder: () => true,
      onPanResponderTerminationRequest: () => true,
      onMoveShouldSetPanResponderCapture: (event, { dx, dy }) =>
        dx !== 0 && dy !== 0,
    });
  }

  onDrag(event, gestureState) {
    const { initialStyles } = this;
    const { draggable } = this.props;

    const isObject = R.is(Object, draggable);

    const left = (isObject ? draggable.x : draggable)
      ? initialStyles.left + gestureState.dx
      : initialStyles.left;

    const top = (isObject ? draggable.y : draggable)
      ? initialStyles.top + gestureState.dy
      : initialStyles.top;

    this.dragStyles = { left, top };
  }

  onRotate = (event) => {
    const { onRotateStart, onRotateChange, rotatable } = this.props;
    const { isRotatingNow, styles } = this.state;

    const { initialTouches } = this;

    if (rotatable) {
      const currentAngle = angle(getTouches(event));
      const initialAngle = initialTouches.length > 1
        ? angle(initialTouches)
        : currentAngle;
      const newAngle = currentAngle - initialAngle;
      const diffAngle = this.prevAngle - newAngle;

      this.pinchStyles.transform.push({
        rotate: getAngle(event, styles, diffAngle),
      });

      this.prevAngle = newAngle;

      if (!isRotatingNow) {
        onRotateStart(event, styles);

        this.setState({ isRotatingNow: true });
      } else {
        onRotateChange(event, styles);
      }
    }
  }

  onScale = (event) => {
    const { onScaleStart, onScaleChange, scalable } = this.props;
    const { isScalingNow, styles } = this.state;
    const { initialTouches } = this;

    const isObject = R.is(Object, scalable);

    if (isObject || scalable) {
      const currentDistance = distance(getTouches(event));
      const initialDistance = distance(initialTouches);
      const increasedDistance = currentDistance - initialDistance;
      const diffDistance = this.prevDistance - increasedDistance;

      const min = isObject ? scalable.min : 0.33;
      const max = isObject ? scalable.max : 2;
      const scale = Math.min(Math.max(getScale(event, styles, diffDistance), min), max);

      this.pinchStyles.transform.push({ scale });
      this.prevDistance = increasedDistance;

      if (!isScalingNow) {
        onScaleStart(event, styles);

        this.setState({ isScalingNow: true });
      } else {
        onScaleChange(event, styles);
      }
    }
  }

  onMoveStart = (event) => {
    const { styles } = this.state;
    const { onMultyTouchStart, onStart } = this.props;

    const touches = getTouches(event);

    this.prevAngle = 0;
    this.prevDistance = 0;
    this.initialTouchesAngle = 0;
    this.pinchStyles = {};
    this.dragStyles = {};

    this.initialTouches = getTouches(event);
    this.initialStyles = styles;

    onStart(event, styles);

    if (touches.length > 1) {
      onMultyTouchStart(event, styles);

      this.setState({ isMultyTouchingNow: true });
    }
  }

  onMove = (event, gestureState) => {
    const { isMultyTouchingNow, styles } = this.state;
    const { onChange, onMultyTouchChange } = this.props;

    const { initialTouches } = this;

    const touches = getTouches(event);

    if (touches.length !== initialTouches.length) {
      this.initialTouches = touches;
    } else {
      this.onDrag(event, gestureState);
      this.onPinch(event);
    }

    if (isMultyTouchingNow) {
      onMultyTouchChange(event, styles);
    }

    this.updateStyles();

    onChange(event, styles);
  }

  onMoveEnd = (event) => {
    const {
      isMultyTouchingNow,
      isRotatingNow,
      isScalingNow,
      styles,
    } = this.state;
    const {
      onEnd,
      onMultyTouchEnd,
      onRelease, // Legacy
      onRotateEnd,
      onScaleEnd,
    } = this.props;

    onEnd(event, styles);
    onRelease(event, styles); // Legacy

    if (isRotatingNow) {
      onRotateEnd(event, styles);
    }

    if (isScalingNow) {
      onScaleEnd(event, styles);
    }

    if (isMultyTouchingNow) {
      onMultyTouchEnd(event, styles);
    }

    this.setState({
      isRotatingNow: false,
      isScalingNow: false,
    });
  }

  onPinch = (event) => {
    if (isMultiTouch(event)) {
      this.pinchStyles = { transform: [] };

      this.onScale(event);
      this.onRotate(event);
    }
  }

  updateStyles = () => {
    const styles = {
      ...this.state.styles,
      ...this.dragStyles,
      ...this.pinchStyles,
    };

    this.updateNativeStyles(styles);
    this.setState({ styles });
  }

  updateNativeStyles = (styles) => {
    this.view.setNativeProps({ styles });
  }

  reset() {
    const left = 0
    const top = 0
    this.dragStyles = { left, top };
    this.updateStyles();
  }

  render() {
    const { styles } = this.state;

    return (
      <View
        ref={(c) => { this.view = c; }}
        style={styles}
        {...this.pan.panHandlers}
      >
        {
          this.props.children
        }
      </View>
    );
  }
}
<|endoftext|>


<commit_before>"use strict";

const ul = require("ul")
    , couleurs = require("couleurs")
    , deffy = require("deffy")
    , Pixel = require("pixel-class")
    , pixelBg = require("pixel-bg")
    , pixelWhiteBg = require("pixel-white-bg")
    ;

const DEFAULTS = {
    pixels: " .,:;i1tfLCG08@"
  , reverse: false
  , colored: true
  , bg: false
  , fg: true
  , white_bg: true
  , px_background: null
};

class Asciifier {
    /**
     * Asciifer
     * Creates a new instance of the `Asciifier` class.
     *
     * @name Asciifier
     * @function
     * @param {Object} options An option that will override the defaults:
     *
     *  - `pixels` (Array|String): An array or string containing the characters used for converting the pixels in strings (default: `" .,:;i1tfLCG08@"`).
     *  - `reverse` (Boolean): If `true`, the pixels will be reversed creating a *negative image* effect (default: `false`).
     *  - `colored` (Boolean): If `true`, the output will contain ANSI styles (default: `true`).
     *  - `bg` (Boolean): If `true`, the background color will be used for coloring (default: false).
     *  - `fg` (Boolean): If `true`, the foreground color will be used for coloring (default: true).
     */
    constructor (options) {
        this.options = ul.merge(options, DEFAULTS);
    }

    /**
     * asciify
     * Calculates the best character to be used for that specific pixel.
     *
     * @name asciify
     * @function
     * @param {Object} pixel The pixel object:
     *
     *  - `r` (Number): The red value.
     *  - `g` (Number): The green value.
     *  - `b` (Number): The blue value.
     *  - `a` (Number): The alpha value.
     *
     * @param {Object} opts An object that will override the options fields.
     * @return {Object} An object containing:
     *
     *  - `pixel` (Object): The pixel object.
     *  - `value` (Number): The sum of the rgb values multiplied by `alpha`.
     *  - `toString` (Function): Returns the character.
     *  - `char` (String): The character value.
     *  - `reverse` (Boolean): Reverse the pixels. The result will be a *negative* image (default: `false`).
     *  - `bg` (Boolean): Enable background coloring (default: `false`).
     *  - `fg` (Boolean): Enable foreground coloring (default: `true`).
     *  - `colored` (Boolean): Enable both foreground and background coloring.
     *  - `white_bg` (Boolean): Turn on the white background for transparent pixels (default: `true`).
     *  - `px_background` (Object): An object containing the `r` (red), `g` (green) and `b` (blue) values of the custom background color.
     *
     */
    asciify (pixel, opts) {
        opts = ul.merge(opts, this.options);
        pixel = new Pixel(pixel);

        if (typeof opts.pixels === "string") {
            opts.pixels = opts.pixels.split("");
        }

        let pxs = [].concat(opts.pixels);

        // Reverse pixels
        if (opts.reverse) {
            pxs.reverse();
        }

        let value = pixel.intensity();

        if (opts.px_background) {
            pixel = pixelBg(pixel, opts.px_background);
            value = pixel.intensity();
        } else if (opts.white_bg) {
            pixel = pixelWhiteBg(pixel);
            value = pixel.intensity();
        }

        let precision = 255 * 3 / (opts.pixels.length - 1)
          , strPx = null
          , rawChar = null
          ;

        rawChar = pxs[Math.round(value / precision)];

        // Colored
        if (!opts.colored) {
            opts.fg = opts.bg = false;
        }

        strPx = rawChar;

        if (opts.fg) {
            strPx = couleurs.fg(strPx, pixel.r, pixel.g, pixel.b);
        }

        if (opts.bg) {
            strPx = couleurs.bg(strPx, pixel.r, pixel.g, pixel.b);
        }

        return {
            toString: () => strPx
          , value: value
          , pixel: pixel
          , char: rawChar
          , ansii: strPx
        };
    }
}

/**
 * asciifyPixel
 * Ascifiies a pixel.
 *
 * @name asciifyPixel
 * @function
 * @param {Object} pixel An pixel object to pass to the `Asciifier` class.
 * @param {Object} options The options to pass to the `Asciifier` class.
 * @return {String} The asciified pixel.
 */
function asciifyPixel(pixel, options) {
    return new Asciifier(options).asciify(pixel).toString();
}

asciifyPixel.Asciifier = Asciifier;

module.exports = asciifyPixel;
<commit_msg>Fix docs<commit_after>"use strict";

const ul = require("ul")
    , couleurs = require("couleurs")
    , deffy = require("deffy")
    , Pixel = require("pixel-class")
    , pixelBg = require("pixel-bg")
    , pixelWhiteBg = require("pixel-white-bg")
    ;

const DEFAULTS = {
    pixels: " .,:;i1tfLCG08@"
  , reverse: false
  , colored: true
  , bg: false
  , fg: true
  , white_bg: true
  , px_background: null
};

class Asciifier {
    /**
     * Asciifer
     * Creates a new instance of the `Asciifier` class.
     *
     * @name Asciifier
     * @function
     * @param {Object} options An option that will override the defaults:
     *
     *  - `pixels` (Array|String): An array or string containing the characters used for converting the pixels in strings (default: `" .,:;i1tfLCG08@"`).
     *  - `reverse` (Boolean): If `true`, the pixels will be reversed creating a *negative image* effect (default: `false`).
     *  - `colored` (Boolean): If `true`, the output will contain ANSI styles (default: `true`).
     *  - `bg` (Boolean): If `true`, the background color will be used for coloring (default: false).
     *  - `fg` (Boolean): If `true`, the foreground color will be used for coloring (default: true).
     *  - `white_bg` (Boolean): Turn on the white background for transparent pixels (default: `true`).
     *  - `px_background` (Object): An object containing the `r` (red), `g` (green) and `b` (blue) values of the custom background color.
     *
     */
    constructor (options) {
        this.options = ul.merge(options, DEFAULTS);
    }

    /**
     * asciify
     * Calculates the best character to be used for that specific pixel.
     *
     * @name asciify
     * @function
     * @param {Object} pixel The pixel object:
     *
     *  - `r` (Number): The red value.
     *  - `g` (Number): The green value.
     *  - `b` (Number): The blue value.
     *  - `a` (Number): The alpha value.
     *
     * @param {Object} opts An object that will override the options fields.
     * @return {Object} An object containing:
     *
     *  - `pixel` (Object): The pixel object.
     *  - `value` (Number): The sum of the rgb values multiplied by `alpha`.
     *  - `toString` (Function): Returns the character.
     *  - `char` (String): The character value.
     *
     */
    asciify (pixel, opts) {
        opts = ul.merge(opts, this.options);
        pixel = new Pixel(pixel);

        if (typeof opts.pixels === "string") {
            opts.pixels = opts.pixels.split("");
        }

        let pxs = [].concat(opts.pixels);

        // Reverse pixels
        if (opts.reverse) {
            pxs.reverse();
        }

        let value = pixel.intensity();

        if (opts.px_background) {
            pixel = pixelBg(pixel, opts.px_background);
            value = pixel.intensity();
        } else if (opts.white_bg) {
            pixel = pixelWhiteBg(pixel);
            value = pixel.intensity();
        }

        let precision = 255 * 3 / (opts.pixels.length - 1)
          , strPx = null
          , rawChar = null
          ;

        rawChar = pxs[Math.round(value / precision)];

        // Colored
        if (!opts.colored) {
            opts.fg = opts.bg = false;
        }

        strPx = rawChar;

        if (opts.fg) {
            strPx = couleurs.fg(strPx, pixel.r, pixel.g, pixel.b);
        }

        if (opts.bg) {
            strPx = couleurs.bg(strPx, pixel.r, pixel.g, pixel.b);
        }

        return {
            toString: () => strPx
          , value: value
          , pixel: pixel
          , char: rawChar
          , ansii: strPx
        };
    }
}

/**
 * asciifyPixel
 * Ascifiies a pixel.
 *
 * @name asciifyPixel
 * @function
 * @param {Object} pixel An pixel object to pass to the `Asciifier` class.
 * @param {Object} options The options to pass to the `Asciifier` class.
 * @return {String} The asciified pixel.
 */
function asciifyPixel(pixel, options) {
    return new Asciifier(options).asciify(pixel).toString();
}

asciifyPixel.Asciifier = Asciifier;

module.exports = asciifyPixel;
<|endoftext|>


<commit_before>#!/usr/bin/env node --harmony --harmony_destructuring --harmony_default_parameters
'use strict';

require('./input.js')
	.parse()
	.then(input => {
		if (!input.cmd) {
			console.log('\n    Invalid option. Run:\n\t$ toggl --help');
			// require('./lib/interactive.js').start(input);
			return;
		}

		require('./lib/simple/').execute(input);
	})
	.catch(err => {
		console.error(err);
	});
<commit_msg>braken relative paths fixed<commit_after>#!/usr/bin/env node --harmony --harmony_destructuring --harmony_default_parameters
'use strict';

require('./input.js')
	.parse()
	.then(input => {
		if (!input.cmd) {
			console.log('\n    Invalid option. Run:\n\t$ toggl --help');
			// require('./interactive.js').start(input);
			return;
		}

		require('./simple/').execute(input);
	})
	.catch(err => {
		console.error(err);
	});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2012, Martin Borho.
 * Licensed under the Affero General Public License version 3 or later.
 * See LICENSE for details.
 */
exports.storage = require('./storage');
exports.connector = require('./connector');
exports.twitter = require('./twitter');
exports.superfeedr = require('./superfeedr-client');
exports.Poller = require('./poller').Poller;<commit_msg>added changes.js to lib<commit_after>/**
 * Copyright (c) 2012, Martin Borho.
 * Licensed under the Affero General Public License version 3 or later.
 * See LICENSE for details.
 */
exports.storage = require('./storage');
exports.connector = require('./connector');
exports.twitter = require('./twitter');
exports.superfeedr = require('./superfeedr-client');
exports.Poller = require('./poller').Poller;
exports.Changes = require('./changes').Changes;<|endoftext|>


<commit_before>/**
 * Simple client-side router
 * @module riot-route
 */

var observable = require('riot-observable')

var RE_ORIGIN = /^.+?\/+[^\/]+/,
  EVENT_LISTENER = 'EventListener',
  REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
  ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
  HAS_ATTRIBUTE = 'hasAttribute',
  REPLACE = 'replace',
  POPSTATE = 'popstate',
  TRIGGER = 'trigger',
  win = window,
  doc = document,
  loc = win.history.location || win.location, // see html5-history-api
  prot = Router.prototype, // to minify more
  clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
  started = false,
  central = observable(),
  base, current

/**
 * Router class
 */
function Router() {
  this.$ = []
  observable(this) // make it observable
  central.on('stop', this.s.bind(this))
  central.on('emit', this.e.bind(this))
}

/**
 * Default parser. You can replace it via router.parser method.
 * @param {string} path - current path (normalized)
 * @returns {array} array
 */
function parser(path) {
  return path.split(/[/?#]/)
}

/**
 * Default parser (second). You can replace it via router.parser method.
 * @param {string} path - current path (normalized)
 * @param {string} filter - filter string (normalized)
 * @returns {array} array
 */
function secondParser(path, filter) {
  var re = new RegExp('^' + filter[REPLACE](/\*/g, '(\\w+)')[REPLACE](/\.\./, '.*') + '$')
  if (args = path.match(re)) return args.slice(1)
}

function normalize(path) {
  return path[REPLACE](/^\/|\/$/, '')
}

/**
 * Get the part after domain name
 * @param {string} href - fullpath
 * @returns {string} path from root
 */
function getPathFromRoot(href) {
  return (href || loc.href)[REPLACE](RE_ORIGIN, '')
}

/**
 * Get the part after base
 * @param {string} href - fullpath
 * @returns {string} path from base
 */
function getPathFromBase(href) {
  return base[0] == '#'
    ? (href || loc.href).split(base)[1] || ''
    : getPathFromRoot(href)[REPLACE](base, '')
}

function emit(force) {
  var path = getPathFromBase()
  if (force || path != current) {
    central[TRIGGER]('emit', path)
    current = path
  }
}

function click(e) {
  if (
    e.which != 1 // not left click
    || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
    || e.defaultPrevented // or default prevented
  ) return

  var el = e.target
  while (el && el.nodeName != 'A') el = el.parentNode
  if (
    !el || el.nodeName != 'A' // not A tag
    || el[HAS_ATTRIBUTE]('download') // has download attr
    || !el[HAS_ATTRIBUTE]('href') // has no href attr
    || el.target && el.target != '_self' // another window or frame
    || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
  ) return

  if (el.href != loc.href) {
    if (el.href.split('#')[0] == loc.href.split('#')[0]) return // internal jump
    go(getPathFromBase(el.href), el.title || doc.title)
  }
  e.preventDefault()
}

/**
 * Go to the path
 * @param {string} path - destination path
 * @param {string} title - page title
 */
function go(path, title) {
  title = title || doc.title
  // browsers ignores the second parameter `title`
  history.pushState(null, title, base + path)
  // so we need to set it manually
  doc.title = title
  emit()
}

/**
 * Go to path or set action
 * a single string:                go there
 * two strings:                    go there with setting a title
 * a single function:              set an action on the default route
 * a string/RegExp and a function: set an action on the route
 * @param {(string|function)} first - path / action / filter
 * @param {(string|RegExp|function)} second - title / action
 */
prot.m = function(first, second) {
  if (first[0] && (!second || second[0])) go(first, second)
  else if (second) this.r(first, second)
  else this.r('@', first)
}

/**
 * Stop routing
 */
prot.s = function() {
  this.off('*')
}

/**
 * Emit
 * @param {string} path - path
 */
prot.e = function(path) {
  this.$.concat('@').some(function(filter) {
    var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
    if (args) {
      this[TRIGGER].apply(null, [filter].concat(args))
      return true // exit from loop
    }
  }, this)
}

/**
 * Register route
 * @param {string} filter - filter for matching to url
 * @param {function} action - action to register
 */
prot.r = function(filter, action) {
  if (filter != '@') this.$.push(filter)
  this.on(filter, action)
}

var mainRouter = new Router()
var route = mainRouter.m.bind(mainRouter)

/**
 * Create a sub router
 * @returns {function} the method of a new Router object
 */
route.create = function() {
  var newSubRouter = new Router()
  return newSubRouter.m.bind(newSubRouter)
}

/**
 * Set the base of url
 * @param {(str|RegExp)} arg - a new base or '#' or '#!'
 */
route.base = function(arg) {
  base = arg || '#'
  current = getPathFromBase() // recalculate current path
}

/** Exec routing right now **/
route.exec = function() {
  emit(true)
}

/**
 * Replace the default router to yours
 * @param {function} fn - your parser function
 * @param {function} fn2 - your secondParser function
 */
route.parser = function(fn, fn2) {
  if (fn) parser = fn
  if (fn2) secondParser = fn2
}

/**
 * Helper function to get url query as an object
 * @returns {object} parsed query
 */
route.query = function() {
  var q = {}
  loc.href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
  return q
}

/** Stop routing **/
route.stop = function () {
  if (started) {
    win[REMOVE_EVENT_LISTENER](POPSTATE, emit)
    doc[REMOVE_EVENT_LISTENER](clickEvent, click)
    central[TRIGGER]('stop')
    started = false
  }
}

/** Start routing **/
route.start = function () {
  if (!started) {
    win[ADD_EVENT_LISTENER](POPSTATE, emit)
    doc[ADD_EVENT_LISTENER](clickEvent, click)
    started = true
  }
}

/** Autostart the router **/
route.base()
route.start()

module.exports = route
<commit_msg>Make perser resetable<commit_after>/**
 * Simple client-side router
 * @module riot-route
 */

var observable = require('riot-observable')

var RE_ORIGIN = /^.+?\/+[^\/]+/,
  EVENT_LISTENER = 'EventListener',
  REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
  ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
  HAS_ATTRIBUTE = 'hasAttribute',
  REPLACE = 'replace',
  POPSTATE = 'popstate',
  TRIGGER = 'trigger',
  win = window,
  doc = document,
  loc = win.history.location || win.location, // see html5-history-api
  prot = Router.prototype, // to minify more
  clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
  started = false,
  central = observable(),
  base, current, parser, secondParser

/**
 * Default parser. You can replace it via router.parser method.
 * @param {string} path - current path (normalized)
 * @returns {array} array
 */
function DEFAULT_PARSER(path) {
  return path.split(/[/?#]/)
}

/**
 * Default parser (second). You can replace it via router.parser method.
 * @param {string} path - current path (normalized)
 * @param {string} filter - filter string (normalized)
 * @returns {array} array
 */
function DEFAULT_SECOND_PARSER(path, filter) {
  var re = new RegExp('^' + filter[REPLACE](/\*/g, '(\\w+)')[REPLACE](/\.\./, '.*') + '$')
  if (args = path.match(re)) return args.slice(1)
}

/**
 * Router class
 */
function Router() {
  this.$ = []
  observable(this) // make it observable
  central.on('stop', this.s.bind(this))
  central.on('emit', this.e.bind(this))
}

function normalize(path) {
  return path[REPLACE](/^\/|\/$/, '')
}

/**
 * Get the part after domain name
 * @param {string} href - fullpath
 * @returns {string} path from root
 */
function getPathFromRoot(href) {
  return (href || loc.href)[REPLACE](RE_ORIGIN, '')
}

/**
 * Get the part after base
 * @param {string} href - fullpath
 * @returns {string} path from base
 */
function getPathFromBase(href) {
  return base[0] == '#'
    ? (href || loc.href).split(base)[1] || ''
    : getPathFromRoot(href)[REPLACE](base, '')
}

function emit(force) {
  var path = getPathFromBase()
  if (force || path != current) {
    central[TRIGGER]('emit', path)
    current = path
  }
}

function click(e) {
  if (
    e.which != 1 // not left click
    || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
    || e.defaultPrevented // or default prevented
  ) return

  var el = e.target
  while (el && el.nodeName != 'A') el = el.parentNode
  if (
    !el || el.nodeName != 'A' // not A tag
    || el[HAS_ATTRIBUTE]('download') // has download attr
    || !el[HAS_ATTRIBUTE]('href') // has no href attr
    || el.target && el.target != '_self' // another window or frame
    || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
  ) return

  if (el.href != loc.href) {
    if (el.href.split('#')[0] == loc.href.split('#')[0]) return // internal jump
    go(getPathFromBase(el.href), el.title || doc.title)
  }
  e.preventDefault()
}

/**
 * Go to the path
 * @param {string} path - destination path
 * @param {string} title - page title
 */
function go(path, title) {
  title = title || doc.title
  // browsers ignores the second parameter `title`
  history.pushState(null, title, base + path)
  // so we need to set it manually
  doc.title = title
  emit()
}

/**
 * Go to path or set action
 * a single string:                go there
 * two strings:                    go there with setting a title
 * a single function:              set an action on the default route
 * a string/RegExp and a function: set an action on the route
 * @param {(string|function)} first - path / action / filter
 * @param {(string|RegExp|function)} second - title / action
 */
prot.m = function(first, second) {
  if (first[0] && (!second || second[0])) go(first, second)
  else if (second) this.r(first, second)
  else this.r('@', first)
}

/**
 * Stop routing
 */
prot.s = function() {
  this.off('*')
  this.$ = []
}

/**
 * Emit
 * @param {string} path - path
 */
prot.e = function(path) {
  this.$.concat('@').some(function(filter) {
    var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
    if (args) {
      this[TRIGGER].apply(null, [filter].concat(args))
      return true // exit from loop
    }
  }, this)
}

/**
 * Register route
 * @param {string} filter - filter for matching to url
 * @param {function} action - action to register
 */
prot.r = function(filter, action) {
  if (filter != '@') this.$.push(filter)
  this.on(filter, action)
}

var mainRouter = new Router()
var route = mainRouter.m.bind(mainRouter)

/**
 * Create a sub router
 * @returns {function} the method of a new Router object
 */
route.create = function() {
  var newSubRouter = new Router()
  // stop only this sub-router
  newSubRouter.m.stop = newSubRouter.s.bind(newSubRouter)
  // return sub-router's main method
  return newSubRouter.m.bind(newSubRouter)
}

/**
 * Set the base of url
 * @param {(str|RegExp)} arg - a new base or '#' or '#!'
 */
route.base = function(arg) {
  base = arg || '#'
  current = getPathFromBase() // recalculate current path
}

/** Exec routing right now **/
route.exec = function() {
  emit(true)
}

/**
 * Replace the default router to yours
 * @param {function} fn - your parser function
 * @param {function} fn2 - your secondParser function
 */
route.parser = function(fn, fn2) {
  if (!fn && !fn2) {
    // reset parser for testing...
    parser = DEFAULT_PARSER
    secondParser = DEFAULT_SECOND_PARSER
  }
  if (fn) parser = fn
  if (fn2) secondParser = fn2
}

/**
 * Helper function to get url query as an object
 * @returns {object} parsed query
 */
route.query = function() {
  var q = {}
  loc.href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
  return q
}

/** Stop routing **/
route.stop = function () {
  if (started) {
    win[REMOVE_EVENT_LISTENER](POPSTATE, emit)
    doc[REMOVE_EVENT_LISTENER](clickEvent, click)
    central[TRIGGER]('stop')
    started = false
  }
}

/** Start routing **/
route.start = function () {
  if (!started) {
    win[ADD_EVENT_LISTENER](POPSTATE, emit)
    doc[ADD_EVENT_LISTENER](clickEvent, click)
    started = true
  }
}

/** Autostart the router **/
route.base()
route.parser()
route.start()

module.exports = route
<|endoftext|>


<commit_before>var _ = require('lodash');
var derbyServices = require('derby-services');
var DefaultCollection = require('./DefaultCollection');
var DefaultItem = require('./DefaultItem');
var rpc = require('./rpc');

// TODO: Split up into one separate file for the server and one for the client for faster loading and less useless code transmitted to the client?
// See: https://github.com/derbyparty/derby-faq/tree/master/en#how-to-require-a-module-that-will-run-only-on-the-server-of-a-derby-application
module.exports = function(derby) {
  var racer = derby;
  var Model = racer.Model;
  var options = {
    schemas: {},
    formats: {},
    validators: {},
    skipNonExisting: true
  };
  racer._models = {};

  Model.INITS.push(function (model) {
    // Initiate racer-schema ASAP model is initiated as we then know no more models can be added
    racer.use(require('racer-schema'), options);
  });

  derby.use(rpc.plugin);
  derby.use(derbyServices);

  /**
   * Adds model classes to Racer
   *
   * @param {Object} args
   * @param {String} args.name - the name/path to the collection on which to bind to
   * @param {Class} [args.Collection] - the model class to be used for the collection
   * @param {Class} [args.Item] - the model class to be used for each item in the collection
   * @param {Object} [args.schema] - a (model) schema for each item in the collection
   * @param {Object} [args.formats] - formats to be used for schema
   * @param {Object} [args.validators] - validators to be used for schema
   * @param {Array} [args.hooks] - a list of hooks to bind to Share. Note! Not yet implemented!
   */
  racer.model = function (args) {
    if(!args || !args.name) throw new Error('Name/path of collection must be passed');

    racer._models[args.name] = args;

    // Use racer-schema for underlying schema stuff
    // TODO: Possibly, do a transformation from a (imo) nicer format for schema stuff
    // TODO: Implement schema refs, basically not using racer-schema
    if(args.schema) options.schemas[args.name] = args.schema;
    if(args.formats) racer.util.mergeInto(options.formats, args.formats);
    if(args.validators) racer.util.mergeInto(options.validators, args.validators);

    // Look into how to do this best.
    if(args.hooks) throw new Error('Hooks are not yet implemented!');
  };

  // Overwrite scope, which is used by all other fns related to path
  // Preserve old scope fn for re-use internally
  Model.prototype._scope = Model.prototype.scope;
  Model.prototype.scope = function(path) {
    var segments = this.__splitPath(path);

    // No segments - no point in parsing further
    if(!segments.length) return this._scope(path);

    // Dereference to simplify checks (if refs are present)
    var dereferencedSegments = this._dereference(segments, true);
    var dereferenced = dereferencedSegments.join('.');

    // Get Models
    var Model = racer._models[segments[0]];
    var dereferencedModel = racer._models[dereferencedSegments[0]];

    // Only allow collection level models straight up from non-dereferenced Models,
    // since otherwise we're either moving through a ref to a collection, which is just pointless (or is it?)
    // or we're moving through a refList on the refList level which doesn't point to the whole collection but only a subset of it - and in those scenarios we don't want a Collection Model to be returned
    // TODO: Revise assumption above, it seems that in some scenarios you'd want to create regular references to a whole collection like if you'd want to reference a certain global collection to a component's view model so that the component could render all of it (though, then it might be a special case of above where it is a "subset" that just happens to be all of the items of the original collection)
    if(Model && segments.length === 1 && Model.Collection) {
      // We are at a collection level, return Collection instance
      // TODO: Add support for refList collections? See above
      return this._createCollection(path, Model.Collection);
    } else if(dereferencedModel && dereferencedSegments.length === 2 && dereferencedModel.Item) {
      // We are at an item level, return Item instance
      return this._createItem(dereferenced, dereferencedModel.Item);
    }

    // Always fallback on original scope/regular Child Model
    return this._scope(path);
  };

  /**
   * Helper fn for splitting a full path
   */
  Model.prototype.__splitPath = function(path) {
    return (path && path.split('.')) || [];
  };

  /**
   * Helper fn for creating a collection for a specific path
   */
  Model.prototype._createCollection = function(path, constructor) {
    function Collection() {}

    constructor.prototype = rpc.add(this, path, constructor.prototype);

    Collection.prototype = _.assign(this._scope(path), DefaultCollection.prototype, constructor.prototype);

    return new Collection();
  }

  Model.prototype._createItem = function(path, constructor) {
    function Item() {}

    constructor.prototype = rpc.add(this, path, constructor.prototype);

    Item.prototype = _.assign(this._scope(path), DefaultItem.prototype, constructor.prototype);

    return new Item();
  }

  // Taken straight from Racer since it's not possible to access it externally
  function ChildModel(model) {
    // Shared properties should be accessed via the root. This makes inheritance
    // cheap and easily extensible
    this.root = model.root;

    // EventEmitter methods access these properties directly, so they must be
    // inherited manually instead of via the root
    this._events = model._events;
    this._maxListeners = model._maxListeners;

    // Properties specific to a child instance
    this._context = model._context;
    this._at = model._at;
    this._pass = model._pass;
    this._silent = model._silent;
    this._eventContext = model._eventContext;
  }
  ChildModel.prototype = new Model();
};
<commit_msg>Remove unneeded code<commit_after>var _ = require('lodash');
var derbyServices = require('derby-services');
var DefaultCollection = require('./DefaultCollection');
var DefaultItem = require('./DefaultItem');
var rpc = require('./rpc');

// TODO: Split up into one separate file for the server and one for the client for faster loading and less useless code transmitted to the client?
// See: https://github.com/derbyparty/derby-faq/tree/master/en#how-to-require-a-module-that-will-run-only-on-the-server-of-a-derby-application
module.exports = function(derby) {
  var racer = derby;
  var Model = racer.Model;
  var options = {
    schemas: {},
    formats: {},
    validators: {},
    skipNonExisting: true
  };
  racer._models = {};

  Model.INITS.push(function (model) {
    // Initiate racer-schema ASAP model is initiated as we then know no more models can be added
    racer.use(require('racer-schema'), options);
  });

  derby.use(rpc.plugin);
  derby.use(derbyServices);

  /**
   * Adds model classes to Racer
   *
   * @param {Object} args
   * @param {String} args.name - the name/path to the collection on which to bind to
   * @param {Class} [args.Collection] - the model class to be used for the collection
   * @param {Class} [args.Item] - the model class to be used for each item in the collection
   * @param {Object} [args.schema] - a (model) schema for each item in the collection
   * @param {Object} [args.formats] - formats to be used for schema
   * @param {Object} [args.validators] - validators to be used for schema
   * @param {Array} [args.hooks] - a list of hooks to bind to Share. Note! Not yet implemented!
   */
  racer.model = function (args) {
    if(!args || !args.name) throw new Error('Name/path of collection must be passed');

    racer._models[args.name] = args;

    // Use racer-schema for underlying schema stuff
    // TODO: Possibly, do a transformation from a (imo) nicer format for schema stuff
    // TODO: Implement schema refs, basically not using racer-schema
    if(args.schema) options.schemas[args.name] = args.schema;
    if(args.formats) racer.util.mergeInto(options.formats, args.formats);
    if(args.validators) racer.util.mergeInto(options.validators, args.validators);

    // Look into how to do this best.
    if(args.hooks) throw new Error('Hooks are not yet implemented!');
  };

  // Overwrite scope, which is used by all other fns related to path
  // Preserve old scope fn for re-use internally
  Model.prototype._scope = Model.prototype.scope;
  Model.prototype.scope = function(path) {
    var segments = this.__splitPath(path);

    // No segments - no point in parsing further
    if(!segments.length) return this._scope(path);

    // Dereference to simplify checks (if refs are present)
    var dereferencedSegments = this._dereference(segments, true);
    var dereferenced = dereferencedSegments.join('.');

    // Get Models
    var Model = racer._models[segments[0]];
    var dereferencedModel = racer._models[dereferencedSegments[0]];

    // Only allow collection level models straight up from non-dereferenced Models,
    // since otherwise we're either moving through a ref to a collection, which is just pointless (or is it?)
    // or we're moving through a refList on the refList level which doesn't point to the whole collection but only a subset of it - and in those scenarios we don't want a Collection Model to be returned
    // TODO: Revise assumption above, it seems that in some scenarios you'd want to create regular references to a whole collection like if you'd want to reference a certain global collection to a component's view model so that the component could render all of it (though, then it might be a special case of above where it is a "subset" that just happens to be all of the items of the original collection)
    if(Model && segments.length === 1 && Model.Collection) {
      // We are at a collection level, return Collection instance
      // TODO: Add support for refList collections? See above
      return this._createCollection(path, Model.Collection);
    } else if(dereferencedModel && dereferencedSegments.length === 2 && dereferencedModel.Item) {
      // We are at an item level, return Item instance
      return this._createItem(dereferenced, dereferencedModel.Item);
    }

    // Always fallback on original scope/regular Child Model
    return this._scope(path);
  };

  /**
   * Helper fn for splitting a full path
   */
  Model.prototype.__splitPath = function(path) {
    return (path && path.split('.')) || [];
  };

  /**
   * Helper fn for creating a collection for a specific path
   */
  Model.prototype._createCollection = function(path, constructor) {
    function Collection() {}

    constructor.prototype = rpc.add(this, path, constructor.prototype);

    Collection.prototype = _.assign(this._scope(path), DefaultCollection.prototype, constructor.prototype);

    return new Collection();
  }

  Model.prototype._createItem = function(path, constructor) {
    function Item() {}

    constructor.prototype = rpc.add(this, path, constructor.prototype);

    Item.prototype = _.assign(this._scope(path), DefaultItem.prototype, constructor.prototype);

    return new Item();
  }
};
<|endoftext|>