<commit_before>/*
 * Copyright 2013 Eduardo Lundgren, All Rights Reserved.
 *
 * Code licensed under the BSD License:
 * https://github.com/eduardolundgren/blob/master/LICENSE.md
 *
 * @author Eduardo Lundgren <eduardolundgren@gmail.com>
 */

// -- Requires -----------------------------------------------------------------
var async = require('async'),
    base = require('../base'),
    clc = require('cli-color'),
    fs = require('fs'),
    git = require('../git'),
    open = require('open'),
    path = require('path'),
    logger = require('../logger'),
    stripNewLines,
    issueImpl = require('./issue').Impl;

// -- Constructor --------------------------------------------------------------
function PullRequest(options) {
    this.options = options;

    if (!options.repo && !options.all) {
        logger.error('You must specify a Git repository to run this command');
    }

    this.issue = new issueImpl(options);
}

// -- Constants ----------------------------------------------------------------
PullRequest.DETAILS = {
    alias: 'pr',
    description: 'Provides a set of util commands to work with Pull Requests.',
    options: {
        'all'     : Boolean,
        'branch'  : String,
        'close'   : Boolean,
        'comment' : String,
        'detailed': Boolean,
        'fetch'   : Boolean,
        'fwd'     : String,
        'list'    : Boolean,
        'merge'   : Boolean,
        'number'  : Number,
        'open'    : Boolean,
        'rebase'  : Boolean,
        'remote'  : String,
        'repo'    : String,
        'state'   : [ 'open', 'closed' ],
        'submit'  : String,
        'title'   : String,
        'user'    : String
    },
    shorthands: {
        'a': [ '--all' ],
        'b': [ '--branch' ],
        'C': [ '--close' ],
        'c': [ '--comment' ],
        'd': [ '--detailed' ],
        'f': [ '--fetch' ],
        'l': [ '--list' ],
        'M': [ '--merge' ],
        'n': [ '--number' ],
        'o': [ '--open' ],
        'R': [ '--rebase' ],
        'r': [ '--repo' ],
        'S': [ '--state' ],
        's': [ '--submit' ],
        't': [ '--title' ],
        'u': [ '--user' ]
    },
    payload: function(payload, options) {
        if (payload[0]) {
            options.fetch = true;
        }
        else {
            options.list = true;
        }
    }
};

PullRequest.FETCH_TYPE_CHECKOUT = 'checkout';

PullRequest.FETCH_TYPE_MERGE = 'merge';

PullRequest.FETCH_TYPE_REBASE = 'rebase';

PullRequest.FETCH_TYPE_SILENT = 'silent';

PullRequest.STATE_CLOSED = 'closed';

PullRequest.STATE_OPEN = 'open';

// -- Commands -----------------------------------------------------------------
PullRequest.prototype.options = null;

PullRequest.prototype.issues = null;

PullRequest.prototype.run = function() {
    var instance = this,
        options = instance.options,
        config = base.getGlobalConfig(),
        fetchType;

    options.state = options.state || PullRequest.STATE_OPEN;
    options.number = options.number || instance.getPullRequestNumberFromBranch_();
    options.pullBranch = instance.getBranchNameFromPullNumber_(options.number);

    if (!options.list) {
        options.branch = options.branch || config.default_branch;
    }

    if (options.close) {
        logger.logTemplate('{{prefix}} [info] Closing pull request {{greenBright "#" options.number}}', {
            options: options
        });

        instance.close(function(err) {
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{link}}', { options: options }));
        });
    }

    if (options.comment) {
        logger.logTemplate('{{prefix}} [info] Adding comment on pull request {{greenBright "#" options.number}}', {
            options: options
        });

        instance.issue.comment(function(err) {
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{link}}', { options: options }));
        });
    }

    if (options.fetch) {
        fetchType = PullRequest.FETCH_TYPE_CHECKOUT;

        if (options.merge) {
            fetchType = PullRequest.FETCH_TYPE_MERGE;
        }
        else if (options.rebase) {
            fetchType = PullRequest.FETCH_TYPE_REBASE;
        }

        logger.logTemplateFile('pr-fetch.handlebars', {
            currentBranch: options.currentBranch,
            options: options
        });

        instance.fetch(fetchType, logger.defaultCallback);
    }
    else if (options.merge || options.rebase) {
        logger.logTemplateFile('pr-merge.handlebars', {
            options: options
        });

        instance.merge(logger.defaultCallback);
    }

    if (options.fwd) {
        logger.logTemplate('{{prefix}} [info] Forwarding pull request {{greenBright "#" options.number}} to {{{magentaBright "@" options.fwd}}}', {
            options: options
        });

        instance.forward(function(err, pull) {
            if (pull) {
                options.forwardedPull = pull.number;
            }
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{forwardedLink}}', { options: options }));
        });
    }

    if (options.list) {
        if (options.all) {
            logger.logTemplate('{{prefix}} [info] Listing {{options.state}} pull requests for {{greenBright options.user}}', {
                options: options
            });

            instance.listFromAllRepositories(function(err) {
                logger.defaultCallback(err, null, false);
            });
        }
        else {
            logger.logTemplate('{{prefix}} [info] Listing {{options.state}} pull requests on {{greenBright options.user "/" options.repo}}', {
                options: options
            });

            instance.list(options.user, options.repo, function(err) {
                logger.defaultCallback(err, null, false);
            });
        }
    }

    if (options.open) {
        logger.logTemplate('{{prefix}} [info] Opening pull request {{greenBright "#" options.number}}', {
            options: options
        });

        instance.open(function(err) {
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{link}}', { options: options }));
        });
    }

    if (options.submit) {
        logger.logTemplate('{{prefix}} [info] Submitting pull request to {{{magentaBright "@" options.submit}}}', {
            options: options
        });

        instance.submit(options.submit, function(err, pull) {
            if (pull) {
                options.submittedPull = pull.number;

                if (config.open_pull_request_in_browser) {
                    open(pull.html_url);
                }
            }
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{submittedLink}}', { options: options }));
        });
    }
};

PullRequest.prototype.close = function(opt_callback) {
    var instance = this,
        options = instance.options,
        operations,
        pull;

    operations = [
        function(callback) {
            instance.getPullRequest_(function(err, data) {
                if (!err) {
                    pull = data;
                }
                callback(err);
            });
        },
        function(callback) {
            instance.updatePullRequest_(
                pull.title, pull.body, PullRequest.STATE_CLOSED, callback);
        },
        function(callback) {
            if (options.pullBranch === options.currentBranch) {
                git.checkout(options.branch, null, callback);
            }
            else {
                callback();
            }
        },
        function(callback) {
            if (options.pullBranch) {
                git.exec('branch', [ '-D', options.pullBranch ]);
            }

            callback();
        }
    ];

    async.series(operations, opt_callback);
};

PullRequest.prototype.checkPullRequestIntegrity_ = function(originalError, user, opt_callback) {
    var instance = this,
        options = instance.options,
        payload,
        pull;

    payload = {
        user: user,
        repo: options.repo,
        state: PullRequest.STATE_OPEN
    };

    base.github.pullRequests.getAll(payload, function(err, pulls) {
        if (!err) {
            pulls.forEach(function(data) {
                if ((data.base.ref === options.branch) &&
                    (data.head.ref === options.currentBranch) &&
                    (data.base.user.login === user) &&
                    (data.head.user.login === options.user)) {

                    pull = data;
                    originalError = null;
                    return;
                }
            });
        }

        opt_callback && opt_callback(originalError, pull);
    });
};

PullRequest.prototype.fetch = function(opt_type, opt_callback) {
    var instance = this,
        options = instance.options,
        headBranch,
        operations,
        pull,
        repoUrl;

    operations = [
        function(callback) {
            instance.getPullRequest_(function(err, data) {
                if (!err) {
                    pull = data;
                    headBranch = pull.head.ref;
                    repoUrl = pull.head.repo.ssh_url;
                }
                callback(err);
            });
        },
        function(callback) {
            git.exec('fetch', [ repoUrl, headBranch + ':' + options.pullBranch ], callback);
        },
        function(callback) {
            if (opt_type === PullRequest.FETCH_TYPE_REBASE) {
                git.merge(options.pullBranch, true, true, callback);
            }
            else if (opt_type === PullRequest.FETCH_TYPE_MERGE) {
                git.merge(options.pullBranch, false, true, callback);
            }
            else if (opt_type === PullRequest.FETCH_TYPE_CHECKOUT) {
                git.checkout(options.pullBranch, null, callback);
            }
            else {
                callback();
            }
        }
    ];

    async.series(operations, function(err) {
        opt_callback && opt_callback(err, pull);
    });
};

PullRequest.prototype.forward = function(opt_callback) {
    var instance = this,
        options = instance.options,
        config = base.getGlobalConfig(),
        operations,
        pull;

    operations = [
        function(callback) {
            instance.fetch(PullRequest.FETCH_TYPE_SILENT, callback);
        },
        function(callback) {
            instance.submit(options.fwd, function(err, data) {
                pull = data;
                callback(err);
            });
        },
        function(callback) {
            instance.close(callback);
        }
    ];

    async.series(operations, function(err) {
        opt_callback && opt_callback(err, pull);
    });
};

PullRequest.prototype.getPullRequest_ = function(opt_callback) {
    var instance = this,
        options = instance.options,
        payload;

    payload = {
        number: options.number,
        repo: options.repo,
        user: options.user
    };

    base.github.pullRequests.get(payload, opt_callback);
};

PullRequest.prototype.getBranchNameFromPullNumber_ = function(number) {
    if (number) {
        return base.getGlobalConfig().pull_branch_name_prefix + number;
    }
};

