<commit_before>// This software is free software. See AUTHORS and LICENSE for more
// information on the copying conditions.

"use strict";

// Rules to evaluate compliancy according to Nexa Center
var nexaRules = {
    deposit_of_item: {
        field_id: 2,
        compliant_values: "required",
        guidelines: ["3.6"],
        gmga: ["29.2.2.a.1", "29.2.2.c.1"],
    },

    locus_of_deposit: {
        field_id: 3,
        compliant_values: ["any_repo", "institution_repo", "suject_repo"],
        guidelines: ["3.6", "3.9", "3.14"],
        gmga: ["29.2.2.a.1"],
    },

    date_of_deposit: {
        field_id: 4,
        compliant_values: ["acceptance", "publication"],
        guidelines: ["3.6", "3.8"],
        gmga: ["29.2.2.a.1"],
    },

    mandate_content_types: {
        field_id: 5,
        compliant_values: function (value) {
            return (value.indexOf("not_specified") < 0);
        },
        explanation: {
            true: "different from 'not_specified'",
            false: "is 'not_specified'",
        },
        guidelines: ["3.1", "3.3", "3.4", "3.11"],
        gmga: ["29.2.1", "29.2.2.a.2"],
    },

    journal_article_version: {
        field_id: 6,
        compliant_values: ["author_final", "published"],
        guidelines: ["3.6", "3.8", "3.14"],
        gmga: ["29.2.2.a.1"],
    },

    can_deposit_be_waived: {
        field_id: 7,
        compliant_values: "no",
        guidelines: ["3.12"],
    },

    making_deposit_open: {
        field_id: 8,
        compliant_values: "required",
        guidelines: ["3.1", "3.15", "3.17"],
        gmga: ["29.1.1", "29.2.1", "29.2.2.b", "29.2.2.c.1"],
    },

    waive_open_access: {
        field_id: 9,
        compliant_values: function (v) {
            return ((v === "no")
                    ? 0.75
                    : (v === "yes")
                        ? 0.25
                        : false);
        },
        explanation: {
            0.75: "is 'no'",
            0.25: "is 'yes'",
            false: "is neither 'no' nor 'yes'",
        },
        gmga: ["29.1.1", "29.1.2"],
        caveats: {
            0.75: "However, please note that Open Access must be " +
                "sacrificed when specific conflicting obligations " +
                "to be respected subsist.",
            0.25: "Except if the waiver is limited to specific exceptions " +
                "introduced in order to safeguard specific obligations " +
                "conflicting with Open Access.",
        },
    },

    date_made_open: {
        field_id: 10,
        compliant_values: function (v, r) {
            if (["acceptance", "publication"].indexOf(v) >= 0) {
                return 1.0;
            }
            if (v === "embargo" &&
                    ["0m", "6m", "12m"].indexOf(r.embargo_hum_soc) >= 0 &&
                    ["0m", "6m"].indexOf(r.embargo_sci_tech_med) >= 0) {
                return 2.0;
            }
            return false;
        },
        explanation: {
            1.0: "one of: acceptance, publication",
            2.0: "embargo ok for both HUSS and STEM",
            false: "not one of acceptance, publication or embargo too long",
        },
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
    },

    embargo_sci_tech_med: {
        field_id: 15,
        compliant_values: ["0m", "6m"],
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
    },

    embargo_hum_soc: {
        field_id: 16,
        compliant_values: ["0m", "6m", "12m"],
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
    },

    maximal_embargo_waivable: {
        field_id: 17,
        compliant_values: function (v) {
            return ((["no", "not_specified"].indexOf(v) >= 0)
                    ? true
                    : (v === "yes")
                        ? 0.25
                        : false);
        },
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
        explanation: {
            true: "one of: no, not_specified",
            0.25: "is yes",
            false: "not one of: no, not_specified, yes",
        },
        caveats: {
            0.25: "Please note that European Commission does not " +
                "expressly state that maximal embargo cannot be " +
                "waived. However, given that a maximal embargo " +
                "is established, and no exceptions are mentioned, " +
                "it is reasonable to believe that waiver is not admitted.",
        },
    },

    open_licensing_conditions: {
        field_id: 18,
        compliant_values: "req_cc_by",
        guidelines: ["3.2", "3.20"],
    },

    gold_oa_options: {
        field_id: 19,
        compliant_values: function () {
            return true;
        },
        guidelines: ["3.5", "3.13", "3.15"],
        gmga: ["29.2.2.b"],
        explanation: {
            true: "any value is ok",
        },
        normalize: function (v) {
            return (((v === "reccomended") ? "recommended" :
                    (v === "reqired") ? "required" : v));
        },
    },
};

var evaluateRule = function (rule, record, key, value) {
    var compliant;
    var reason;

    if (typeof rule.compliant_values === "string") {
        compliant = (value === rule.compliant_values);
        reason = ((!compliant) ? "not " : "") + "equal to "
                + "'" + rule.compliant_values + "'";

    } else if (rule.compliant_values instanceof Array) {
        compliant = (rule.compliant_values.indexOf(value) >= 0);
        reason = ((!compliant) ? "not " : "") + "one of: "
                + rule.compliant_values.join(", ");

    } else if (rule.compliant_values instanceof Function) {
        compliant = rule.compliant_values(value, record, key);
        reason = rule.explanation[compliant];

    } else {
        throw new Error("Programmer error");
    }

    return {
        fuzzyLabel: (rule.caveats && rule.caveats[compliant]),
        reason: reason,
        value: compliant
    };
};

var apply = exports.apply = function (record) {
    var rules = nexaRules;
    var newRecord = [];
    Object.keys(rules).forEach(function (key) {
        var rule = rules[key],
            value = record[key],
            compliantRec = evaluateRule(rule, record, key, value);
        newRecord.push({
            field_id: rule.field_id,
            field: key,
            reason: compliantRec.reason,
            value: (function () {
                return (rule.normalize && rule.normalize(value));
            })() || value,
            is_compliant: (compliantRec.value >= 0.5),
            is_compliant_float: compliantRec.value,
            guidelines: rule.guidelines,
            gmga: rule.gmga,
            fuzzy_label: compliantRec.fuzzyLabel,
        });
    });
    return newRecord;
};
<commit_msg>rules.js: start creating return object in inner function<commit_after>// This software is free software. See AUTHORS and LICENSE for more
// information on the copying conditions.

"use strict";

// Rules to evaluate compliancy according to Nexa Center
var nexaRules = {
    deposit_of_item: {
        field_id: 2,
        compliant_values: "required",
        guidelines: ["3.6"],
        gmga: ["29.2.2.a.1", "29.2.2.c.1"],
    },

    locus_of_deposit: {
        field_id: 3,
        compliant_values: ["any_repo", "institution_repo", "suject_repo"],
        guidelines: ["3.6", "3.9", "3.14"],
        gmga: ["29.2.2.a.1"],
    },

    date_of_deposit: {
        field_id: 4,
        compliant_values: ["acceptance", "publication"],
        guidelines: ["3.6", "3.8"],
        gmga: ["29.2.2.a.1"],
    },

    mandate_content_types: {
        field_id: 5,
        compliant_values: function (value) {
            return (value.indexOf("not_specified") < 0);
        },
        explanation: {
            true: "different from 'not_specified'",
            false: "is 'not_specified'",
        },
        guidelines: ["3.1", "3.3", "3.4", "3.11"],
        gmga: ["29.2.1", "29.2.2.a.2"],
    },

    journal_article_version: {
        field_id: 6,
        compliant_values: ["author_final", "published"],
        guidelines: ["3.6", "3.8", "3.14"],
        gmga: ["29.2.2.a.1"],
    },

    can_deposit_be_waived: {
        field_id: 7,
        compliant_values: "no",
        guidelines: ["3.12"],
    },

    making_deposit_open: {
        field_id: 8,
        compliant_values: "required",
        guidelines: ["3.1", "3.15", "3.17"],
        gmga: ["29.1.1", "29.2.1", "29.2.2.b", "29.2.2.c.1"],
    },

    waive_open_access: {
        field_id: 9,
        compliant_values: function (v) {
            return ((v === "no")
                    ? 0.75
                    : (v === "yes")
                        ? 0.25
                        : false);
        },
        explanation: {
            0.75: "is 'no'",
            0.25: "is 'yes'",
            false: "is neither 'no' nor 'yes'",
        },
        gmga: ["29.1.1", "29.1.2"],
        caveats: {
            0.75: "However, please note that Open Access must be " +
                "sacrificed when specific conflicting obligations " +
                "to be respected subsist.",
            0.25: "Except if the waiver is limited to specific exceptions " +
                "introduced in order to safeguard specific obligations " +
                "conflicting with Open Access.",
        },
    },

    date_made_open: {
        field_id: 10,
        compliant_values: function (v, r) {
            if (["acceptance", "publication"].indexOf(v) >= 0) {
                return 1.0;
            }
            if (v === "embargo" &&
                    ["0m", "6m", "12m"].indexOf(r.embargo_hum_soc) >= 0 &&
                    ["0m", "6m"].indexOf(r.embargo_sci_tech_med) >= 0) {
                return 2.0;
            }
            return false;
        },
        explanation: {
            1.0: "one of: acceptance, publication",
            2.0: "embargo ok for both HUSS and STEM",
            false: "not one of acceptance, publication or embargo too long",
        },
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
    },

    embargo_sci_tech_med: {
        field_id: 15,
        compliant_values: ["0m", "6m"],
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
    },

    embargo_hum_soc: {
        field_id: 16,
        compliant_values: ["0m", "6m", "12m"],
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
    },

    maximal_embargo_waivable: {
        field_id: 17,
        compliant_values: function (v) {
            return ((["no", "not_specified"].indexOf(v) >= 0)
                    ? true
                    : (v === "yes")
                        ? 0.25
                        : false);
        },
        guidelines: ["3.14"],
        gmga: ["29.2.2.b"],
        explanation: {
            true: "one of: no, not_specified",
            0.25: "is yes",
            false: "not one of: no, not_specified, yes",
        },
        caveats: {
            0.25: "Please note that European Commission does not " +
                "expressly state that maximal embargo cannot be " +
                "waived. However, given that a maximal embargo " +
                "is established, and no exceptions are mentioned, " +
                "it is reasonable to believe that waiver is not admitted.",
        },
    },

    open_licensing_conditions: {
        field_id: 18,
        compliant_values: "req_cc_by",
        guidelines: ["3.2", "3.20"],
    },

    gold_oa_options: {
        field_id: 19,
        compliant_values: function () {
            return true;
        },
        guidelines: ["3.5", "3.13", "3.15"],
        gmga: ["29.2.2.b"],
        explanation: {
            true: "any value is ok",
        },
        normalize: function (v) {
            return (((v === "reccomended") ? "recommended" :
                    (v === "reqired") ? "required" : v));
        },
    },
};