PullRequest.prototype.getPullRequestNumberFromBranch_ = function() {
    var instance = this,
        config = base.getGlobalConfig(),
        options = instance.options,
        prefix;

    prefix = config.pull_branch_name_prefix;

    if (options.currentBranch && options.currentBranch.indexOf(prefix) > -1) {
        return options.currentBranch.replace(prefix, '');
    }
};

PullRequest.prototype.getPullsTemplateJson_ = function(pulls, opt_callback) {
    var instance = this,
        options = instance.options,
        branch,
        branches,
        json;

    branches = {};
    json = {
        branches: []
    };

    pulls.forEach(function(pull) {
        branch = pull.base.ref;

        if (!options.branch || options.branch === branch) {
            branches[branch] = branches[branch] || [];
            branches[branch].push(pull);
        }
    });

    Object.keys(branches).forEach(function(branch) {
        json.branches.push({
            name: branch,
            pulls: branches[branch],
            total: branches[branch].length
        });
    });

    opt_callback && opt_callback(null, json);
};

PullRequest.prototype.list = function(user, repo, opt_callback) {
    var instance = this,
        options = instance.options,
        json,
        operations,
        payload,
        pulls;

    payload = {
        repo: repo,
        state: options.state,
        user: user
    };

    operations = [
        function(callback) {
            base.github.pullRequests.getAll(payload, function(err, data) {
                if (!err) {
                    pulls = data;
                }
                callback(err);
            });
        },
        function(callback) {
            instance.getPullsTemplateJson_(pulls, function(err, data) {
                if (!err) {
                    json = data;
                }
                callback(err);
            });
        }
    ];

    async.series(operations, function(err) {
        if (!err && pulls.length) {
            json.repo = repo;
            json.user = options.user;
            json.detailed = options.detailed;
            logger.logTemplateFile('pr.handlebars', json);
        }

        opt_callback && opt_callback(err);
    });
};

PullRequest.prototype.listFromAllRepositories = function() {
    var instance = this;

    base.github.repos.getAll({ type: 'all' }, function(err, repositories) {
        if (err) {
            opt_callback && opt_callback(err);
        }
        else {
            repositories.forEach(function(repository) {
                instance.list(repository.owner.login, repository.name);
            });
        }
    });
};

PullRequest.prototype.merge = function(opt_callback) {
    var instance = this,
        options = instance.options,
        operations;

    operations = [
        function(callback) {
            git.checkout(options.branch, null, callback);
        },
        function(callback) {
            git.merge(options.pullBranch, options.rebase, false, function(err, data) {
                console.log(data);
                callback(err);
            });
        },
        function(callback) {
            instance.close(callback);
        }
    ];

    async.series(operations, opt_callback);
};

PullRequest.prototype.open = function(opt_callback) {
    var instance = this;

    instance.getPullRequest_(function(err, pull) {
        if (err) {
            opt_callback && opt_callback(err);
        }
        else {
            instance.updatePullRequest_(
                pull.title, pull.body, PullRequest.STATE_OPEN, opt_callback);
        }
    });
};

PullRequest.prototype.submit = function(user, opt_callback) {
    var instance = this,
        options = instance.options,
        operations,
        pullBranch;

    pullBranch = options.pullBranch || options.currentBranch;

    operations = [
        function(callback) {
            git.exec('push', [ 'origin', pullBranch ], callback);
        },
        function(callback) {
            base.github.pullRequests.create({
                base: options.branch,
                body: '',
                head: options.user + ':' + pullBranch,
                repo: options.repo,
                title: options.title || pullBranch,
                user: user
            }, callback);
        }
    ];

    async.series(operations, function(err, results) {
        if (err) {
            instance.checkPullRequestIntegrity_(err, user, opt_callback);
        }
        else {
            opt_callback && opt_callback(err, results[1]);
        }
    });
};

PullRequest.prototype.updatePullRequest_ = function(title, opt_body, state, opt_callback) {
    var instance = this,
        options = instance.options,
        payload;

    if (opt_body) {
        opt_body = logger.applyReplacements(opt_body);
    }

    payload = {
        body: opt_body,
        number: options.number,
        repo: options.repo,
        state: state,
        title: title,
        user: options.user
    };

    base.github.pullRequests.update(payload, opt_callback);
};

exports.Impl = PullRequest;<commit_msg>Pass callback on pull request list from all repositories<commit_after>/*
 * Copyright 2013 Eduardo Lundgren, All Rights Reserved.
 *
 * Code licensed under the BSD License:
 * https://github.com/eduardolundgren/blob/master/LICENSE.md
 *
 * @author Eduardo Lundgren <eduardolundgren@gmail.com>
 */

// -- Requires -----------------------------------------------------------------
var async = require('async'),
    base = require('../base'),
    clc = require('cli-color'),
    fs = require('fs'),
    git = require('../git'),
    open = require('open'),
    path = require('path'),
    logger = require('../logger'),
    stripNewLines,
    issueImpl = require('./issue').Impl;

// -- Constructor --------------------------------------------------------------
function PullRequest(options) {
    this.options = options;

    if (!options.repo && !options.all) {
        logger.error('You must specify a Git repository to run this command');
    }

    this.issue = new issueImpl(options);
}

// -- Constants ----------------------------------------------------------------
PullRequest.DETAILS = {
    alias: 'pr',
    description: 'Provides a set of util commands to work with Pull Requests.',
    options: {
        'all'     : Boolean,
        'branch'  : String,
        'close'   : Boolean,
        'comment' : String,
        'detailed': Boolean,
        'fetch'   : Boolean,
        'fwd'     : String,
        'list'    : Boolean,
        'merge'   : Boolean,
        'number'  : Number,
        'open'    : Boolean,
        'rebase'  : Boolean,
        'remote'  : String,
        'repo'    : String,
        'state'   : [ 'open', 'closed' ],
        'submit'  : String,
        'title'   : String,
        'user'    : String
    },
    shorthands: {
        'a': [ '--all' ],
        'b': [ '--branch' ],
        'C': [ '--close' ],
        'c': [ '--comment' ],
        'd': [ '--detailed' ],
        'f': [ '--fetch' ],
        'l': [ '--list' ],
        'M': [ '--merge' ],
        'n': [ '--number' ],
        'o': [ '--open' ],
        'R': [ '--rebase' ],
        'r': [ '--repo' ],
        'S': [ '--state' ],
        's': [ '--submit' ],
        't': [ '--title' ],
        'u': [ '--user' ]
    },
    payload: function(payload, options) {
        if (payload[0]) {
            options.fetch = true;
        }
        else {
            options.list = true;
        }
    }
};

PullRequest.FETCH_TYPE_CHECKOUT = 'checkout';

PullRequest.FETCH_TYPE_MERGE = 'merge';

PullRequest.FETCH_TYPE_REBASE = 'rebase';

PullRequest.FETCH_TYPE_SILENT = 'silent';

PullRequest.STATE_CLOSED = 'closed';

PullRequest.STATE_OPEN = 'open';

// -- Commands -----------------------------------------------------------------
PullRequest.prototype.options = null;

PullRequest.prototype.issues = null;

PullRequest.prototype.run = function() {
    var instance = this,
        options = instance.options,
        config = base.getGlobalConfig(),
        fetchType;

    options.state = options.state || PullRequest.STATE_OPEN;
    options.number = options.number || instance.getPullRequestNumberFromBranch_();
    options.pullBranch = instance.getBranchNameFromPullNumber_(options.number);

    if (!options.list) {
        options.branch = options.branch || config.default_branch;
    }

    if (options.close) {
        logger.logTemplate('{{prefix}} [info] Closing pull request {{greenBright "#" options.number}}', {
            options: options
        });

        instance.close(function(err) {
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{link}}', { options: options }));
        });
    }

    if (options.comment) {
        logger.logTemplate('{{prefix}} [info] Adding comment on pull request {{greenBright "#" options.number}}', {
            options: options
        });

        instance.issue.comment(function(err) {
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{link}}', { options: options }));
        });
    }

    if (options.fetch) {
        fetchType = PullRequest.FETCH_TYPE_CHECKOUT;

        if (options.merge) {
            fetchType = PullRequest.FETCH_TYPE_MERGE;
        }
        else if (options.rebase) {
            fetchType = PullRequest.FETCH_TYPE_REBASE;
        }

        logger.logTemplateFile('pr-fetch.handlebars', {
            currentBranch: options.currentBranch,
            options: options
        });

        instance.fetch(fetchType, logger.defaultCallback);
    }
    else if (options.merge || options.rebase) {
        logger.logTemplateFile('pr-merge.handlebars', {
            options: options
        });

        instance.merge(logger.defaultCallback);
    }

    if (options.fwd) {
        logger.logTemplate('{{prefix}} [info] Forwarding pull request {{greenBright "#" options.number}} to {{{magentaBright "@" options.fwd}}}', {
            options: options
        });

        instance.forward(function(err, pull) {
            if (pull) {
                options.forwardedPull = pull.number;
            }
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{forwardedLink}}', { options: options }));
        });
    }

    if (options.list) {
        if (options.all) {
            logger.logTemplate('{{prefix}} [info] Listing {{options.state}} pull requests for {{greenBright options.user}}', {
                options: options
            });

            instance.listFromAllRepositories(function(err) {
                logger.defaultCallback(err, null, false);
            });
        }
        else {
            logger.logTemplate('{{prefix}} [info] Listing {{options.state}} pull requests on {{greenBright options.user "/" options.repo}}', {
                options: options
            });

            instance.list(options.user, options.repo, function(err) {
                logger.defaultCallback(err, null, false);
            });
        }
    }

    if (options.open) {
        logger.logTemplate('{{prefix}} [info] Opening pull request {{greenBright "#" options.number}}', {
            options: options
        });

        instance.open(function(err) {
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{link}}', { options: options }));
        });
    }

    if (options.submit) {
        logger.logTemplate('{{prefix}} [info] Submitting pull request to {{{magentaBright "@" options.submit}}}', {
            options: options
        });

        instance.submit(options.submit, function(err, pull) {
            if (pull) {
                options.submittedPull = pull.number;

                if (config.open_pull_request_in_browser) {
                    open(pull.html_url);
                }
            }
            logger.defaultCallback(
                err, null, logger.compileTemplate('{{submittedLink}}', { options: options }));
        });
    }
};

PullRequest.prototype.close = function(opt_callback) {
    var instance = this,
        options = instance.options,
        operations,
        pull;

    operations = [
        function(callback) {
            instance.getPullRequest_(function(err, data) {
                if (!err) {
                    pull = data;
                }
                callback(err);
            });
        },
        function(callback) {
            instance.updatePullRequest_(
                pull.title, pull.body, PullRequest.STATE_CLOSED, callback);
        },
        function(callback) {
            if (options.pullBranch === options.currentBranch) {
                git.checkout(options.branch, null, callback);
            }
            else {
                callback();
            }
        },
        function(callback) {
            if (options.pullBranch) {
                git.exec('branch', [ '-D', options.pullBranch ]);
            }

            callback();
        }
    ];

    async.series(operations, opt_callback);
};

PullRequest.prototype.checkPullRequestIntegrity_ = function(originalError, user, opt_callback) {
    var instance = this,
        options = instance.options,
        payload,
        pull;

    payload = {
        user: user,
        repo: options.repo,
        state: PullRequest.STATE_OPEN
    };

    base.github.pullRequests.getAll(payload, function(err, pulls) {
        if (!err) {
            pulls.forEach(function(data) {
                if ((data.base.ref === options.branch) &&
                    (data.head.ref === options.currentBranch) &&
                    (data.base.user.login === user) &&
                    (data.head.user.login === options.user)) {

                    pull = data;
                    originalError = null;
                    return;
                }
            });
        }

        opt_callback && opt_callback(originalError, pull);
    });
};

PullRequest.prototype.fetch = function(opt_type, opt_callback) {
    var instance = this,
        options = instance.options,
        headBranch,
        operations,
        pull,
        repoUrl;

    operations = [
        function(callback) {
            instance.getPullRequest_(function(err, data) {
                if (!err) {
                    pull = data;
                    headBranch = pull.head.ref;
                    repoUrl = pull.head.repo.ssh_url;
                }
                callback(err);
            });
        },
        function(callback) {
            git.exec('fetch', [ repoUrl, headBranch + ':' + options.pullBranch ], callback);
        },
        function(callback) {
            if (opt_type === PullRequest.FETCH_TYPE_REBASE) {
                git.merge(options.pullBranch, true, true, callback);
            }
            else if (opt_type === PullRequest.FETCH_TYPE_MERGE) {
                git.merge(options.pullBranch, false, true, callback);
            }
            else if (opt_type === PullRequest.FETCH_TYPE_CHECKOUT) {
                git.checkout(options.pullBranch, null, callback);
            }
            else {
                callback();
            }
        }
    ];

    async.series(operations, function(err) {
        opt_callback && opt_callback(err, pull);
    });
};

PullRequest.prototype.forward = function(opt_callback) {
    var instance = this,
        options = instance.options,
        config = base.getGlobalConfig(),
        operations,
        pull;

    operations = [
        function(callback) {
            instance.fetch(PullRequest.FETCH_TYPE_SILENT, callback);
        },
        function(callback) {
            instance.submit(options.fwd, function(err, data) {
                pull = data;
                callback(err);
            });
        },
        function(callback) {
            instance.close(callback);
        }
    ];

    async.series(operations, function(err) {
        opt_callback && opt_callback(err, pull);
    });
};

PullRequest.prototype.getPullRequest_ = function(opt_callback) {
    var instance = this,
        options = instance.options,
        payload;

    payload = {
        number: options.number,
        repo: options.repo,
        user: options.user
    };

    base.github.pullRequests.get(payload, opt_callback);
};

PullRequest.prototype.getBranchNameFromPullNumber_ = function(number) {
    if (number) {
        return base.getGlobalConfig().pull_branch_name_prefix + number;
    }
};

PullRequest.prototype.getPullRequestNumberFromBranch_ = function() {
    var instance = this,
        config = base.getGlobalConfig(),
        options = instance.options,
        prefix;

    prefix = config.pull_branch_name_prefix;

    if (options.currentBranch && options.currentBranch.indexOf(prefix) > -1) {
        return options.currentBranch.replace(prefix, '');
    }
};

PullRequest.prototype.getPullsTemplateJson_ = function(pulls, opt_callback) {
    var instance = this,
        options = instance.options,
        branch,
        branches,
        json;

    branches = {};
    json = {
        branches: []
    };

    pulls.forEach(function(pull) {
        branch = pull.base.ref;

        if (!options.branch || options.branch === branch) {
            branches[branch] = branches[branch] || [];
            branches[branch].push(pull);
        }
    });

    Object.keys(branches).forEach(function(branch) {
        json.branches.push({
            name: branch,
            pulls: branches[branch],
            total: branches[branch].length
        });
    });

    opt_callback && opt_callback(null, json);
};

PullRequest.prototype.list = function(user, repo, opt_callback) {
    var instance = this,
        options = instance.options,
        json,
        operations,
        payload,
        pulls;

    payload = {
        repo: repo,
        state: options.state,
        user: user
    };

    operations = [
        function(callback) {
            base.github.pullRequests.getAll(payload, function(err, data) {
                if (!err) {
                    pulls = data;
                }
                callback(err);
            });
        },
        function(callback) {
            instance.getPullsTemplateJson_(pulls, function(err, data) {
                if (!err) {
                    json = data;
                }
                callback(err);
            });
        }
    ];

    async.series(operations, function(err) {
        if (!err && pulls.length) {
            json.repo = repo;
            json.user = options.user;
            json.detailed = options.detailed;
            logger.logTemplateFile('pr.handlebars', json);
        }

        opt_callback && opt_callback(err);
    });
};

PullRequest.prototype.listFromAllRepositories = function(opt_callback) {
    var instance = this;

    base.github.repos.getAll({ type: 'all' }, function(err, repositories) {
        if (err) {
            opt_callback && opt_callback(err);
        }
        else {
            repositories.forEach(function(repository) {
                instance.list(repository.owner.login, repository.name, opt_callback);
            });
        }
    });
};

PullRequest.prototype.merge = function(opt_callback) {
    var instance = this,
        options = instance.options,
        operations;

    operations = [
        function(callback) {
            git.checkout(options.branch, null, callback);
        },
        function(callback) {
            git.merge(options.pullBranch, options.rebase, false, function(err, data) {
                console.log(data);
                callback(err);
            });
        },
        function(callback) {
            instance.close(callback);
        }
    ];

    async.series(operations, opt_callback);
};

PullRequest.prototype.open = function(opt_callback) {
    var instance = this;

    instance.getPullRequest_(function(err, pull) {
        if (err) {
            opt_callback && opt_callback(err);
        }
        else {
            instance.updatePullRequest_(
                pull.title, pull.body, PullRequest.STATE_OPEN, opt_callback);
        }
    });
};

PullRequest.prototype.submit = function(user, opt_callback) {
    var instance = this,
        options = instance.options,
        operations,
        pullBranch;

    pullBranch = options.pullBranch || options.currentBranch;

    operations = [
        function(callback) {
            git.exec('push', [ 'origin', pullBranch ], callback);
        },
        function(callback) {
            base.github.pullRequests.create({
                base: options.branch,
                body: '',
                head: options.user + ':' + pullBranch,
                repo: options.repo,
                title: options.title || pullBranch,
                user: user
            }, callback);
        }
    ];

    async.series(operations, function(err, results) {
        if (err) {
            instance.checkPullRequestIntegrity_(err, user, opt_callback);
        }
        else {
            opt_callback && opt_callback(err, results[1]);
        }
    });
};

PullRequest.prototype.updatePullRequest_ = function(title, opt_body, state, opt_callback) {
    var instance = this,
        options = instance.options,
        payload;

    if (opt_body) {
        opt_body = logger.applyReplacements(opt_body);
    }

    payload = {
        body: opt_body,
        number: options.number,
        repo: options.repo,
        state: state,
        title: title,
        user: options.user
    };

    base.github.pullRequests.update(payload, opt_callback);
};

exports.Impl = PullRequest;<|endoftext|>


<commit_before>var _ = require('../util').lodash,
    PropertyBase = require('./property-base').PropertyBase,

    Cookie,

    PAIR_SPLIT_REGEX = /; */,

    /**
     * Enum for all the Cookie attributes.
     * @private
     * @readonly
     * @enum {string} CookieAttributes
     */
    cookieAttributes = {
        httponly: 'httpOnly',
        secure: 'secure',
        domain: 'domain',
        path: 'path',
        'max-age': 'maxAge',
        session: 'session',
        expires: 'expires'
    };