var evaluateRule = function (rule, record, key, value) {
    var result = {
        is_compliant: null,
        reason: null,
    };

    if (typeof rule.compliant_values === "string") {
        result.is_compliant = (value === rule.compliant_values);
        result.reason = ((!result.is_compliant) ? "not " : "") + "equal to "
                + "'" + rule.compliant_values + "'";

    } else if (rule.compliant_values instanceof Array) {
        result.is_compliant = (rule.compliant_values.indexOf(value) >= 0);
        result.reason = ((!result.is_compliant) ? "not " : "") + "one of: "
                + rule.compliant_values.join(", ");

    } else if (rule.compliant_values instanceof Function) {
        result.is_compliant = rule.compliant_values(value, record, key);
        result.reason = rule.explanation[result.is_compliant];

    } else {
        throw new Error("Programmer error");
    }

    result.fuzzyLabel = (rule.caveats && rule.caveats[result.is_compliant]);
    return result;
};

var apply = exports.apply = function (record) {
    var rules = nexaRules;
    var newRecord = [];
    Object.keys(rules).forEach(function (key) {
        var rule = rules[key],
            value = record[key],
            compliantRec = evaluateRule(rule, record, key, value);
        newRecord.push({
            field_id: rule.field_id,
            field: key,
            reason: compliantRec.reason,
            value: (function () {
                return (rule.normalize && rule.normalize(value));
            })() || value,
            is_compliant: (compliantRec.is_compliant >= 0.5),
            is_compliant_float: compliantRec.is_compliant,
            guidelines: rule.guidelines,
            gmga: rule.gmga,
            fuzzy_label: compliantRec.fuzzyLabel,
        });
    });
    return newRecord;
};
<|endoftext|>


<commit_before>const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const websocket = require('koa-easy-ws')
const Router = require('koa-router')
const log = require('sepal/log')('http')
const {resolve, stream} = require('./stream')

const requestLog = async (ctx, next) => {
    const start = new Date()
    log.debug(`${ctx.method} ${ctx.url} started`)
    await next()
    const ms = new Date() - start
    log.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
}

const router = Router()

const start = ({port, routes, timeout = 10 * 60 * 1000}) => {
    const app = new Koa()
    app.use(requestLog)
    app.use(bodyParser())
    app.use(websocket())
    app.use(resolve)
    app.use(routes(router).routes())

    const server = app.listen(port, () =>
        log.info(`Server started on port ${port}`)
    )
    server.setTimeout(timeout)

}

module.exports = {start, stream}
<commit_msg>Improved  'server started' log message.<commit_after>const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const websocket = require('koa-easy-ws')
const Router = require('koa-router')
const log = require('sepal/log')('http')
const {resolve, stream} = require('./stream')

const requestLog = async (ctx, next) => {
    const start = new Date()
    log.debug(`${ctx.method} ${ctx.url} started`)
    await next()
    const ms = new Date() - start
    log.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
}

const router = Router()

const start = ({port, routes, timeout = 10 * 60 * 1000}) => {
    const app = new Koa()
    app.use(requestLog)
    app.use(bodyParser())
    app.use(websocket())
    app.use(resolve)
    app.use(routes(router).routes())

    const server = app.listen(port, () =>
        log.info(`HTTP server started on port ${port}`)
    )
    server.setTimeout(timeout)

}

module.exports = {start, stream}
<|endoftext|>


<commit_before>const _ = require('underscore');
const request = require('request');
const promise = require('promise');
const RateLimiter = require('limiter').RateLimiter;
const xml2js = require('xml2js');
const parser = new xml2js.Parser();
const util = require('util');
const OAuth = require('oauth');

var GoodReadsAPI = module.exports = function (config) {
    this.config = _.extend({
        grApi: 'https://www.goodreads.com',
        apiKeys: [],
    }, config);

    this.maxRequestsPerSecond = this.config.apiKeys.length;
    this.limiter = new RateLimiter(1, 1000);

    for (var i in this.config.apiKeys) {
        this.oauthClients.push(new OAuth.OAuth(
            'http://www.goodreads.com/oauth/request_token',
            'http://www.goodreads.com/oauth/access_token',
            this.config.apiKeys[i].key,
            this.config.apiKeys[i].secret,
            '1.0A',
            null,
            'HMAC-SHA1'
        ));
    }
}