/**
 * The following is the object structure accepted as constructor parameter while calling `new Cookie(...)`. It is
 * also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
 * Cookie instance.
 * @typedef Cookie~definition
 *
 * @property {String=} [key] The name of the cookie. Some call it the "name".
 * @property {String=} [value] The value stored in the Cookie
 * @property {String=} [expires] Expires sets an expiry date for when a coo0kie gets deleted. It should either be a
 * date object or timestamp string of date.
 * @property {Number=} [maxAge] Max-age sets the time in seconds for when a cookie will be deleted.
 * @property {String=} [domain] Indicates the domain(s) for which the cookie should be sent.
 * @property {String=} [path] Limits the scope of the cookie to a specified path, e.g: "/accounts"
 * @property {Boolean=} [secure] A secure cookie will only be sent to the server when a request is made using SSL and
 * the HTTPS protocol.
 * The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
 * as clear text.
 * @property {Boolean=} [httpOnly] The idea behind HTTP-only cookies is to instruct a browser that a cookie should never
 * be accessible via JavaScript through the document.cookie property. This feature was designed as a security measure
 * to help prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
 * @property {Boolean=} [hostOnly] Indicates that this cookie is only valid for the given domain (and not its parent or
 * child domains.)
 * @property {Boolean=} [session] Indicates whether this is a Session Cookie. (A transient cookie, which is deleted at
 * the end of an HTTP session.)
 * @property {Array=} [extensions] Any extra attributes that are extensions to the original Cookie specification can be
 * specified here.
 * @property {String} [extensions[].key] Name of the extension.
 * @property {String} [extensions[].value] Value of the extension
 *
 * @example
JSON definition of an example collection

 * {
 *     key: 'my-cookie-name',
 *     expires: '1464769543832', // UNIX timestamp, in *milliseconds*
 *     maxAge: '300'  // In seconds. In this case, the Cookie is valid for 5 minutes
 *     domain: 'something.example.com',
 *     path: '/',
 *     secure: false,
 *     httpOnly: true,
 *     session: false,
 *     value: 'my-cookie-value',
 *     extensions: [{
 *         key: 'Priority',
 *         value: 'HIGH'
 *     }]
 * }
 */
_.inherit((

    /**
     * A Postman Cookie definition that comprehensively represents an HTTP Cookie.
     * @constructor
     * @extends {PropertyBase}
     *
     * @param {Cookie~definition} [options] Pass the initial definition of the Cookie.
     * @example
Create a new Cookie

     * var Cookie = require('postman-collection').Cookie,
     *     myCookie = new Cookie({
     *          name: 'my-cookie-name',
     *          expires: '1464769543832', // UNIX timestamp, in *milliseconds*
     *          maxAge: '300'  // In seconds. In this case, the Cookie is valid for 5 minutes
     *          domain: 'something.example.com',
     *          path: '/',
     *          secure: false,
     *          httpOnly: true,
     *          session: false,
     *          value: 'my-cookie-value',
     *          extensions: [{
     *              key: 'Priority',
     *              value: 'HIGH'
     *          }]
     *     });
     *
     * @example
Parse a Cookie Header

     * var Cookie = require('postman-collection').Cookie,
     *     rawHeader = 'myCookie=myValue;Path=/;Expires=Sun, 04-Feb-2018 14:18:27 GMT;Secure;HttpOnly;Priority=HIGH'
     *     myCookie = new Cookie(rawHeader);
     *
     * console.log(myCookie.toJSON());
     */
    Cookie = function PostmanCookie (options) {
        // this constructor is intended to inherit and as such the super constructor is required to be executed
        Cookie.super_.call(this, options);

        _.isString(options) && (options = Cookie.parse(options));

        this.update(options || {});
    }), PropertyBase);

_.assign(Cookie.prototype, /** @lends Cookie.prototype */ {
    update: function (options) {
        _.mergeDefined(this, /** @lends Cookie.prototype */ {
            /**
             * The name of the cookie.
             * @type {String}
             */
            name: _.choose(options.name, options.key),

            /**
             * Expires sets an expiry date for when a cookie gets deleted. It should either be a date object or
             * timestamp string of date.
             * @type {Date|String}
             *
             * @note
             * The value for this option is a date in the format Wdy, DD-Mon-YYYY HH:MM:SS GMT such as
             * "Sat, 02 May 2009 23:38:25 GMT". Without the expires option, a cookie has a lifespan of a single session.
             * A session is defined as finished when the browser is shut down, so session cookies exist only while the
             * browser remains open. If the expires option is set to a date that appears in the past, then the cookie is
             * immediately deleted in browser.
             *
             * @todo Accept date object and convert stringified date (timestamp only) to date object
             */
            expires: _.isString(options.expires) ? new Date(options.expires) : options.expires,

            /**
             * Max-age sets the time in seconds for when a cookie will be deleted.
             * @type {Number}
             */
            maxAge: _.has(options, 'maxAge') ? Number(options.maxAge) : undefined,

            /**
             * Indicates the domain(s) for which the cookie should be sent.
             * @type {String}
             *
             * @note
             * By default, domain is set to the host name of the page setting the cookie, so the cookie value is sent
             * whenever a request is made to the same host name. The value set for the domain option must be part of the
             * host name that is sending the Set-Cookie header. The SDK does not perform this check, but the underlying
             * client that actually sends the request could do it automatically.
             */
            domain: options.domain,

            /**
             * @type {String}
             *
             * @note
             * On server, the default value for the path option is the path of the URL that sent the Set-Cookie header.
             */
            path: options.path,

            /**
             * A secure cookie will only be sent to the server when a request is made using SSL and the HTTPS protocol.
             * The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
             * as clear text.
             * @type: {Boolean}
             */
            secure: _.has(options, 'secure') ? Boolean(options.secure) : undefined,

            /**
             * The idea behind HTTP-only cookies is to instruct a browser that a cookie should never be accessible via
             * JavaScript through the document.cookie property. This feature was designed as a security measure to help
             * prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
             * @type: {Boolean}
             */
            httpOnly: _.has(options, 'httpOnly') ? Boolean(options.httpOnly) : undefined,

            /**
             * @type: {Boolean}
             */
            hostOnly: _.has(options, 'hostOnly') ? Boolean(options.hostOnly) : undefined,

            /**
             * Indicates whether this is a Session Cookie.
             * @type: {Boolean}
             */
            session: _.has(options, 'session') ? Boolean(options.session) : undefined,

            /**
             * @note The commonly held belief is that cookie values must be URL-encoded, but this is a fallacy even
             * though it is the de facto implementation. The original specification indicates that only three types of
             * characters must be encoded: semicolon, comma, and white space. The specification indicates that URL
             * encoding may be used but stops short of requiring it. The RFC makes no mention of encoding whatsoever.
             * Still, almost all implementations perform some sort of URL encoding on cookie values.
             * @type {String}
             */
            value: options.value ? _.ensureEncoded(options.value) : undefined,

            /**
             * Any extra parameters that are not strictly a part of the Cookie spec go here.
             * @type {Array}
             */
            extensions: options.extensions || undefined
        });
    }
});

_.assign(Cookie, /** @lends Cookie */ {

    /**
     * Defines the name of this property for internal use.
     * @private
     * @readOnly
     * @type {String}
     */
    _postman_propertyName: 'Cookie',

    /**
     * Check whether an object is an instance of PostmanCookie.
     *
     * @param {*} obj
     * @returns {Boolean}
     */
    isCookie: function (obj) {
        return Boolean(obj) && ((obj instanceof Cookie) ||
            _.inSuperChain(obj.constructor, '_postman_propertyName', Cookie._postman_propertyName));
    },

    /**
     * Cookie header parser
     *
     * @param str
     * @returns {*} A plain cookie options object, use it to create a new Cookie
     */
    parse: function (str) {
        if (!_.isString(str)) {
            return str;
        }

        var obj = {},
            pairs = str.split(PAIR_SPLIT_REGEX),
            nameval;

        nameval = Cookie.splitParam(pairs.shift()); // The first kvp is the name and value
        obj.key = nameval.key;
        obj.value = nameval.value;

        pairs.forEach(function (pair) {
            var keyval = Cookie.splitParam(pair),
                value = keyval.value,
                keyLower = keyval.key.toLowerCase();

            if (cookieAttributes[keyLower]) {
                obj[cookieAttributes[keyLower]] = value;
            }
            else {
                obj.extensions = obj.extensions || [];
                obj.extensions.push(keyval);
            }
        });
        // Handle the hostOnly flag
        if (!obj.domain) {
            obj.hostOnly = true;
        }
        return obj;
    },

    /**
     * Splits a Cookie parameter into a key and a value
     *
     * @private
     * @param param
     * @returns {{key: *, value: (boolean|*)}}
     */
    splitParam: function (param) {
        var split = param.split('='),
            key, value;
        key = split[0].trim();
        value = (split[1]) ? split[1].trim() : true;

        if (_.isString(value) && value[0] === '"') {
            value = value.slice(1, -1);
        }

        return { key: key, value: value };
    }
});

module.exports = {
    Cookie: Cookie
};
<commit_msg>Ensured Cookie behaves properly in a list<commit_after>var _ = require('../util').lodash,
    PropertyBase = require('./property-base').PropertyBase,

    Cookie,

    PAIR_SPLIT_REGEX = /; */,

    /**
     * Enum for all the Cookie attributes.
     * @private
     * @readonly
     * @enum {string} CookieAttributes
     */
    cookieAttributes = {
        httponly: 'httpOnly',
        secure: 'secure',
        domain: 'domain',
        path: 'path',
        'max-age': 'maxAge',
        session: 'session',
        expires: 'expires'
    };