GoodReadsAPI.prototype = {
    config: null,
    limiter: null,
    maxRequestsPerSecond: 0,
    currentApiKey: 0,
    oauthClients: [],

    wrapApiGetRequest: function wrapApiRequest(reqUrl, requestData, handler) {
        if (typeof requestData === 'function' && typeof handler === 'undefined') {
            handler = requestData;
            requestData = null;
        }

        var _this = this;
        this.limiter.removeTokens(1, function (err, remaining) {
            var thisKey = _this.config.apiKeys[_this.currentApiKey];
            _this.currentApiKey = (++_this.currentApiKey) % _this.config.apiKeys.length;

            if (null !== requestData) {
                var params = [];
                for (var i in requestData) {
                    params.push(i + '=' + requestData[i]);
                }

                reqUrl += '?' + params.join('&');
            }

            request(reqUrl.replace('{{grKey}}', thisKey.key), handler);
        });
    },

    wrapAuthenticatedApiGetRequest: function wrapAuthenticatedApiRequest(reqUrl, userOauthInfo, handler) {
        var _this = this;
        this.limiter.removeTokens(1, function (err, remaining) {
            var thisKeyNo = _this.currentApiKey;
            var thisKey = _this.config.apiKeys[thisKeyNo];
            _this.currentApiKey = (++_this.currentApiKey) % _this.config.apiKeys.length;
            _this.oauthClients[thisKeyNo].get(reqUrl, userOauthInfo.key, userOauthInfo.secret, function (e, data, response) {
                handler(e, response, data);
            });
        });
    },

    wrapAuthenticatedApiPostRequest: function wrapAuthenticatedApiRequest(reqUrl, requestData, userOauthInfo, handler) {
        var _this = this;
        this.limiter.removeTokens(1, function (err, remaining) {
            var thisKeyNo = _this.currentApiKey;
            var thisKey = _this.config.apiKeys[thisKeyNo];
            _this.currentApiKey = (++_this.currentApiKey) % _this.config.apiKeys.length;
            _this.oauthClients[thisKeyNo].post(reqUrl, userOauthInfo.key, userOauthInfo.secret, requestData, function (e, data, response) {
                handler(e, response, data);
            });
        });
    },

    rejectWithError: function rejectWithError(reject, error, response, errorObj) {
        if (typeof errorObj === 'undefined') {
            errorObj = {};
        } else if (typeof errorObj !== 'object') {
            errorObj = { arg: errorObj };
        }

        errorObj.error = error;
        errorObj.response = response;

        reject(errorObj);
    },

    /* GoodReads API Functions *****************/
    isbnToBookId: function isbnToBookId(isbn) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/book/isbn_to_id/' + isbn + '?key={{grKey}}', function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        var match = body.match(/^\d+$/);
                        if (match === null) {
                            return _this.rejectWithError(reject, error, response, {
                                message: 'Invalid response from goodreads',
                            });
                        }
                        return resolve(match);

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown isbn',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    bookShow: function bookShow(bookId) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/book/show/' + bookId + '.xml?key={{grKey}}', function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    bookShowByIsbn: function bookShow(isbn) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/book/isbn/' + isbn + '?key={{grKey}}', function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    getUserNotifications: function getUserNotifications(userOauthInfo) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapAuthenticatedApiGetRequest(_this.config.grApi + '/notifications.xml', userOauthInfo, function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    getUserShelves: function getUserShelves(userId, page) {
        if (typeof page === 'undefined') {
            page = 1;
        }

        var _this = this,
            request = {
                page: page,
                key: '{{grKey}}',
                user_id: userId,
            };
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/shelf/list.xml', request, function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            console.log('Shelves response: ', result);
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else if (parseInt(result.shelves.end) < parseInt(result.shelves.total)) {
                                console.log('More shelves...');
                                _this.getUserShelves(userId, page + 1)
                                    .then(mergeShelves(result))
                                    .then(function(merged) {
                                        resolve(merged);
                                    });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    addUserOwnedBook: function addUserOwnedBook(addBookRequest, userOauthInfo) {
        var _this = this;
        if (typeof addBookRequest !== 'object') {
            addBookRequest = {
                bookId: addBookRequest,
            };
        }

        return new promise(function (resolve, reject) {
            var request = {
                'owned_book[book_id]': addBookRequest.bookId,
            };

            if (typeof addBookRequest.condition === 'number') {
                request['owned_book[condition_code]'] = addBookRequest.condition;
            }

            _this.wrapAuthenticatedApiPostRequest(_this.config.grApi + '/owned_books.xml', request, userOauthInfo, function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                    case 201:
                    case 202:
                    case 203:
                    case 204:
                        parseGRXmlResponse(body, false, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },
};

function parseGRXmlResponse(data, rootElementName, callback) {
    if (typeof rootElementName === 'function') {
        callback = rootElementName;
        rootElementName = 'GoodreadsResponse';
    }

    parser.parseString(data, function (err, result) {
        if (err || (rootElementName !== false && typeof result[rootElementName] !== 'object')) {
            return callback(err ? err : true);
        }

        callback(null, removeXmlArrays(rootElementName !== false ? result[rootElementName] : result));
    });
}

function removeXmlArrays(data) {
    for (var i in data) {
        if (Object.prototype.toString.call( data[i] ) === '[object Array]') {
            if (data[i].length === 1 && typeof data[i][0] !== 'object') {
                data[i] = data[i][0];
            } else {
                data[i] = removeXmlArrays(data[i]);
            }
        } else if (typeof data[i] === 'object') {
            data[i] = removeXmlArrays(data[i]);
        }
    }

    return data;
}

function mergeShelves(baseShelves) {
    return function(shelves) {
        return [ baseShelves, shelves ];
    }
}<commit_msg>Check proper condition codes<commit_after>const _ = require('underscore');
const request = require('request');
const promise = require('promise');
const RateLimiter = require('limiter').RateLimiter;
const xml2js = require('xml2js');
const parser = new xml2js.Parser();
const util = require('util');
const OAuth = require('oauth');

const ValidGRConditionCodes = [ 10, 20, 30, 40, 50, 60 ];

var GoodReadsAPI = module.exports = function (config) {
    this.config = _.extend({
        grApi: 'https://www.goodreads.com',
        apiKeys: [],
    }, config);

    this.maxRequestsPerSecond = this.config.apiKeys.length;
    this.limiter = new RateLimiter(1, 1000);

    for (var i in this.config.apiKeys) {
        this.oauthClients.push(new OAuth.OAuth(
            'http://www.goodreads.com/oauth/request_token',
            'http://www.goodreads.com/oauth/access_token',
            this.config.apiKeys[i].key,
            this.config.apiKeys[i].secret,
            '1.0A',
            null,
            'HMAC-SHA1'
        ));
    }
}

GoodReadsAPI.prototype = {
    config: null,
    limiter: null,
    maxRequestsPerSecond: 0,
    currentApiKey: 0,
    oauthClients: [],

    wrapApiGetRequest: function wrapApiRequest(reqUrl, requestData, handler) {
        if (typeof requestData === 'function' && typeof handler === 'undefined') {
            handler = requestData;
            requestData = null;
        }

        var _this = this;
        this.limiter.removeTokens(1, function (err, remaining) {
            var thisKey = _this.config.apiKeys[_this.currentApiKey];
            _this.currentApiKey = (++_this.currentApiKey) % _this.config.apiKeys.length;

            if (null !== requestData) {
                var params = [];
                for (var i in requestData) {
                    params.push(i + '=' + requestData[i]);
                }

                reqUrl += '?' + params.join('&');
            }

            request(reqUrl.replace('{{grKey}}', thisKey.key), handler);
        });
    },

    wrapAuthenticatedApiGetRequest: function wrapAuthenticatedApiRequest(reqUrl, userOauthInfo, handler) {
        var _this = this;
        this.limiter.removeTokens(1, function (err, remaining) {
            var thisKeyNo = _this.currentApiKey;
            var thisKey = _this.config.apiKeys[thisKeyNo];
            _this.currentApiKey = (++_this.currentApiKey) % _this.config.apiKeys.length;
            _this.oauthClients[thisKeyNo].get(reqUrl, userOauthInfo.key, userOauthInfo.secret, function (e, data, response) {
                handler(e, response, data);
            });
        });
    },

    wrapAuthenticatedApiPostRequest: function wrapAuthenticatedApiRequest(reqUrl, requestData, userOauthInfo, handler) {
        var _this = this;
        this.limiter.removeTokens(1, function (err, remaining) {
            var thisKeyNo = _this.currentApiKey;
            var thisKey = _this.config.apiKeys[thisKeyNo];
            _this.currentApiKey = (++_this.currentApiKey) % _this.config.apiKeys.length;
            _this.oauthClients[thisKeyNo].post(reqUrl, userOauthInfo.key, userOauthInfo.secret, requestData, function (e, data, response) {
                handler(e, response, data);
            });
        });
    },

    rejectWithError: function rejectWithError(reject, error, response, errorObj) {
        if (typeof errorObj === 'undefined') {
            errorObj = {};
        } else if (typeof errorObj !== 'object') {
            errorObj = { arg: errorObj };
        }

        errorObj.error = error;
        errorObj.response = response;

        reject(errorObj);
    },

    /* GoodReads API Functions *****************/
    isbnToBookId: function isbnToBookId(isbn) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/book/isbn_to_id/' + isbn + '?key={{grKey}}', function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        var match = body.match(/^\d+$/);
                        if (match === null) {
                            return _this.rejectWithError(reject, error, response, {
                                message: 'Invalid response from goodreads',
                            });
                        }
                        return resolve(match);

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown isbn',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    bookShow: function bookShow(bookId) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/book/show/' + bookId + '.xml?key={{grKey}}', function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    bookShowByIsbn: function bookShow(isbn) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/book/isbn/' + isbn + '?key={{grKey}}', function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    getUserNotifications: function getUserNotifications(userOauthInfo) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.wrapAuthenticatedApiGetRequest(_this.config.grApi + '/notifications.xml', userOauthInfo, function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    getUserShelves: function getUserShelves(userId, page) {
        if (typeof page === 'undefined') {
            page = 1;
        }

        var _this = this,
            request = {
                page: page,
                key: '{{grKey}}',
                user_id: userId,
            };
        return new promise(function (resolve, reject) {
            _this.wrapApiGetRequest(_this.config.grApi + '/shelf/list.xml', request, function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                        parseGRXmlResponse(body, function(err, result) {
                            console.log('Shelves response: ', result);
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else if (parseInt(result.shelves.end) < parseInt(result.shelves.total)) {
                                console.log('More shelves...');
                                _this.getUserShelves(userId, page + 1)
                                    .then(mergeShelves(result))
                                    .then(function(merged) {
                                        resolve(merged);
                                    });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },

    addUserOwnedBook: function addUserOwnedBook(addBookRequest, userOauthInfo) {
        var _this = this;
        if (typeof addBookRequest !== 'object') {
            addBookRequest = {
                bookId: addBookRequest,
            };
        }

        return new promise(function (resolve, reject) {
            var request = {
                'owned_book[book_id]': addBookRequest.bookId,
            };

            if (typeof addBookRequest.condition === 'number' && ValidGRConditionCodes.indexOf(addBookRequest.condition) !== -1) {
                request['owned_book[condition_code]'] = addBookRequest.condition;
            }

            _this.wrapAuthenticatedApiPostRequest(_this.config.grApi + '/owned_books.xml', request, userOauthInfo, function (error, response, body) {
                if (error) {
                    return _this.rejectWithError(reject, error, response, {
                        message: 'Error processing request',
                    });
                }

                switch (response.statusCode) {
                    case 200:
                    case 201:
                    case 202:
                    case 203:
                    case 204:
                        parseGRXmlResponse(body, false, function(err, result) {
                            if (err) {
                                _this.rejectWithError(reject, err, response, {
                                    message: 'Error parsing response',
                                });
                            } else {
                                resolve(result);
                            }
                        });
                        break;

                    case 404:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Unknown book id',
                        });

                    default:
                        return _this.rejectWithError(reject, error, response, {
                            message: 'Invalid response from goodreads',
                        });
                }
            });
        });
    },
};

function parseGRXmlResponse(data, rootElementName, callback) {
    if (typeof rootElementName === 'function') {
        callback = rootElementName;
        rootElementName = 'GoodreadsResponse';
    }

    parser.parseString(data, function (err, result) {
        if (err || (rootElementName !== false && typeof result[rootElementName] !== 'object')) {
            return callback(err ? err : true);
        }

        callback(null, removeXmlArrays(rootElementName !== false ? result[rootElementName] : result));
    });
}

function removeXmlArrays(data) {
    for (var i in data) {
        if (Object.prototype.toString.call( data[i] ) === '[object Array]') {
            if (data[i].length === 1 && typeof data[i][0] !== 'object') {
                data[i] = data[i][0];
            } else {
                data[i] = removeXmlArrays(data[i]);
            }
        } else if (typeof data[i] === 'object') {
            data[i] = removeXmlArrays(data[i]);
        }
    }

    return data;
}

function mergeShelves(baseShelves) {
    return function(shelves) {
        return [ baseShelves, shelves ];
    }
}<|endoftext|>


<commit_before>'use strict';

var passport = require('passport');
var Strategy = require('passport-github').Strategy;
var cryptoHelper = require('../helpers/encryption.helper');

function passportConfig(app) {

    app.use(passport.initialize());
    app.use(passport.session());

    //github sigin
    passport.use(new Strategy({
        clientID: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        callbackURL: '/oauth/github',
        failureRedirect: 'login/github',
        scope: [ 'user:email','repo' ]
    },
    function(accessToken, refreshToken, profile, done) {
        return done(null, {profile: profile, accessToken: accessToken});
    }));

    //serialisation/deserialisation of users
    //session contains sensitive info like access tokens so encrypt it before storage

    //encrypt is async to avoid blocking when generating random iv
    passport.serializeUser(function(user, done) {
        cryptoHelper.encrypt(JSON.stringify(user), function(cipherText) {
            done(null, cipherText);
        });
    });

    //decrypt is syncronous because there is no blocking code
    passport.deserializeUser(function(obj, done) {
        var user = JSON.parse(cryptoHelper.decrypt(obj));
        done(null, user);
    });
}

module.exports = passportConfig;
<commit_msg>more debugging<commit_after>'use strict';

var passport = require('passport');
var Strategy = require('passport-github').Strategy;
var cryptoHelper = require('../helpers/encryption.helper');

function passportConfig(app) {

    app.use(passport.initialize());
    app.use(passport.session());

    //github sigin
    passport.use(new Strategy({
        clientID: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        callbackURL: '/oauth/github',
        failureRedirect: 'login/github',
        scope: [ 'user:email','repo' ]
    },
    function(accessToken, refreshToken, profile, done) {
        return done(null, {profile: profile, accessToken: accessToken});
    }));

    //serialisation/deserialisation of users
    //session contains sensitive info like access tokens so encrypt it before storage

    //encrypt is async to avoid blocking when generating random iv
    passport.serializeUser(function(user, done) {
        require('../config/loggers.config').logger.info('serialising user');
        try {
        cryptoHelper.encrypt(JSON.stringify(user), function(cipherText) {
            done(null, cipherText);
        });
        }
        catch(err) {
            require('../config/loggers.config').logger.info('got an error serialising user: ' + err.message);
        }
    });

    //decrypt is syncronous because there is no blocking code
    passport.deserializeUser(function(obj, done) {
        require('../config/loggers.config').logger.info('deserialising user');
        try {
        var user = JSON.parse(cryptoHelper.decrypt(obj));
        done(null, user);
        }
        catch(err) {
            require('../config/loggers.config').logger.info('got and error deserialising user: ' + err.message);
        }
    });
}

module.exports = passportConfig;
<|endoftext|>


<commit_before>/*
 * Sends stats to a statsd configured endpoint.
 */
 "use strict";
var dgram = require('dgram');
var log = require("../logging").get("stats");

var client = dgram.createSocket('udp4');
var endpoint = null;

module.exports.setEndpoint = function(newEndpoint) {
    endpoint = newEndpoint;
    log.info("statsd endpoint: %s", JSON.stringify(endpoint));
    if (endpoint) {
        // start monitoring memory usage
        sendMemoryUsage();
    }
};

var sendStat = function(metricName, value, type) {
    if (!endpoint) {
        return;
    }
    var msg = new Buffer(metricName+":"+value+"|"+type);
    client.send(msg, 0, msg.length, endpoint.port, endpoint.hostname,
    function(err) {
        if (err) {
            log.error(err);
            if (err.stack) {
                log.error(err.stack);
            }
        }
    });
};

var sendMemoryUsage = function() {
    setTimeout(function() {
        var info = process.memoryUsage();
        Object.keys(info).forEach(function(key) {
            sendStat("MEM"+key, info[key], "g");
        });
        sendMemoryUsage();
    }, (1000 * 60)); // 1 min
};

module.exports.request = function(outcome, durationMs) {
    sendStat("REQ"+outcome, durationMs, "ms");
};<commit_msg>Use dot separated metrics so prometheus-statsd bridge can parse it.<commit_after>/*
 * Sends stats to a statsd configured endpoint.
 */
 "use strict";
var dgram = require('dgram');
var log = require("../logging").get("stats");

var client = dgram.createSocket('udp4');
var endpoint = null;

module.exports.setEndpoint = function(newEndpoint) {
    endpoint = newEndpoint;
    log.info("statsd endpoint: %s", JSON.stringify(endpoint));
    if (endpoint) {
        // start monitoring memory usage
        sendMemoryUsage();
    }
};

var sendStat = function(metricName, value, type) {
    if (!endpoint) {
        return;
    }
    var msg = new Buffer(metricName+":"+value+"|"+type);
    client.send(msg, 0, msg.length, endpoint.port, endpoint.hostname,
    function(err) {
        if (err) {
            log.error(err);
            if (err.stack) {
                log.error(err.stack);
            }
        }
    });
};

var sendMemoryUsage = function() {
    setTimeout(function() {
        var info = process.memoryUsage();
        Object.keys(info).forEach(function(key) {
            sendStat("mem."+key, info[key], "g");
        });
        sendMemoryUsage();
    }, (1000 * 60)); // 1 min
};

module.exports.request = function(outcome, durationMs) {
    sendStat("req."+outcome, durationMs, "ms");
};<|endoftext|>


<commit_before>const merge = require('deepmerge');
const defaults = require('./config');
const utils = require('./utils');

const loaderDefaults = defaults.loader;
const isomorphicSpriteModule = require.resolve('../runtime/sprite.build');
const isomorphicSymbolModule = require.resolve('svg-baker-runtime/symbol');

const isTargetBrowser = target => target === 'web' || target === 'electron-renderer'||'node-webkit'
||'nwjs';

/**
 * @param {Object} params
 * @param {Object} [params.config] Parsed loader config {@see SVGSpriteLoaderConfig}
 * @param {LoaderContext} context Loader context {@see https://webpack.js.org/api/loaders/#the-loader-context}
 * @return {Object}
 */
module.exports = function configurator({ config, context, target }) {
  const module = context._module;
  const compiler = context._compiler;
  const compilerName = compiler.name;

  const autoConfigured = {
    spriteModule: isTargetBrowser(target) ? loaderDefaults.spriteModule : isomorphicSpriteModule,
    symbolModule: isTargetBrowser(target) ? loaderDefaults.symbolModule : isomorphicSymbolModule,
    extract: utils.isModuleShouldBeExtracted(module),
    esModule: context.version && context.version >= 2
  };

  const finalConfig = merge.all([loaderDefaults, autoConfigured, config || {}]);

  /**
   * esModule should be `false` when compiles via extract-text-webpack-plugin or html-webpack-plugin.
   * Because this compilers executes module as usual node module so export should be always in commonjs style.
   * This could be dropped when Node.js will support ES modules natively :)
   * @see https://git.io/vS7Sn
   * @see https://git.io/v9w60
   */
  if (compilerName && (
    compilerName.includes('extract-text-webpack-plugin') ||
      compilerName.includes('html-webpack-plugin')
  )) {
    finalConfig.esModule = false;
  }

  return finalConfig;
};
<commit_msg>support node-webkit<commit_after>const merge = require('deepmerge');
const defaults = require('./config');
const utils = require('./utils');

const loaderDefaults = defaults.loader;
const isomorphicSpriteModule = require.resolve('../runtime/sprite.build');
const isomorphicSymbolModule = require.resolve('svg-baker-runtime/symbol');

const isTargetBrowser = target => target === 'web' || target === 'electron-renderer' || 'node-webkit' || 'nwjs';

/**
 * @param {Object} params
 * @param {Object} [params.config] Parsed loader config {@see SVGSpriteLoaderConfig}
 * @param {LoaderContext} context Loader context {@see https://webpack.js.org/api/loaders/#the-loader-context}
 * @return {Object}
 */
module.exports = function configurator({ config, context, target }) {
  const module = context._module;
  const compiler = context._compiler;
  const compilerName = compiler.name;

  const autoConfigured = {
    spriteModule: isTargetBrowser(target) ? loaderDefaults.spriteModule : isomorphicSpriteModule,
    symbolModule: isTargetBrowser(target) ? loaderDefaults.symbolModule : isomorphicSymbolModule,
    extract: utils.isModuleShouldBeExtracted(module),
    esModule: context.version && context.version >= 2
  };

  const finalConfig = merge.all([loaderDefaults, autoConfigured, config || {}]);

  /**
   * esModule should be `false` when compiles via extract-text-webpack-plugin or html-webpack-plugin.
   * Because this compilers executes module as usual node module so export should be always in commonjs style.
   * This could be dropped when Node.js will support ES modules natively :)
   * @see https://git.io/vS7Sn
   * @see https://git.io/v9w60
   */
  if (compilerName && (
    compilerName.includes('extract-text-webpack-plugin') ||
      compilerName.includes('html-webpack-plugin')
  )) {
    finalConfig.esModule = false;
  }

  return finalConfig;
};
<|endoftext|>


<commit_before>/**
 * ircloggr - a node.js all-in-one implementation of irc logging visible via
 *            a REST/JSON interface.
 *
 * See LICENSE file for licensing information.
 */

const sys = require("sys"),
     http = require("http"),
      url = require("url"),
     path = require("path"),
       fs = require("fs"),
  connect = require("connect");
     path = require('path'),
    wsapi = require('./wsapi.js'),
httputils = require('./httputils.js'),
  connect = require('connect'),
      irc = require('./irc.js'),
   config = require('./config.js'),
       db = require('./db.js');

// XXX: command line switch handling

// parse config file
try {
    config.parse_config_file();
} catch(e) {
    if (e && e.code === 'EBADF') {
        console.log("missing config file: " + e.path);
    } else {
        console.log("problem reading config file (" + config.config_path + "): " + e);
    }
    process.exit(1);
}

// load up databases
db.load_databases();

// need rooms configured, otherwise, what are we even doing?
if (Object.keys(config.servers).length === 0) {
    console.log("No irc rooms are configured!  Go update the config file!");
//    process.exit(1);
}

// now connect to specified servers
var toConnect = [];
for (var host in config.servers) {
    for (var i = 0; i < config.servers[host].length; i++) {
        var room = config.servers[host][i];
        toConnect.push([host, room]);
    }
}
function connectOneRoom() {
    if (toConnect.length == 0) runWSAPI();
    else {
        var cur = toConnect.shift();
        if (cur[1].substr(0,1) != '#') cur[1] = "#" + cur[1];
        irc.listen(cur[0], cur[1], function(x) {
            if (x !== true) {
                console.log("error connecting to : " + cur[0] + " " + cur[1]);
                process.exit(1);
            }
            connectOneRoom();
        });
    }
}
connectOneRoom();

var server = undefined;

function runWSAPI() {
    server = connect.createServer()
        .use(connect.favicon())
        .use(connect.logger({
            format: ":status :method :remote-addr :response-time :url",
            stream: fs.createWriteStream(path.join(__dirname, "server.log"))
        }))
        .use(function(request, response, serveFile) {
            var urlpath = url.parse(request.url).pathname;
            // break pathname up by '/'
            try {
                var args = urlpath.substr(1).split('/');
                if (wsapi[args[0]]) {
                    wsapi[args[0]](args, request, response);
                } else {
                    httputils.fourOhFour(response, "no such function: " + urlpath + "\n");
                }
            } catch(e) {
                httputils.badRequest(response, e.toString());
            }
        })
        .listen(config.port, config.host);
}
<commit_msg>start up web service in parallel with connecting up IRC connections.<commit_after>/**
 * ircloggr - a node.js all-in-one implementation of irc logging visible via
 *            a REST/JSON interface.
 *
 * See LICENSE file for licensing information.
 */

const sys = require("sys"),
     http = require("http"),
      url = require("url"),
     path = require("path"),
       fs = require("fs"),
  connect = require("connect");
     path = require('path'),
    wsapi = require('./wsapi.js'),
httputils = require('./httputils.js'),
  connect = require('connect'),
      irc = require('./irc.js'),
   config = require('./config.js'),
       db = require('./db.js');

// XXX: command line switch handling

// parse config file
try {
    config.parse_config_file();
} catch(e) {
    if (e && e.code === 'EBADF') {
        console.log("missing config file: " + e.path);
    } else {
        console.log("problem reading config file (" + config.config_path + "): " + e);
    }
    process.exit(1);
}

// load up databases
db.load_databases();

// need rooms configured, otherwise, what are we even doing?
if (Object.keys(config.servers).length === 0) {
    console.log("No irc rooms are configured!  Go update the config file!");
//    process.exit(1);
}

// now connect to specified servers
var toConnect = [];
for (var host in config.servers) {
    for (var i = 0; i < config.servers[host].length; i++) {
        var room = config.servers[host][i];
        toConnect.push([host, room]);
    }
}
function connectOneRoom() {
    if (toConnect.length == 0) return;
    else {
        var cur = toConnect.shift();
        if (cur[1].substr(0,1) != '#') cur[1] = "#" + cur[1];
        irc.listen(cur[0], cur[1], function(x) {
            if (x !== true) {
                console.log("error connecting to : " + cur[0] + " " + cur[1]);
                process.exit(1);
            }
            connectOneRoom();
        });
    }
}
connectOneRoom();

var server = server = connect.createServer()
    .use(connect.favicon())
    .use(connect.logger({
        format: ":status :method :remote-addr :response-time :url",
        stream: fs.createWriteStream(path.join(__dirname, "server.log"))
    }))
    .use(function(request, response, serveFile) {
        var urlpath = url.parse(request.url).pathname;
        // break pathname up by '/'
        try {
            var args = urlpath.substr(1).split('/');
            if (wsapi[args[0]]) {
                wsapi[args[0]](args, request, response);
            } else {
                httputils.fourOhFour(response, "no such function: " + urlpath + "\n");
            }
        } catch(e) {
            httputils.badRequest(response, e.toString());
        }
    })
    .listen(config.port, config.host);

<|endoftext|>


<commit_before>'use babel';

import {shallowCopyTargetProperty, unicodeEscapeSequenceToChar, toArrayBuffer, toBuffer } from './utility';

import fs from 'fs';
import cson from 'cson';

/*
class
*/

export class SourceFile {
  constructor() {
    this.name = '';
    this.fileName = '';
    this.fileData = null;
  }

  toString() {
    let ret = 'name: ' + this.name;
    ret += ', fileName: ' + this.fileName;
    return ret;
  }
}

export class KeyBind {
  constructor() {
    this.keyCode = -1;
    this.shift = false;
    this.ctrl = false;
    this.alt = false;
    this.name = '';
    this.fileData = null;
  }

  toString() {
    let ret = 'keyCode: ' + this.keyCode.toString(10);
    ret += ', name: ' + this.name;
    ret += ', shift: ' + String(this.shift);
    ret += ', ctrl: ' + String(this.ctrl);
    ret += ', alt: ' + String(this.alt);
    return ret;
  }
}

export class ImpulseResponse {
  constructor() {
    this.description = '';
    this.fileName = '';
    this.fileData = null;
  }

  toString() {
    let ret = 'description: ' + this.description;
    ret += ', fileName: ' + this.fileName;
    return ret;
  }
}
/*
export read
*/
export function readSource(path, dataPath) {
  return readConfigFile(path, dataPath, stringToSourceFile);
}
export function getReadSource(path, dataPath) {
  return () => {
    return readSource(path, dataPath);
  }
}

export function readIR(path, dataPath) {
  return readConfigFile(path, dataPath, stringToIR);
}
export function getReadIR(path, dataPath) {
  return () => {
    return readIR(path, dataPath);
  }
}

export function readKeyBind(path) {
  if(path === null) {
    return null;
  }
  return createReadFilePromise(path, null, stringToKeyBind);
}

//
function createReadFilePromise(path, dataPath, converter) {
  return new Promise ( (resolve, reject)=> {
    fs.readFile(path, 'utf-8', (err, data) => {
      if(err) {
        reject([err, 0]);
        return;
      }
      let result = converter(data, dataPath);
      if(result instanceof Error) {
        reject([result, 1]);
        return;
      }
      resolve(result);
    });
  });
}

//
function readConfigFile(path, dataPath, converter) {
  if(path === null) {
    return null;
  }
  let promise = createReadFilePromise(path, dataPath, converter);
  return promise.then(readBinaryDatas); //返るのは、readBinaryDatasのpromise
}

function readBinaryDatas(keyBinds) {
  let promises = [];
  for(let keyBind of keyBinds) {
    promises.push(readBinaryData(keyBind));
  }
  return Promise.all(promises);
}

function readBinaryData(keyBind) {
  return new Promise( (resolve, reject) => {
    fs.readFile(keyBind.fileName, (err, data) => {
      if(err) {
        reject(err);
        return;
      }
      keyBind.fileData = toArrayBuffer(data);
      resolve(keyBind);
    });
  });
}

/*
converter
*/
function stringToKeyBind(data) {
  let obj = cson.parse(unicodeEscapeSequenceToChar(data));

  if(obj instanceof Error) {
    return obj;
  }
  if( obj.hasOwnProperty("keyBinds") === false || Object.prototype.toString.call(obj.keyBinds) !== '[object Array]') {
    return new Error(`file is invalid.`);
  }

  let keyBinds = [];

  for(let i = 0; i < obj.keyBinds.length; ++i) {
    let keyBind = new KeyBind();

    shallowCopyTargetProperty(keyBind, obj.keyBinds[i]);
    if(keyBind.name === '' || typeof keyBind.name !== 'string'
      || typeof keyBind.keyCode !== 'number'
      || typeof keyBind.shift !== 'boolean'
      || typeof keyBind.ctrl !== 'boolean'
      || typeof keyBind.alt !== 'boolean'
    ) {
      return new Error(`${i}th data is invalid.` + keyBind.toString());
    }
    keyBinds.push(keyBind);
  }
  return keyBinds;
}

function stringToSourceFile(data, path) {
  let obj = cson.parse(unicodeEscapeSequenceToChar(data));

  if(obj instanceof Error) {
    return obj;
  }
  if(obj.hasOwnProperty("commonPath") === false || typeof obj.commonPath !== 'string'
    || obj.hasOwnProperty("sourceFiles") === false || Object.prototype.toString.call(obj.sourceFiles) !== '[object Array]') {
    return new Error(`file is invalid.`);
  }
  if(obj.commonPath === '') {
    obj.commonPath = path;
  }

  let sourceFiles = [];

  for(let i = 0; i < obj.sourceFiles.length; ++i) {
    let sourceFile = new SourceFile();

    shallowCopyTargetProperty(sourceFile, obj.sourceFiles[i]);
    if(typeof sourceFile.name !== 'string'
      || sourceFile.fileName === '' || typeof sourceFile.fileName !== 'string'
    ) {
      return new Error(`${i}th data is invalid.` + sourceFile.toString());
    }
    if(sourceFile.name === '') {
      sourceFile.name = sourceFile.fileName;
    }
    sourceFile.fileName = obj.commonPath + '\\' + sourceFile.fileName
    sourceFiles.push(sourceFile);
  }
  return sourceFiles;
}

function stringToIR(data, path) {
  let obj = cson.parse(unicodeEscapeSequenceToChar(data));

  if(obj instanceof Error) {
    return obj;
  }
  if(obj.hasOwnProperty("commonPath") === false || typeof obj.commonPath !== 'string'
    || obj.hasOwnProperty("impulseResponses") === false || Object.prototype.toString.call(obj.impulseResponses) !== '[object Array]') {
    return new Error(`file is invalid.`);
  }
  if(obj.commonPath === '') {
    obj.commonPath = path;
  }

  let impulseResponses = [];

  for(let i = 0; i < obj.impulseResponses.length; ++i) {
    let impulseResponse = new ImpulseResponse();

    shallowCopyTargetProperty(impulseResponse, obj.impulseResponses[i]);
    if(typeof impulseResponse.description !== 'string'
      || typeof impulseResponse.fileName !== 'string'
    ) {
      return new Error(`${i}th data is invalid.` + impulseResponse.toString());
    }
    impulseResponse.fileName = obj.commonPath + '\\' + impulseResponse.fileName
    impulseResponses.push(impulseResponse);
  }
  return impulseResponses;
}

/*
export output
*/

export function writeToCson(path, obj, resolved, rejected) {
  let data = cson.createCSONString(obj, {indent: "  "});
  let promise = new Promise( (resolve, reject) => {
    fs.writeFile(path, data, (err) => {
      if(err) {
        reject(err);
        return
      }
      resolve(path);
    });
  });
  promise.then(resolved, rejected);
}

export function writeAnalysedData(path, data, resolved, rejected) {
  let writeString = '';
  for(let i = 0; i < data.length; ++i) {
    writeString += data[i].toString(10) + '\n';
  }
  let promise = new Promise( (resolve, reject) => {
    fs.writeFile(path, writeString, (err) => {
      if(err) {
        reject(err);
        return
      }
      resolve(path);
    });
  });
  promise.then(resolved, rejected);
}

export function writeBinaryData(path, data) {
  let buf = toBuffer(data);
  let promise = new Promise( (resolve, reject) => {
    fs.writeFile(path, buf, (err) => {
      if(err) {
        reject(err);
        return
      }
      resolve(path);
    });
  });
  return promise;
}
<commit_msg>use path.join()<commit_after>'use babel';

import {shallowCopyTargetProperty, unicodeEscapeSequenceToChar, toArrayBuffer, toBuffer } from './utility';

import fs from 'fs';
import cson from 'cson';
import path from 'path';

/*
class
*/

export class SourceFile {
  constructor() {
    this.name = '';
    this.fileName = '';
    this.fileData = null;
  }

  toString() {
    let ret = 'name: ' + this.name;
    ret += ', fileName: ' + this.fileName;
    return ret;
  }
}

export class KeyBind {
  constructor() {
    this.keyCode = -1;
    this.shift = false;
    this.ctrl = false;
    this.alt = false;
    this.name = '';
    this.fileData = null;
  }

  toString() {
    let ret = 'keyCode: ' + this.keyCode.toString(10);
    ret += ', name: ' + this.name;
    ret += ', shift: ' + String(this.shift);
    ret += ', ctrl: ' + String(this.ctrl);
    ret += ', alt: ' + String(this.alt);
    return ret;
  }
}

export class ImpulseResponse {
  constructor() {
    this.description = '';
    this.fileName = '';
    this.fileData = null;
  }

  toString() {
    let ret = 'description: ' + this.description;
    ret += ', fileName: ' + this.fileName;
    return ret;
  }
}
/*
export read
*/
export function readSource(filePath, dataPath) {
  return readConfigFile(filePath, dataPath, stringToSourceFile);
}
export function getReadSource(filePath, dataPath) {
  return () => {
    return readSource(filePath, dataPath);
  }
}

export function readIR(filePath, dataPath) {
  return readConfigFile(filePath, dataPath, stringToIR);
}
export function getReadIR(filePath, dataPath) {
  return () => {
    return readIR(filePath, dataPath);
  }
}

export function readKeyBind(filePath) {
  if(filePath === null) {
    return null;
  }
  return createReadFilePromise(filePath, null, stringToKeyBind);
}

//
function createReadFilePromise(filePath, dataPath, converter) {
  return new Promise ( (resolve, reject)=> {
    fs.readFile(filePath, 'utf-8', (err, data) => {
      if(err) {
        reject([err, 0]);
        return;
      }
      let result = converter(data, dataPath);
      if(result instanceof Error) {
        reject([result, 1]);
        return;
      }
      resolve(result);
    });
  });
}

//
function readConfigFile(filePath, dataPath, converter) {
  if(filePath === null) {
    return null;
  }
  let promise = createReadFilePromise(filePath, dataPath, converter);
  return promise.then(readBinaryDatas); //返るのは、readBinaryDatasのpromise
}

function readBinaryDatas(keyBinds) {
  let promises = [];
  for(let keyBind of keyBinds) {
    promises.push(readBinaryData(keyBind));
  }
  return Promise.all(promises);
}

function readBinaryData(keyBind) {
  return new Promise( (resolve, reject) => {
    fs.readFile(keyBind.fileName, (err, data) => {
      if(err) {
        reject(err);
        return;
      }
      keyBind.fileData = toArrayBuffer(data);
      resolve(keyBind);
    });
  });
}

/*
converter
*/
function stringToKeyBind(data) {
  let obj = cson.parse(unicodeEscapeSequenceToChar(data));

  if(obj instanceof Error) {
    return obj;
  }
  if( obj.hasOwnProperty("keyBinds") === false || Object.prototype.toString.call(obj.keyBinds) !== '[object Array]') {
    return new Error(`file is invalid.`);
  }

  let keyBinds = [];

  for(let i = 0; i < obj.keyBinds.length; ++i) {
    let keyBind = new KeyBind();

    shallowCopyTargetProperty(keyBind, obj.keyBinds[i]);
    if(keyBind.name === '' || typeof keyBind.name !== 'string'
      || typeof keyBind.keyCode !== 'number'
      || typeof keyBind.shift !== 'boolean'
      || typeof keyBind.ctrl !== 'boolean'
      || typeof keyBind.alt !== 'boolean'
    ) {
      return new Error(`${i}th data is invalid.` + keyBind.toString());
    }
    keyBinds.push(keyBind);
  }
  return keyBinds;
}

function stringToSourceFile(data, commonPath) {
  let obj = cson.parse(unicodeEscapeSequenceToChar(data));

  if(obj instanceof Error) {
    return obj;
  }
  if(obj.hasOwnProperty("commonPath") === false || typeof obj.commonPath !== 'string'
    || obj.hasOwnProperty("sourceFiles") === false || Object.prototype.toString.call(obj.sourceFiles) !== '[object Array]') {
    return new Error(`file is invalid.`);
  }
  if(obj.commonPath === '') {
    obj.commonPath = commonPath;
  }

  let sourceFiles = [];

  for(let i = 0; i < obj.sourceFiles.length; ++i) {
    let sourceFile = new SourceFile();

    shallowCopyTargetProperty(sourceFile, obj.sourceFiles[i]);
    if(typeof sourceFile.name !== 'string'
      || sourceFile.fileName === '' || typeof sourceFile.fileName !== 'string'
    ) {
      return new Error(`${i}th data is invalid.` + sourceFile.toString());
    }
    if(sourceFile.name === '') {
      sourceFile.name = sourceFile.fileName;
    }
    sourceFile.fileName = path.join(obj.commonPath, sourceFile.fileName);
    sourceFiles.push(sourceFile);
  }
  return sourceFiles;
}

function stringToIR(data, commonPath) {
  let obj = cson.parse(unicodeEscapeSequenceToChar(data));

  if(obj instanceof Error) {
    return obj;
  }
  if(obj.hasOwnProperty("commonPath") === false || typeof obj.commonPath !== 'string'
    || obj.hasOwnProperty("impulseResponses") === false || Object.prototype.toString.call(obj.impulseResponses) !== '[object Array]') {
    return new Error(`file is invalid.`);
  }
  if(obj.commonPath === '') {
    obj.commonPath = commonPath;
  }

  let impulseResponses = [];

  for(let i = 0; i < obj.impulseResponses.length; ++i) {
    let impulseResponse = new ImpulseResponse();

    shallowCopyTargetProperty(impulseResponse, obj.impulseResponses[i]);
    if(typeof impulseResponse.description !== 'string'
      || typeof impulseResponse.fileName !== 'string'
    ) {
      return new Error(`${i}th data is invalid.` + impulseResponse.toString());
    }
    impulseResponse.fileName = path.join(obj.commonPath, impulseResponse.fileName);
    impulseResponses.push(impulseResponse);
  }
  return impulseResponses;
}

/*
export output
*/

export function writeToCson(filePath, obj, resolved, rejected) {
  let data = cson.createCSONString(obj, {indent: "  "});
  let promise = new Promise( (resolve, reject) => {
    fs.writeFile(filePath, data, (err) => {
      if(err) {
        reject(err);
        return
      }
      resolve(filePath);
    });
  });
  promise.then(resolved, rejected);
}

export function writeAnalysedData(filePath, data, resolved, rejected) {
  let writeString = '';
  for(let i = 0; i < data.length; ++i) {
    writeString += data[i].toString(10) + '\n';
  }
  let promise = new Promise( (resolve, reject) => {
    fs.writeFile(filePath, writeString, (err) => {
      if(err) {
        reject(err);
        return
      }
      resolve(filePath);
    });
  });
  promise.then(resolved, rejected);
}

export function writeBinaryData(filePath, data) {
  let buf = toBuffer(data);
  let promise = new Promise( (resolve, reject) => {
    fs.writeFile(filePath, buf, (err) => {
      if(err) {
        reject(err);
        return
      }
      resolve(filePath);
    });
  });
  return promise;
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var assert = require('assert');
var GetOpt = require('node-getopt');
var S = require('string');

/*
 * Create and return a new branch object for the task tree.
 */
var createBranch = function(name) {
  return {
    name: name,
    children: [],
    __isBranch: true
  };
};

/*
 * The task tree object.
 */
var taskTree = createBranch('root');

/*
 * Is object a branch?
 */
var isBranch = exports.isBranch = function(obj) {
  return obj.__isBranch;
};

/*
 * Is object a task?
 */
var isTask = exports.isTask = function(obj) {
  return obj.__isTask;
};

/*
 * Is object either a branch or a task?
 */
var isBranchOrTask = function(obj) {
  return isBranch(obj) || isTask(obj);
};

/*
 * Is object an argv object?
 */
var isArgv = exports.isArgv = function(obj) {
  return Array.isArray(obj);
};

/*
 * Return name of branch or task.
 */
var getName = exports.getName = function(obj) {
  if (isBranch(obj)) {
    return obj.name;
  } else if (isTask(obj)) {
    if (obj.path.length < 1) {
      assert(false);
    }
    return obj.path[0];
  } else {
    assert(false);
  }
};

/*
 * Standard compare function to apply to either branch or task.
 */
var compareBranchOrTask = function(x, y) {
  var xName = getName(x);
  var yName = getName(y);

  if (typeof xName !== 'string' || xName.length < 1) {
    assert(false);
  }
  if (typeof yName !== 'string' || yName.length < 1) {
    assert(false);
  }

  if (xName === yName) {
    return 0;
  } else if (xName < yName) {
    return -1;
  } else if (xName > yName) {
    return 1;
  } else {
    assert(false);
  }
};

/*
 * Search a branch's children for a branch or task with a given name.
 */
var findChild = function(branch, name) {
  return _.find(branch.children, function(child) {
    return getName(child) === name;
  });
};

/*
 * Check if branch contains a child with a name/path.
 */
var branchContainsChild = function(branch, obj) {
  var names = _.map(branch.children, getName);
  var name = getName(obj);
  return _.contains(names, name);
};

/*
 * Add a task or branch to an existing branch.
 */
var addChild = function(branch, child) {
  // Validate.
  if (!isBranch(branch)) {
    assert(false);
  }
  if (!isBranchOrTask(child)) {
    assert(false);
  }

  // Branch already contains a child with the same name.
  if (branchContainsChild(branch, child)) {
    assert(false);
  }

  // Add child and then sort children.
  branch.children.push(child);
  branch.children.sort(compareBranchOrTask);
};

/*
 * Add a task to the root of the task tree.
 */
var addTask = function(task) {

  // Validate input.
  if (!isTask(task)) {
    throw new TypeError('Invalid task: ' + task);
  }
  if (task.path.length === 0) {
    throw new TypeError('Invalid task path: ' + task) ;
  }

  // Recursive function for placing task in task tree.
  var rec = function(node, path) {

    // Validate.
    if (!isBranch(node)) {
      assert(false);
    }
    if (path.length === 0) {
      assert(false);
    }

    // Partition head and tail.
    var hd = path[0];
    var tl = path.slice(1);

    // Find a child of node that matches path.
    var cursor = _.find(node.children, function(obj) {
      return getName(obj) === hd;
    });

    // Conflicting tasks exist in task tree.
    if (cursor && isTask(cursor)) {
      assert(false);
    }

    // Continue.
    if (tl.length === 0) {

      // We are at the end of the path.
      task.path = [hd];

      if (cursor) {

        addChild(cursor, task);

      } else {

        addChild(node, task);

      }

      return;

    } else {

      // We are NOT at the end of the path.
      if (!cursor) {

        cursor = createBranch(hd);
        addChild(node, cursor);

      }

      return rec(cursor, tl);

    }

  };

  // Kick off recursive function for placing task in task tree.
  rec(taskTree, task.path);

};

/*
 * Clear the task tree.
 */
var clearTasks = exports.clearTasks = function() {
  throw new Error('Not implemented.');
};

/*
 * Blueprint that every task will be cloned from.
 */
var blueprint = {
  path: [],
  description: null,
  allowArgv: false,
  options: [],
  func: null,
  __isTask: true
};

/*
 * Pretty print object in a nicer way.
 */
var pp = function(obj) {
  JSON.stringify(obj);
};

/*
 * Validation function for a task.
 */
var invariant = function(task) {

  // Validate __isTask.
  if (!isTask(task)) {
    throw new TypeError('Invalid task: ' + pp(task));
  }

  // Validate path.
  if (!Array.isArray(task.path)) {
    throw new TypeError('Invalid task path: ' + pp(task));
  }
  if (task.path.length === 0) {
    throw new TypeError('Empty task path: ' + pp(task));
  }
  task.path.forEach(function(atom) {
    if (typeof atom !== 'string') {
      throw new TypeError('Invalid task path: ' + pp(task));
    }
  });

  // Validate description.
  if (typeof task.description !== 'string' || task.description.length === 0) {
    throw new TypeError('Invalid task description: ' + pp(task));
  }

  // Validate allowArgv.
  if (typeof task.allowArgv !== 'boolean') {
    throw new TypeError('Invalid task allowArgv: ' + pp(task));
  }

  // @todo: validate options.

  // Validate function.
  if (typeof task.func !== 'function') {
    throw new TypeError('Invalid task function: ' + pp(task));
  }
  if (task.func.length > 1) {
    throw new TypeError('Wrong number of arguments in task function: ' +
      task.func.toString());
  }

};

/*
 * Take blueprint and clone it to produce a new object reference.
 */
var cloneBlueprint = function() {
  return _.cloneDeep(blueprint);
};

/*
 * Add needed default stuff to a build task.
 */
var decorate = function(task) {
  var helpOption = _.find(task.options, function(option) {
    return option[1] === 'khelp';
  });

  if (!helpOption) {
    task.options.push(['', 'khelp', 'Display usage and options.']);
  }
};

/*
 * Add new task to the task tree.
 */
var registerTask = exports.registerTask = function(handler) {
  if (typeof handler !== 'function') {
    throw new TypeError('Invalid task registration handler: ' + handler);
  }

  var task = cloneBlueprint();
  if (handler.length === 1) {

    handler(task);
    invariant(task);
    decorate(task);
    addTask(task);

  } else if (handler.length === 2) {

    var timeout = setTimeout(function() {
      throw new Error('Task registration done callback was not called: ' +
        task.path);
    }, 2 * 60 * 1000);

    handler(task, function() {
      clearTimeout(timeout);
      invariant(task);
      decorate(task);
      addTask(task);
    });

  } else {

    throw new TypeError('Invalid task registration handler: ' +
      handler.toString());

  }

};

/*
 * Shortcut alias.
 */
exports.add = registerTask;

/*
 * Return the task tree.
 */
var getTaskTree = exports.getTaskTree = function() {
  return taskTree;
};

/*
 * Search a task tree using a given argv, return the found task and
 * slice the used portion from the argv.
 */
var find = exports.find = function(tree, argv) {

  // Optional argument handling.
  if (argv === undefined && isArgv(tree)) {
    argv = tree;
    tree = getTaskTree();
  }

  // Validate.
  if (!isBranch(tree)) {
    throw new TypeError('Expected a branch: ' + tree);
  }
  if (!isArgv(argv)) {
    throw new TypeError('Invalid argv object: ' + pp(argv));
  }

  // Recursive function for searching the task tree.
  var rec = function(node, argv) {

    if (argv.length === 0) {

      // Reached end of argv path, so return current node.
      node.argv = argv;
      return node;

    } else if (isTask(node)) {

      // A task has been found along the argv path, so return it.
      node.argv = argv;
      return node;

    } else if (isBranch(node)) {

      // Get the head of the argv path.
      var hd = argv[0];

      // Find a child of node branch that matches hd.
      var child = findChild(node, hd);

      if (child) {

        // Update the argv path and then recurse with child that was found.
        return rec(child, argv.slice(1));

      } else {

        // No matching child was found, it's a dead search so return null.
        return null;

      }

    } else {

      // This should never happen.
      assert(false);

    }

  };

  // Kick off the recursive function with the task tree.
  return rec(tree, argv);
};

var getHelpMessage = function(task) {
  return 'Usage: ' + getName(task) + ' [OPTION]\n' +
    '\n' +
    '[[OPTIONS]]\n';
};

/*
 * Run a task's function.
 */
var run = exports.run = function(task, callback) {

  // Validate.
  if (!isTask(task)) {
    throw new TypeError('Invalid task object: ' + pp(task));
  }
  if (!isArgv(task.argv)) {
    throw new TypeError('Invalid argv object: ' + pp(task.argv));
  }
  if (typeof callback !== 'function') {
    throw new TypeError('Invalid callback function: ' + pp(callback));
  }

  // Parse cli options.
  var parser = new GetOpt(task.options);
  var argv = parser.parse(task.argv);

  // Display task help message instead of running.
  if (argv.options.khelp) {
    parser.setHelp(getHelpMessage(task));
    console.log(parser.getHelp());
    return process.exit(1);
  }

  // Build context for task function.
  var context = {
    argv: argv.argv,
    options: argv.options,
    task: task
  };

  // Enforce task.allowArgv.
  if (!task.allowArg && context.argv.length > 0) {
    var helpMessage = '\nInvalid argument: ' + context.argv[0] + '\n\n' +
      getHelpMessage(task);
    parser.setHelp(helpMessage);
    console.log(parser.getHelp());
    return process.exit(1);
  }

  // Find out if task function is asynchronous.
  var isAsync = task.func.length === 1;
  if (task.func.length > 1) {
    // This should never happen.
    assert(false);
  }

  if (isAsync) {

    // Treat task function as asynchronous.
    task.func.apply(context, [callback]);

  } else {

    // Treat task function as synchronous.
    task.func.apply(context, []);
    callback();

  }

};

/*
 * Return string representation of task tree.
 */
var getMenu = exports.getMenu = function(branch) {

  // Validate.
  if (!isBranch(branch)) {
    throw new TypeError('Invalid branch: ' + pp(branch));
  }

  var tabSpacing = 4;

  var header = '--- Command Menu ---\n';

  var output = header;

  var append = function(s, depth) {
    output += S(' ').repeat(depth * tabSpacing).s;
    if (Array.isArray(s)) {
      output += s.join(S(' ').repeat(tabSpacing * 2).s);
    } else if (typeof s === 'string') {
      output += s;
    } else {
      assert(false);
    }
    output += '\n';
  };

  var rec = function(node, depth) {
    if (isTask(node)) {
      append([getName(node), node.description], depth);
    } else if (isBranch(node)) {
      append(getName(node), depth);
      node.children.forEach(function(child) {
        rec(child, depth + 1);
      });
    } else {
      assert(false);
    }

  };
  branch.children.forEach(function(child) {
    rec(child, 0);
  });

  return output;

};

/*
 * Display menu from branch's reference point, and then exit(1).
 */
var showMenu = exports.showMenu = function(branch) {
  // Display menu.
  console.log(getMenu(branch));
  // Exit the process.
  process.exit(1);
};
<commit_msg>Cleaned up taskNew.js#getMenu().<commit_after>'use strict';

var _ = require('lodash');
var assert = require('assert');
var GetOpt = require('node-getopt');
var S = require('string');

/*
 * Create and return a new branch object for the task tree.
 */
var createBranch = function(name) {
  return {
    name: name,
    children: [],
    __isBranch: true
  };
};

/*
 * The task tree object.
 */
var taskTree = createBranch('root');

/*
 * Is object a branch?
 */
var isBranch = exports.isBranch = function(obj) {
  return obj.__isBranch;
};

/*
 * Is object a task?
 */
var isTask = exports.isTask = function(obj) {
  return obj.__isTask;
};

/*
 * Is object either a branch or a task?
 */
var isBranchOrTask = function(obj) {
  return isBranch(obj) || isTask(obj);
};

/*
 * Is object an argv object?
 */
var isArgv = exports.isArgv = function(obj) {
  return Array.isArray(obj);
};

/*
 * Return name of branch or task.
 */
var getName = exports.getName = function(obj) {
  if (isBranch(obj)) {
    return obj.name;
  } else if (isTask(obj)) {
    if (obj.path.length < 1) {
      assert(false);
    }
    return obj.path[0];
  } else {
    assert(false);
  }
};

/*
 * Standard compare function to apply to either branch or task.
 */
var compareBranchOrTask = function(x, y) {
  var xName = getName(x);
  var yName = getName(y);

  if (typeof xName !== 'string' || xName.length < 1) {
    assert(false);
  }
  if (typeof yName !== 'string' || yName.length < 1) {
    assert(false);
  }

  if (xName === yName) {
    return 0;
  } else if (xName < yName) {
    return -1;
  } else if (xName > yName) {
    return 1;
  } else {
    assert(false);
  }
};

/*
 * Search a branch's children for a branch or task with a given name.
 */
var findChild = function(branch, name) {
  return _.find(branch.children, function(child) {
    return getName(child) === name;
  });
};

/*
 * Check if branch contains a child with a name/path.
 */
var branchContainsChild = function(branch, obj) {
  var names = _.map(branch.children, getName);
  var name = getName(obj);
  return _.contains(names, name);
};

/*
 * Add a task or branch to an existing branch.
 */
var addChild = function(branch, child) {
  // Validate.
  if (!isBranch(branch)) {
    assert(false);
  }
  if (!isBranchOrTask(child)) {
    assert(false);
  }

  // Branch already contains a child with the same name.
  if (branchContainsChild(branch, child)) {
    assert(false);
  }

  // Add child and then sort children.
  branch.children.push(child);
  branch.children.sort(compareBranchOrTask);
};

/*
 * Add a task to the root of the task tree.
 */
var addTask = function(task) {

  // Validate input.
  if (!isTask(task)) {
    throw new TypeError('Invalid task: ' + task);
  }
  if (task.path.length === 0) {
    throw new TypeError('Invalid task path: ' + task) ;
  }

  // Recursive function for placing task in task tree.
  var rec = function(node, path) {

    // Validate.
    if (!isBranch(node)) {
      assert(false);
    }
    if (path.length === 0) {
      assert(false);
    }

    // Partition head and tail.
    var hd = path[0];
    var tl = path.slice(1);

    // Find a child of node that matches path.
    var cursor = _.find(node.children, function(obj) {
      return getName(obj) === hd;
    });

    // Conflicting tasks exist in task tree.
    if (cursor && isTask(cursor)) {
      assert(false);
    }

    // Continue.
    if (tl.length === 0) {

      // We are at the end of the path.
      task.path = [hd];

      if (cursor) {

        addChild(cursor, task);

      } else {

        addChild(node, task);

      }

      return;

    } else {

      // We are NOT at the end of the path.
      if (!cursor) {

        cursor = createBranch(hd);
        addChild(node, cursor);

      }

      return rec(cursor, tl);

    }

  };

  // Kick off recursive function for placing task in task tree.
  rec(taskTree, task.path);

};

/*
 * Clear the task tree.
 */
var clearTasks = exports.clearTasks = function() {
  throw new Error('Not implemented.');
};

/*
 * Blueprint that every task will be cloned from.
 */
var blueprint = {
  path: [],
  description: null,
  allowArgv: false,
  options: [],
  func: null,
  __isTask: true
};

/*
 * Pretty print object in a nicer way.
 */
var pp = function(obj) {
  JSON.stringify(obj);
};

/*
 * Validation function for a task.
 */
var invariant = function(task) {

  // Validate __isTask.
  if (!isTask(task)) {
    throw new TypeError('Invalid task: ' + pp(task));
  }

  // Validate path.
  if (!Array.isArray(task.path)) {
    throw new TypeError('Invalid task path: ' + pp(task));
  }
  if (task.path.length === 0) {
    throw new TypeError('Empty task path: ' + pp(task));
  }
  task.path.forEach(function(atom) {
    if (typeof atom !== 'string') {
      throw new TypeError('Invalid task path: ' + pp(task));
    }
  });

  // Validate description.
  if (typeof task.description !== 'string' || task.description.length === 0) {
    throw new TypeError('Invalid task description: ' + pp(task));
  }

  // Validate allowArgv.
  if (typeof task.allowArgv !== 'boolean') {
    throw new TypeError('Invalid task allowArgv: ' + pp(task));
  }

  // @todo: validate options.

  // Validate function.
  if (typeof task.func !== 'function') {
    throw new TypeError('Invalid task function: ' + pp(task));
  }
  if (task.func.length > 1) {
    throw new TypeError('Wrong number of arguments in task function: ' +
      task.func.toString());
  }

};

/*
 * Take blueprint and clone it to produce a new object reference.
 */
var cloneBlueprint = function() {
  return _.cloneDeep(blueprint);
};

/*
 * Add needed default stuff to a build task.
 */
var decorate = function(task) {
  var helpOption = _.find(task.options, function(option) {
    return option[1] === 'khelp';
  });

  if (!helpOption) {
    task.options.push(['', 'khelp', 'Display usage and options.']);
  }
};

/*
 * Add new task to the task tree.
 */
var registerTask = exports.registerTask = function(handler) {
  if (typeof handler !== 'function') {
    throw new TypeError('Invalid task registration handler: ' + handler);
  }

  var task = cloneBlueprint();
  if (handler.length === 1) {

    handler(task);
    invariant(task);
    decorate(task);
    addTask(task);

  } else if (handler.length === 2) {

    var timeout = setTimeout(function() {
      throw new Error('Task registration done callback was not called: ' +
        task.path);
    }, 2 * 60 * 1000);

    handler(task, function() {
      clearTimeout(timeout);
      invariant(task);
      decorate(task);
      addTask(task);
    });

  } else {

    throw new TypeError('Invalid task registration handler: ' +
      handler.toString());

  }

};

/*
 * Shortcut alias.
 */
exports.add = registerTask;

/*
 * Return the task tree.
 */
var getTaskTree = exports.getTaskTree = function() {
  return taskTree;
};

/*
 * Search a task tree using a given argv, return the found task and
 * slice the used portion from the argv.
 */
var find = exports.find = function(tree, argv) {

  // Optional argument handling.
  if (argv === undefined && isArgv(tree)) {
    argv = tree;
    tree = getTaskTree();
  }

  // Validate.
  if (!isBranch(tree)) {
    throw new TypeError('Expected a branch: ' + tree);
  }
  if (!isArgv(argv)) {
    throw new TypeError('Invalid argv object: ' + pp(argv));
  }

  // Recursive function for searching the task tree.
  var rec = function(node, argv) {

    if (argv.length === 0) {

      // Reached end of argv path, so return current node.
      node.argv = argv;
      return node;

    } else if (isTask(node)) {

      // A task has been found along the argv path, so return it.
      node.argv = argv;
      return node;

    } else if (isBranch(node)) {

      // Get the head of the argv path.
      var hd = argv[0];

      // Find a child of node branch that matches hd.
      var child = findChild(node, hd);

      if (child) {

        // Update the argv path and then recurse with child that was found.
        return rec(child, argv.slice(1));

      } else {

        // No matching child was found, it's a dead search so return null.
        return null;

      }

    } else {

      // This should never happen.
      assert(false);

    }

  };

  // Kick off the recursive function with the task tree.
  return rec(tree, argv);
};

var getHelpMessage = function(task) {
  return 'Usage: ' + getName(task) + ' [OPTION]\n' +
    '\n' +
    '[[OPTIONS]]\n';
};

/*
 * Run a task's function.
 */
var run = exports.run = function(task, callback) {

  // Validate.
  if (!isTask(task)) {
    throw new TypeError('Invalid task object: ' + pp(task));
  }
  if (!isArgv(task.argv)) {
    throw new TypeError('Invalid argv object: ' + pp(task.argv));
  }
  if (typeof callback !== 'function') {
    throw new TypeError('Invalid callback function: ' + pp(callback));
  }

  // Parse cli options.
  var parser = new GetOpt(task.options);
  var argv = parser.parse(task.argv);

  // Display task help message instead of running.
  if (argv.options.khelp) {
    parser.setHelp(getHelpMessage(task));
    console.log(parser.getHelp());
    return process.exit(1);
  }

  // Build context for task function.
  var context = {
    argv: argv.argv,
    options: argv.options,
    task: task
  };

  // Enforce task.allowArgv.
  if (!task.allowArg && context.argv.length > 0) {
    var helpMessage = '\nInvalid argument: ' + context.argv[0] + '\n\n' +
      getHelpMessage(task);
    parser.setHelp(helpMessage);
    console.log(parser.getHelp());
    return process.exit(1);
  }

  // Find out if task function is asynchronous.
  var isAsync = task.func.length === 1;
  if (task.func.length > 1) {
    // This should never happen.
    assert(false);
  }

  if (isAsync) {

    // Treat task function as asynchronous.
    task.func.apply(context, [callback]);

  } else {

    // Treat task function as synchronous.
    task.func.apply(context, []);
    callback();

  }

};

/*
 * Return string representation of task tree.
 */
var getMenu = exports.getMenu = function(branch) {

  // Validate.
  if (!isBranch(branch)) {
    throw new TypeError('Invalid branch: ' + pp(branch));
  }

  var tabSpacing = 4;

  var header = '--- Command Menu ---\n';

  // Recursively build list of lines.
  var lines = [];
  var rec = function(node, depth) {
    if (isTask(node)) {
      lines.push({
        depth: depth,
        name: getName(node),
        description: node.description
      });
    } else if (isBranch(node)) {
      lines.push({
        depth: depth,
        name: getName(node)
      });
      node.children.forEach(function(child) {
        rec(child, depth + 1);
      });
    } else {
      assert(false);
    }
  };
  branch.children.forEach(function(child) {
    rec(child, 0);
  });

  lines = _.map(lines, function(line) {
    var depthPrefix = S(' ').repeat(tabSpacing * line.depth).s;
    var name = depthPrefix + line.name;
    return {
      name: name,
      description: line.description
    };
  });

  var widths = _.map(lines, function(line) {
    return line.name.length;
  });
  var maxWidth = _.max(widths);

  lines = _.map(lines, function(line) {
    var name = S(line.name).padRight(maxWidth).s;
    if (line.description) {
      return [name, line.description].join(S(' ').repeat(tabSpacing).s);
    } else {
      return name;
    }
  });

  lines.unshift(header);
  lines.push('');
  return lines.join('\n');

};

/*
 * Display menu from branch's reference point, and then exit(1).
 */
var showMenu = exports.showMenu = function(branch) {
  // Display menu.
  console.log(getMenu(branch));
  // Exit the process.
  process.exit(1);
};
<|endoftext|>