/**
 * The following is the object structure accepted as constructor parameter while calling `new Cookie(...)`. It is
 * also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
 * Cookie instance.
 * @typedef Cookie~definition
 *
 * @property {String=} [key] The name of the cookie. Some call it the "name".
 * @property {String=} [value] The value stored in the Cookie
 * @property {String=} [expires] Expires sets an expiry date for when a coo0kie gets deleted. It should either be a
 * date object or timestamp string of date.
 * @property {Number=} [maxAge] Max-age sets the time in seconds for when a cookie will be deleted.
 * @property {String=} [domain] Indicates the domain(s) for which the cookie should be sent.
 * @property {String=} [path] Limits the scope of the cookie to a specified path, e.g: "/accounts"
 * @property {Boolean=} [secure] A secure cookie will only be sent to the server when a request is made using SSL and
 * the HTTPS protocol.
 * The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
 * as clear text.
 * @property {Boolean=} [httpOnly] The idea behind HTTP-only cookies is to instruct a browser that a cookie should never
 * be accessible via JavaScript through the document.cookie property. This feature was designed as a security measure
 * to help prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
 * @property {Boolean=} [hostOnly] Indicates that this cookie is only valid for the given domain (and not its parent or
 * child domains.)
 * @property {Boolean=} [session] Indicates whether this is a Session Cookie. (A transient cookie, which is deleted at
 * the end of an HTTP session.)
 * @property {Array=} [extensions] Any extra attributes that are extensions to the original Cookie specification can be
 * specified here.
 * @property {String} [extensions[].key] Name of the extension.
 * @property {String} [extensions[].value] Value of the extension
 *
 * @example
JSON definition of an example collection

 * {
 *     key: 'my-cookie-name',
 *     expires: '1464769543832', // UNIX timestamp, in *milliseconds*
 *     maxAge: '300'  // In seconds. In this case, the Cookie is valid for 5 minutes
 *     domain: 'something.example.com',
 *     path: '/',
 *     secure: false,
 *     httpOnly: true,
 *     session: false,
 *     value: 'my-cookie-value',
 *     extensions: [{
 *         key: 'Priority',
 *         value: 'HIGH'
 *     }]
 * }
 */
_.inherit((

    /**
     * A Postman Cookie definition that comprehensively represents an HTTP Cookie.
     * @constructor
     * @extends {PropertyBase}
     *
     * @param {Cookie~definition} [options] Pass the initial definition of the Cookie.
     * @example
Create a new Cookie

     * var Cookie = require('postman-collection').Cookie,
     *     myCookie = new Cookie({
     *          name: 'my-cookie-name',
     *          expires: '1464769543832', // UNIX timestamp, in *milliseconds*
     *          maxAge: '300'  // In seconds. In this case, the Cookie is valid for 5 minutes
     *          domain: 'something.example.com',
     *          path: '/',
     *          secure: false,
     *          httpOnly: true,
     *          session: false,
     *          value: 'my-cookie-value',
     *          extensions: [{
     *              key: 'Priority',
     *              value: 'HIGH'
     *          }]
     *     });
     *
     * @example
Parse a Cookie Header

     * var Cookie = require('postman-collection').Cookie,
     *     rawHeader = 'myCookie=myValue;Path=/;Expires=Sun, 04-Feb-2018 14:18:27 GMT;Secure;HttpOnly;Priority=HIGH'
     *     myCookie = new Cookie(rawHeader);
     *
     * console.log(myCookie.toJSON());
     */
    Cookie = function PostmanCookie (options) {
        // this constructor is intended to inherit and as such the super constructor is required to be executed
        Cookie.super_.call(this, options);

        _.isString(options) && (options = Cookie.parse(options));

        this.update(options || {});
    }), PropertyBase);

_.assign(Cookie.prototype, /** @lends Cookie.prototype */ {
    update: function (options) {
        _.mergeDefined(this, /** @lends Cookie.prototype */ {
            /**
             * The name of the cookie.
             * @type {String}
             */
            name: _.choose(options.name, options.key),

            /**
             * Expires sets an expiry date for when a cookie gets deleted. It should either be a date object or
             * timestamp string of date.
             * @type {Date|String}
             *
             * @note
             * The value for this option is a date in the format Wdy, DD-Mon-YYYY HH:MM:SS GMT such as
             * "Sat, 02 May 2009 23:38:25 GMT". Without the expires option, a cookie has a lifespan of a single session.
             * A session is defined as finished when the browser is shut down, so session cookies exist only while the
             * browser remains open. If the expires option is set to a date that appears in the past, then the cookie is
             * immediately deleted in browser.
             *
             * @todo Accept date object and convert stringified date (timestamp only) to date object
             */
            expires: _.isString(options.expires) ? new Date(options.expires) : options.expires,

            /**
             * Max-age sets the time in seconds for when a cookie will be deleted.
             * @type {Number}
             */
            maxAge: _.has(options, 'maxAge') ? Number(options.maxAge) : undefined,

            /**
             * Indicates the domain(s) for which the cookie should be sent.
             * @type {String}
             *
             * @note
             * By default, domain is set to the host name of the page setting the cookie, so the cookie value is sent
             * whenever a request is made to the same host name. The value set for the domain option must be part of the
             * host name that is sending the Set-Cookie header. The SDK does not perform this check, but the underlying
             * client that actually sends the request could do it automatically.
             */
            domain: options.domain,

            /**
             * @type {String}
             *
             * @note
             * On server, the default value for the path option is the path of the URL that sent the Set-Cookie header.
             */
            path: options.path,

            /**
             * A secure cookie will only be sent to the server when a request is made using SSL and the HTTPS protocol.
             * The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
             * as clear text.
             * @type: {Boolean}
             */
            secure: _.has(options, 'secure') ? Boolean(options.secure) : undefined,

            /**
             * The idea behind HTTP-only cookies is to instruct a browser that a cookie should never be accessible via
             * JavaScript through the document.cookie property. This feature was designed as a security measure to help
             * prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
             * @type: {Boolean}
             */
            httpOnly: _.has(options, 'httpOnly') ? Boolean(options.httpOnly) : undefined,

            /**
             * @type: {Boolean}
             */
            hostOnly: _.has(options, 'hostOnly') ? Boolean(options.hostOnly) : undefined,

            /**
             * Indicates whether this is a Session Cookie.
             * @type: {Boolean}
             */
            session: _.has(options, 'session') ? Boolean(options.session) : undefined,

            /**
             * @note The commonly held belief is that cookie values must be URL-encoded, but this is a fallacy even
             * though it is the de facto implementation. The original specification indicates that only three types of
             * characters must be encoded: semicolon, comma, and white space. The specification indicates that URL
             * encoding may be used but stops short of requiring it. The RFC makes no mention of encoding whatsoever.
             * Still, almost all implementations perform some sort of URL encoding on cookie values.
             * @type {String}
             */
            value: options.value ? _.ensureEncoded(options.value) : undefined,

            /**
             * Any extra parameters that are not strictly a part of the Cookie spec go here.
             * @type {Array}
             */
            extensions: options.extensions || undefined
        });
    }
});

_.assign(Cookie, /** @lends Cookie */ {

    /**
     * Defines the name of this property for internal use.
     * @private
     * @readOnly
     * @type {String}
     */
    _postman_propertyName: 'Cookie',

    // define behaviour of this object when put in list
    _postman_propertyIndexKey: 'name',
    _postman_propertyIndexCaseInsensitive: true,
    _postman_propertyAllowsMultipleValues: true,

    /**
     * Check whether an object is an instance of PostmanCookie.
     *
     * @param {*} obj
     * @returns {Boolean}
     */
    isCookie: function (obj) {
        return Boolean(obj) && ((obj instanceof Cookie) ||
            _.inSuperChain(obj.constructor, '_postman_propertyName', Cookie._postman_propertyName));
    },

    /**
     * Cookie header parser
     *
     * @param str
     * @returns {*} A plain cookie options object, use it to create a new Cookie
     */
    parse: function (str) {
        if (!_.isString(str)) {
            return str;
        }

        var obj = {},
            pairs = str.split(PAIR_SPLIT_REGEX),
            nameval;

        nameval = Cookie.splitParam(pairs.shift()); // The first kvp is the name and value
        obj.key = nameval.key;
        obj.value = nameval.value;

        pairs.forEach(function (pair) {
            var keyval = Cookie.splitParam(pair),
                value = keyval.value,
                keyLower = keyval.key.toLowerCase();

            if (cookieAttributes[keyLower]) {
                obj[cookieAttributes[keyLower]] = value;
            }
            else {
                obj.extensions = obj.extensions || [];
                obj.extensions.push(keyval);
            }
        });
        // Handle the hostOnly flag
        if (!obj.domain) {
            obj.hostOnly = true;
        }
        return obj;
    },

    /**
     * Splits a Cookie parameter into a key and a value
     *
     * @private
     * @param param
     * @returns {{key: *, value: (boolean|*)}}
     */
    splitParam: function (param) {
        var split = param.split('='),
            key, value;
        key = split[0].trim();
        value = (split[1]) ? split[1].trim() : true;

        if (_.isString(value) && value[0] === '"') {
            value = value.slice(1, -1);
        }

        return { key: key, value: value };
    }
});

module.exports = {
    Cookie: Cookie
};
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var EventEmitter = require('events').EventEmitter
  , amqp = require('amqp')
  , util = require('util')
  , Message = require('./message')
  , debug = require('debug')('crane-amqp');

/**
 * Message delivery mode constants.
 */
var NONPERSISTENT_MODE = 1;
var PERSISTENT_MODE = 2;

/**
 * `Broker` constructor.
 *
 * @api public
 */
function Broker() {
  EventEmitter.call(this);
  this._exchange = null;
  this._queues = {};
  this._ctags = {};
}

/**
 * Inherit from `EventEmitter`.
 */
util.inherits(Broker, EventEmitter);

/**
 * Connect to AMQP server
 *
 * For AMQP brokers, `options` argument should be an object that specifies the
 * following parameters:
 *
 *   - `port`  port the client should connect to, defaults to 5672
 *   - `host`  host the client should connect to, defaults to localhost
 *
 * @param {Object} options
 * @param {Function} readyListener
 * @api public
 */
Broker.prototype.connect = function(options, readyListener) {
  if (readyListener) { this.once('ready', readyListener); }

  debug('connecting %s:%s', options.host || 'localhost', options.port || 5672);

  var self = this;
  this._connection = amqp.createConnection(options);
  this._connection.once('ready', function() {
    var exchange = options.exchange;
    if (!exchange) {
      debug('default exchange');

      // Get the default exchange, which is a direct exchange, and thus suitable
      // for use as a task queue.
      self._exchange = self._connection.exchange();
      return self.emit('ready');
    } else {
      var name = exchange
        , opts = {};
      if (typeof exchange == 'object') {
        name = exchange.name;
        opts = exchange.options;
      }

      // AMQP uses period ('.') separators rather than slash ('/')
      name = name.replace(/\//g, '.');

      opts.type = (opts.type === undefined) ? 'direct' : opts.type;
      opts.durable = (opts.durable === undefined) ? true : opts.durable;
      opts.autoDelete = (opts.autoDelete === undefined) ? false : opts.autoDelete;
      opts.confirm = (opts.confirm === undefined) ? true : opts.confirm;

      debug('exchange %s %s', name);

      self._exchange = self._connection.exchange(name, opts, function(exchange) {
        return self.emit('ready');
      });
      self._exchange.on('error', function(err) {
        return self.emit('error', err);
      });
    }
  });

  this._connection.on('error', this.emit.bind(this, 'error'));
}

Broker.prototype.declare = function(name, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // AMQP uses period ('.') separators rather than slash ('/')
  name = name.replace(/\//g, '.');

  // Task queues are declared as durable, by default.  This ensures that tasks
  // are not lost in the event that that server is stopped or crashes.
  options.durable = (options.durable === undefined) ? true : options.durable;
  options.autoDelete = (options.autoDelete === undefined) ? false : options.autoDelete;

  var q = this._connection.queue(name, options, function(q) {
    q.removeListener('error', onQueueDeclareError);

    var exchange = options.bind;
    if (exchange) {
      // AMQP uses period ('.') separators rather than slash ('/')
      exchange = exchange.replace(/\//g, '.');

      // TODO: support binding to multiple topics
      var topic = options.topic || name;
      topic = topic.replace(/\//g, '.');

      q.bind(exchange, topic, function(q) {
        q.removeListener('error', onQueueBindError);
        return cb && cb();
      });

      // Listen for events that indicate that the queue failed to be bound.
      var onQueueBindError = function(err) {
        return cb && cb(err);
      };
      q.once('error', onQueueBindError);
    } else {
      return cb && cb();
    }
  });
  this._queues[name] = q;

  // Listen for events that indicate that the queue failed to be declared.
  var onQueueDeclareError = function(err) {
    return cb && cb(err);
  };
  q.once('error', onQueueDeclareError);
}

Broker.prototype.enqueue = function(queue, msg, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // AMQP uses period ('.') separators rather than slash ('/')
  queue = queue.replace(/\//g, '.');

  options.deliveryMode = (options.deliveryMode === undefined) ? PERSISTENT_MODE : options.deliveryMode;
  // TODO: This option appears to have no effect. Why?
  //options.mandatory = (options.mandatory === undefined) ? true : options.mandatory;

  // FIXME: not working properly
  /*
  if (this._exchange.options && this._exchange.options.confirm) {
    this._exchange.publish(queue, msg, options, function(hadError) {
      var err;
      if (hadError) {
        err = new Error('Failed to enqueue task in queue "' + queue + '"');
      }
      return cb(err);
    });
  } else { */
    this._exchange.publish(queue, msg, options);
    // TODO: call cb on nextTick
    return cb && cb();
  /*}*/
}

Broker.prototype.subscribe = function(queue, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // AMQP uses period ('.') separators rather than slash ('/')
  queue = queue.replace(/\//g, '.');

  options.ack = (options.ack === undefined) ? true : options.ack;

  var self = this
    , q = this._queues[queue];
  q.subscribe(options, function(message, headers, deliveryInfo) {
    var m = new Message(q, message, headers, deliveryInfo);
    m.broker = self;

    self.emit('message', m);
  }).addCallback(function(ok) {
    self._ctags[queue] = ok.consumerTag;
  });

  // Listen for events that indicate the subscription has been processed.
  // `basicConsumeOk` is emitted when the subscription is successful.
  // `error` is emitted when the subscription failed.  In practice, this
  // has only been observed when attempting to subscribe to queue that has a
  // previously existing exclusive subscription.

  var onBasicConsumeOk = function(args) {
    q.removeListener('error', onBasicConsumeError);
    return cb && cb();
  };

  var onBasicConsumeError = function(err) {
    q.removeListener('basicConsumeOk', onBasicConsumeOk);
    return cb && cb(err);
  };

  q.once('basicConsumeOk', onBasicConsumeOk);
  q.once('error', onBasicConsumeError);
}

Broker.prototype.unsubscribe = function(queue, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};
  options.close = (options.close === undefined) ? true : options.close;

  // AMQP uses period ('.') separators rather than slash ('/')
  queue = queue.replace(/\//g, '.');

  var self = this
    , q = this._queues[queue]
    , ctag = this._ctags[queue];

  q.unsubscribe(ctag)
    .addCallback(function(ok) {
      delete self._ctags[queue];

      if (options.close) {
        q.close();
        return cb();
      } else {
        return cb();
      }
    })
    .addErrback(function(err) {
      // TODO: Handle this (figure out how to force this in a real-world scenario, invalid ctag???)
    });
}

/**
 * Expose `Broker`.
 */
module.exports = Broker;
<commit_msg>Fix bug where by confirm incorrectly used on built-in exchanges.<commit_after>/**
 * Module dependencies.
 */
var EventEmitter = require('events').EventEmitter
  , amqp = require('amqp')
  , util = require('util')
  , Message = require('./message')
  , debug = require('debug')('crane-amqp');

/**
 * Message delivery mode constants.
 */
var NONPERSISTENT_MODE = 1;
var PERSISTENT_MODE = 2;

/**
 * `Broker` constructor.
 *
 * @api public
 */
function Broker() {
  EventEmitter.call(this);
  this._exchange = null;
  this._queues = {};
  this._ctags = {};
}

/**
 * Inherit from `EventEmitter`.
 */
util.inherits(Broker, EventEmitter);

/**
 * Connect to AMQP server
 *
 * For AMQP brokers, `options` argument should be an object that specifies the
 * following parameters:
 *
 *   - `port`  port the client should connect to, defaults to 5672
 *   - `host`  host the client should connect to, defaults to localhost
 *
 * @param {Object} options
 * @param {Function} readyListener
 * @api public
 */
Broker.prototype.connect = function(options, readyListener) {
  if (readyListener) { this.once('ready', readyListener); }

  debug('connecting %s:%s', options.host || 'localhost', options.port || 5672);

  var self = this;
  this._connection = amqp.createConnection(options);
  this._connection.once('ready', function() {
    var exchange = options.exchange;
    if (!exchange) {
      debug('default exchange');

      // Get the default exchange, which is a direct exchange, and thus suitable
      // for use as a task queue.
      self._exchange = self._connection.exchange();
      return self.emit('ready');
    } else {
      var name = exchange
        , opts = {};
      if (typeof exchange == 'object') {
        name = exchange.name;
        opts = exchange.options;
      }

      // AMQP uses period ('.') separators rather than slash ('/')
      name = name.replace(/\//g, '.');
      if (name.indexOf('amq.') !== 0) {
        opts.type = (opts.type === undefined) ? 'direct' : opts.type;
        opts.durable = (opts.durable === undefined) ? true : opts.durable;
        opts.autoDelete = (opts.autoDelete === undefined) ? false : opts.autoDelete;
        opts.confirm = (opts.confirm === undefined) ? true : opts.confirm;
      } else {
        // Options for built-in exchanges can not be overridden.
        opts = {};
      }

      debug('exchange %s %s', name);
      self._exchange = self._connection.exchange(name, opts, function(exchange) {
        return self.emit('ready');
      });
      self._exchange.on('error', function(err) {
        return self.emit('error', err);
      });
    }
  });

  this._connection.on('error', this.emit.bind(this, 'error'));
}

Broker.prototype.declare = function(name, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // AMQP uses period ('.') separators rather than slash ('/')
  name = name.replace(/\//g, '.');

  // Task queues are declared as durable, by default.  This ensures that tasks
  // are not lost in the event that that server is stopped or crashes.
  options.durable = (options.durable === undefined) ? true : options.durable;
  options.autoDelete = (options.autoDelete === undefined) ? false : options.autoDelete;

  var q = this._connection.queue(name, options, function(q) {
    q.removeListener('error', onQueueDeclareError);

    var exchange = options.bind;
    if (exchange) {
      // AMQP uses period ('.') separators rather than slash ('/')
      exchange = exchange.replace(/\//g, '.');

      // TODO: support binding to multiple topics
      var topic = options.topic || name;
      topic = topic.replace(/\//g, '.');

      q.bind(exchange, topic, function(q) {
        q.removeListener('error', onQueueBindError);
        return cb && cb();
      });

      // Listen for events that indicate that the queue failed to be bound.
      var onQueueBindError = function(err) {
        return cb && cb(err);
      };
      q.once('error', onQueueBindError);
    } else {
      return cb && cb();
    }
  });
  this._queues[name] = q;

  // Listen for events that indicate that the queue failed to be declared.
  var onQueueDeclareError = function(err) {
    return cb && cb(err);
  };
  q.once('error', onQueueDeclareError);
}

Broker.prototype.enqueue = function(topic, msg, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // AMQP uses period ('.') separators rather than slash ('/')
  topic = topic.replace(/\//g, '.');

  options.deliveryMode = (options.deliveryMode === undefined) ? PERSISTENT_MODE : options.deliveryMode;
  // TODO: This option appears to have no effect. Why?
  //options.mandatory = (options.mandatory === undefined) ? true : options.mandatory;

  if (this._exchange.options && this._exchange.options.confirm) {
    debug('publish %s (confirm)', topic);
    this._exchange.publish(topic, msg, options, function(hadError) {
      var err;
      if (hadError) {
        err = new Error('Failed to publish message to topic "' + topic + '"');
      }
      return cb(err);
    });
  } else {
    debug('publish %s', topic);
    this._exchange.publish(topic, msg, options);
    if (cb) { return process.nextTick(cb); }
  }
}

Broker.prototype.subscribe = function(queue, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // AMQP uses period ('.') separators rather than slash ('/')
  queue = queue.replace(/\//g, '.');

  options.ack = (options.ack === undefined) ? true : options.ack;

  var self = this
    , q = this._queues[queue];
  q.subscribe(options, function(message, headers, deliveryInfo) {
    var m = new Message(q, message, headers, deliveryInfo);
    m.broker = self;

    self.emit('message', m);
  }).addCallback(function(ok) {
    self._ctags[queue] = ok.consumerTag;
  });

  // Listen for events that indicate the subscription has been processed.
  // `basicConsumeOk` is emitted when the subscription is successful.
  // `error` is emitted when the subscription failed.  In practice, this
  // has only been observed when attempting to subscribe to queue that has a
  // previously existing exclusive subscription.

  var onBasicConsumeOk = function(args) {
    q.removeListener('error', onBasicConsumeError);
    return cb && cb();
  };

  var onBasicConsumeError = function(err) {
    q.removeListener('basicConsumeOk', onBasicConsumeOk);
    return cb && cb(err);
  };

  q.once('basicConsumeOk', onBasicConsumeOk);
  q.once('error', onBasicConsumeError);
}

Broker.prototype.unsubscribe = function(queue, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};
  options.close = (options.close === undefined) ? true : options.close;

  // AMQP uses period ('.') separators rather than slash ('/')
  queue = queue.replace(/\//g, '.');

  var self = this
    , q = this._queues[queue]
    , ctag = this._ctags[queue];

  q.unsubscribe(ctag)
    .addCallback(function(ok) {
      delete self._ctags[queue];

      if (options.close) {
        q.close();
        return cb();
      } else {
        return cb();
      }
    })
    .addErrback(function(err) {
      // TODO: Handle this (figure out how to force this in a real-world scenario, invalid ctag???)
    });
}

/**
 * Expose `Broker`.
 */
module.exports = Broker;
<|endoftext|>


<commit_before>const got = require('got');
const parseLinkHeader = require('parse-link-header');

module.exports = {
  getDigest,
  getTags,
};

function massageRegistry(input) {
  let registry = input;
  if (!registry || registry === 'docker.io') {
    registry = 'index.docker.io'; // eslint-disable-line no-param-reassign
  }
  if (!registry.match('$https?://')) {
    registry = `https://${registry}`; // eslint-disable-line no-param-reassign
  }
  return registry;
}

function getRepository(pkgName) {
  return pkgName.includes('/') ? pkgName : `library/${pkgName}`;
}

async function getAuthHeaders(registry, repository) {
  // istanbul ignore if
  if (registry !== 'https://index.docker.io') {
    return {};
  }
  try {
    const authUrl = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repository}:pull`;
    logger.debug(`Obtaining docker registry token for ${repository}`);
    const { token } = (await got(authUrl, { json: true })).body;
    if (!token) {
      logger.warn('Failed to obtain docker registry token');
      return null;
    }
    return {
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.docker.distribution.manifest.v2+json',
    };
  } catch (err) {
    // istanbul ignore if
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn({ err }, 'docker registry failure: internal error');
      throw new Error('registry-failure');
    }
    logger.info('Error obtaining docker token');
    return null;
  }
}

async function getDigest(registry, name, tag = 'latest') {
  logger.debug(`getDigest(${registry}, ${name}, ${tag})`);
  const massagedRegistry = massageRegistry(registry);
  const repository = getRepository(name);
  try {
    const url = `${massagedRegistry}/v2/${repository}/manifests/${tag}`;
    const headers = await getAuthHeaders(massagedRegistry, repository);
    headers.accept = 'application/vnd.docker.distribution.manifest.v2+json';
    const digest = (await got(url, { json: true, headers, timeout: 10000 }))
      .headers['docker-content-digest'];
    logger.debug({ digest }, 'Got docker digest');
    return digest;
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode === 401) {
      logger.info(
        { err, body: err.response ? err.response.body : undefined, name, tag },
        'Lookup is unauthorized (private image)'
      );
      return null;
    }
    if (err.statusCode === 404) {
      logger.info(
        { err, body: err.response ? err.response.body : undefined, name, tag },
        'Docker Manifest is unknown'
      );
      return null;
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn(
        { err, body: err.response ? err.response.body : undefined, name, tag },
        'docker registry failure: internal error'
      );
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { massagedRegistry },
        'Timeout when attempting to connect to docker registry'
      );
      logger.debug({ err });
      return null;
    }
    logger.info(
      { err, body: err.response ? err.response.body : undefined, name, tag },
      'Unknown Error looking up docker image digest'
    );
    return null;
  }
}

async function getTags(registry, name) {
  logger.debug(`getTags(${registry}, ${name})`);
  const massagedRegistry = massageRegistry(registry);
  const repository = getRepository(name);
  try {
    let url = `${massagedRegistry}/v2/${repository}/tags/list?n=10000`;
    const headers = await getAuthHeaders(massagedRegistry, repository);
    let tags = [];
    let page = 1;
    do {
      const res = await got(url, { json: true, headers, timeout: 10000 });
      tags = tags.concat(res.body.tags);
      const linkHeader = parseLinkHeader(res.headers.link);
      url = linkHeader && linkHeader.next ? linkHeader.next.url : null;
      page += 1;
    } while (url && page < 20);
    logger.debug({ length: tags.length }, 'Got docker tags');
    logger.trace({ tags });
    return tags;
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn({ err }, 'docker registry failure: internal error');
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { massagedRegistry },
        'Timeout when attempting to connect to docker registry'
      );
      logger.debug({ err });
      return null;
    }
    logger.warn({ err, name }, 'Error getting docker image tags');
    return null;
  }
}
<commit_msg>fix: return early if docker token acquisition fails<commit_after>const got = require('got');
const parseLinkHeader = require('parse-link-header');

module.exports = {
  getDigest,
  getTags,
};

function massageRegistry(input) {
  let registry = input;
  if (!registry || registry === 'docker.io') {
    registry = 'index.docker.io'; // eslint-disable-line no-param-reassign
  }
  if (!registry.match('$https?://')) {
    registry = `https://${registry}`; // eslint-disable-line no-param-reassign
  }
  return registry;
}

function getRepository(pkgName) {
  return pkgName.includes('/') ? pkgName : `library/${pkgName}`;
}

async function getAuthHeaders(registry, repository) {
  // istanbul ignore if
  if (registry !== 'https://index.docker.io') {
    return {};
  }
  try {
    const authUrl = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repository}:pull`;
    logger.debug(`Obtaining docker registry token for ${repository}`);
    const { token } = (await got(authUrl, { json: true })).body;
    if (!token) {
      logger.warn('Failed to obtain docker registry token');
      return null;
    }
    return {
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.docker.distribution.manifest.v2+json',
    };
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode === 401) {
      logger.info({ registry, repository }, 'Unauthorized docker lookup');
      logger.debug({
        err,
        message: err.message,
        body: err.response ? err.response.body : undefined,
      });
      return null;
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn({ err }, 'docker registry failure: internal error');
      throw new Error('registry-failure');
    }
    logger.warn('Error obtaining docker token');
    return null;
  }
}

async function getDigest(registry, name, tag = 'latest') {
  logger.debug(`getDigest(${registry}, ${name}, ${tag})`);
  const massagedRegistry = massageRegistry(registry);
  const repository = getRepository(name);
  try {
    const url = `${massagedRegistry}/v2/${repository}/manifests/${tag}`;
    const headers = await getAuthHeaders(massagedRegistry, repository);
    if (!headers) {
      logger.info('No docker auth found - returning');
      return null;
    }
    headers.accept = 'application/vnd.docker.distribution.manifest.v2+json';
    const digest = (await got(url, { json: true, headers, timeout: 10000 }))
      .headers['docker-content-digest'];
    logger.debug({ digest }, 'Got docker digest');
    return digest;
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode === 401) {
      logger.info({ registry, repository }, 'Unauthorized docker lookup');
      logger.debug({
        err,
        message: err.message,
        body: err.response ? err.response.body : undefined,
      });
      return null;
    }
    if (err.statusCode === 404) {
      logger.info(
        { err, body: err.response ? err.response.body : undefined, name, tag },
        'Docker Manifest is unknown'
      );
      return null;
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn(
        { err, body: err.response ? err.response.body : undefined, name, tag },
        'docker registry failure: internal error'
      );
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { massagedRegistry },
        'Timeout when attempting to connect to docker registry'
      );
      logger.debug({ err });
      return null;
    }
    logger.info(
      { err, body: err.response ? err.response.body : undefined, name, tag },
      'Unknown Error looking up docker image digest'
    );
    return null;
  }
}

async function getTags(registry, name) {
  logger.debug(`getTags(${registry}, ${name})`);
  const massagedRegistry = massageRegistry(registry);
  const repository = getRepository(name);
  try {
    let url = `${massagedRegistry}/v2/${repository}/tags/list?n=10000`;
    const headers = await getAuthHeaders(massagedRegistry, repository);
    if (!headers) {
      return null;
    }
    let tags = [];
    let page = 1;
    do {
      const res = await got(url, { json: true, headers, timeout: 10000 });
      tags = tags.concat(res.body.tags);
      const linkHeader = parseLinkHeader(res.headers.link);
      url = linkHeader && linkHeader.next ? linkHeader.next.url : null;
      page += 1;
    } while (url && page < 20);
    logger.debug({ length: tags.length }, 'Got docker tags');
    logger.trace({ tags });
    return tags;
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn({ err }, 'docker registry failure: internal error');
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { massagedRegistry },
        'Timeout when attempting to connect to docker registry'
      );
      logger.debug({ err });
      return null;
    }
    logger.warn({ err, name }, 'Error getting docker image tags');
    return null;
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var conn = require('../common/connection.js');
var entity = require('./entity.js');
var Transaction = require('./transaction.js');
var Query = require('./query.js');
var util = require('../common/util.js');

var SCOPES = [
  'https://www.googleapis.com/auth/datastore',
  'https://www.googleapis.com/auth/userinfo.email'
];

/**
 * Creates a new dataset with the provided options.
 * @param {object} opts             Dataset identifier options.
 * @param {string} opts.id          Dataset ID, this is your project ID
 *                                  from Google Developers Console.
 * @param {string} opts.keyFilename Path to the JSON key file downloaded from
 *                                  Google Developers Console.
 */
function Dataset(opts) {
  opts = opts || {};
  var id = opts.projectId;

  this.connection = new conn.Connection({
    keyFilename: opts.keyFilename,
    scopes: SCOPES
  });
  this.id = id;
  this.transaction = this.createTransaction();
}

/**
 * Creates a query from the current dataset to query the specified
 * kinds.
 *
 * Example usage:
 *   ds.createQuery(['Lion', 'Chimp'])
 *   ds.createQuery('zoo', ['Lion', 'Chimp'])
 *
 * @param  {string=}       ns      Optional namespace to query entities from.
 * @param  {Array<String>} kinds A list of kinds to query.
 * @return {Query}
 */
Dataset.prototype.createQuery = function(ns, kinds) {
  if (!kinds) {
    kinds = ns;
    ns = '';
  }
  kinds = util.arrayize(kinds);
  return new Query(ns, kinds);
};

/**
 * Gets the object identified with the provided key.
 * @param  {Key}        key       A non-incomplete key.
 * @param  {Function}   callback  The callback function.
 */
Dataset.prototype.get = function(key, callback) {
  this.transaction.get(key, callback);
};

Dataset.prototype.save = function(key, obj, callback) {
  this.transaction.save(key, obj, callback);
};

Dataset.prototype.delete = function(key, callback) {
  this.transaction.delete(key, callback);
};

Dataset.prototype.runQuery = function(q, callback) {
  this.transaction.runQuery(q, callback);
};

Dataset.prototype.runInTransaction = function(fn, callback) {
  var newTransaction = this.createTransaction();
  newTransaction.begin(function(err) {
    if (err) {
      return callback(err);
    }
    fn(newTransaction, newTransaction.finalize.bind(newTransaction, callback));
  });
};

Dataset.prototype.allocateIds = function(incompleteKey, n, callback) {
  if (entity.isKeyComplete(incompleteKey)) {
    throw new Error('An incomplete key should be provided.');
  }
  var incompleteKeys = [];
  for (var i = 0; i < n; i++) {
    incompleteKeys.push(entity.keyToKeyProto(incompleteKey));
  }
  this.transaction.makeReq(
      'allocateIds', { keys: incompleteKeys }, function(err, resp) {
    if (err) {
      return callback(err);
    }
    var keys = [];
    resp.keys.forEach(function(k) {
      keys.push(entity.keyFromKeyProto(k));
    });
    callback(null ,keys);
  });
};

Dataset.prototype.createTransaction = function() {
  return new Transaction(this.connection, this.id);
};

/**
 * Exports Dataset.
 * @type {Dataset}
 */
module.exports = Dataset;
<commit_msg>Migrate ID allocation to proto.<commit_after>/**
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var conn = require('../common/connection.js');
var entity = require('./entity.js');
var Transaction = require('./transaction.js');
var pb = require('./pb.js');
var Query = require('./query.js');
var util = require('../common/util.js');

var SCOPES = [
  'https://www.googleapis.com/auth/datastore',
  'https://www.googleapis.com/auth/userinfo.email'
];

/**
 * Creates a new dataset with the provided options.
 * @param {object} opts             Dataset identifier options.
 * @param {string} opts.id          Dataset ID, this is your project ID
 *                                  from Google Developers Console.
 * @param {string} opts.keyFilename Path to the JSON key file downloaded from
 *                                  Google Developers Console.
 */
function Dataset(opts) {
  opts = opts || {};
  var id = opts.projectId;

  this.connection = new conn.Connection({
    keyFilename: opts.keyFilename,
    scopes: SCOPES
  });
  this.id = id;
  this.transaction = this.createTransaction();
}

/**
 * Creates a query from the current dataset to query the specified
 * kinds.
 *
 * Example usage:
 *   ds.createQuery(['Lion', 'Chimp'])
 *   ds.createQuery('zoo', ['Lion', 'Chimp'])
 *
 * @param  {string=}       ns      Optional namespace to query entities from.
 * @param  {Array<String>} kinds A list of kinds to query.
 * @return {Query}
 */
Dataset.prototype.createQuery = function(ns, kinds) {
  if (!kinds) {
    kinds = ns;
    ns = '';
  }
  kinds = util.arrayize(kinds);
  return new Query(ns, kinds);
};

/**
 * Gets the object identified with the provided key.
 * @param  {Key}        key       A non-incomplete key.
 * @param  {Function}   callback  The callback function.
 */
Dataset.prototype.get = function(key, callback) {
  this.transaction.get(key, callback);
};

Dataset.prototype.save = function(key, obj, callback) {
  this.transaction.save(key, obj, callback);
};

Dataset.prototype.delete = function(key, callback) {
  this.transaction.delete(key, callback);
};

Dataset.prototype.runQuery = function(q, callback) {
  this.transaction.runQuery(q, callback);
};

Dataset.prototype.runInTransaction = function(fn, callback) {
  var newTransaction = this.createTransaction();
  newTransaction.begin(function(err) {
    if (err) {
      return callback(err);
    }
    fn(newTransaction, newTransaction.finalize.bind(newTransaction, callback));
  });
};

Dataset.prototype.allocateIds = function(incompleteKey, n, callback) {
  if (entity.isKeyComplete(incompleteKey)) {
    throw new Error('An incomplete key should be provided.');
  }
  var incompleteKeys = [];
  for (var i = 0; i < n; i++) {
    incompleteKeys.push(entity.keyToKeyProto(incompleteKey));
  }
  this.transaction.makeReq(
      'allocateIds',
      new pb.AllocateIdsRequest({ key: incompleteKeys }),
      pb.AllocateIdsResponse, function(err, resp) {
    if (err) {
      return callback(err);
    }
    var keys = [];
    (resp.key || []).forEach(function(k) {
      keys.push(entity.keyFromKeyProto(k));
    });
    callback(null ,keys);
  });
};

Dataset.prototype.createTransaction = function() {
  return new Transaction(this.connection, this.id);
};

/**
 * Exports Dataset.
 * @type {Dataset}
 */
module.exports = Dataset;
<|endoftext|>


<commit_before>/*
 * This file is part of the Spludo Framework.
 * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/
 *
 * Licensed under the terms of MIT License. For the full copyright and license
 * information, please see the LICENSE file in the root folder.
 */

new TestSuite("datamapper.JsonMapperTests", {

    simpleEncodeDecode: function() {
        var data_mapper = data_mapper_manager.getDataMapper('json');

        equal(data_mapper.encodeSync("abcde"), "\"abcde\"");
    },

    expectingAnExceptionOnInvalidJson: function() {
        var data_mapper = data_mapper_manager.getDataMapper('json');

        try {
            data_mapper.decodeSync("abcde");
            fail("Should fail, because invalid json!");
        } catch (e) {
            notEqual(e.message.indexOf("Could not decode JSON"), -1, "Should throw something with 'Could not decode JSON'");
        }
    },

    simpleEncodeOfUtf8Data: function() {
        var data_mapper = data_mapper_manager.getDataMapper('json');

        equal(data_mapper.encodeSync("Dateigröße (in KB)"), "\"Dateigr\\u00f6\\u00dfe (in KB)\"");
        equal(data_mapper.decodeSync("\"Dateigr\\u00f6\\u00dfe (in KB)\""), "Dateigröße (in KB)");
        equal(data_mapper.decodeSync(data_mapper.encodeSync("Dateigröße (in KB)")), "Dateigröße (in KB)");
    }
});
<commit_msg>Encoding special utf8 characters does not result in \u* anymore. Testcase changed.<commit_after>/*
 * This file is part of the Spludo Framework.
 * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/
 *
 * Licensed under the terms of MIT License. For the full copyright and license
 * information, please see the LICENSE file in the root folder.
 */

new TestSuite("datamapper.JsonMapperTests", {

    simpleEncodeDecode: function() {
        var data_mapper = data_mapper_manager.getDataMapper('json');

        equal(data_mapper.encodeSync("abcde"), "\"abcde\"");
    },

    expectingAnExceptionOnInvalidJson: function() {
        var data_mapper = data_mapper_manager.getDataMapper('json');

        try {
            data_mapper.decodeSync("abcde");
            fail("Should fail, because invalid json!");
        } catch (e) {
            notEqual(e.message.indexOf("Could not decode JSON"), -1, "Should throw something with 'Could not decode JSON'");
        }
    },

    simpleEncodeOfUtf8Data: function() {
        var data_mapper = data_mapper_manager.getDataMapper('json');

        equal(data_mapper.encodeSync("Dateigröße (in KB)"), "\"Dateigröße (in KB)\"");
        equal(data_mapper.decodeSync("\"Dateigr\\u00f6\\u00dfe (in KB)\""), "Dateigröße (in KB)");
        equal(data_mapper.decodeSync(data_mapper.encodeSync("Dateigröße (in KB)")), "Dateigröße (in KB)");
    }
});
<|endoftext|>