<commit_before>var base        = require('taskcluster-base');
var debug       = require('debug')('hooks:routes:v1');
var Promise     = require('promise');
var taskcluster = require('taskcluster-client');
var nextDate    = require('../hooks/nextdate');
var _           = require('lodash');

var api = new base.API({
  title:         "Hooks API Documentation",
  description:   [
    "Hooks are a mechanism for creating tasks in response to events.",
    "",
    "Hooks are identified with a `hookGroupId` and a `hookId`.",
    "",
    "When an event occurs, the resulting task is automatically created.  The",
    "task is created using the scope `assume:hook-id:<hookGroupId>/<hookId>`,",
    "which must have scopes to make the createTask call, including satisfying all",
    "scopes in `task.scopes`.",
    "",
    "Hooks can have a 'schedule' indicating specific times that new tasks should",
    "be created.  Each schedule is in a simple cron format, per ",
    "https://www.npmjs.com/package/cron-parser.  For example:",
    " * `[\"0 0 1 * * *\"]` -- daily at 1:00 UTC",
    " * `[\"0 0 9,21 * * 1-5\", \"0 0 12 * * 0,6\"]` -- weekdays at 9:00 and 21:00 UTC, weekends at noon",
  ].join('\n'),
  schemaPrefix:  'http://schemas.taskcluster.net/hooks/v1/'
});

// Export api
module.exports = api;

/** Get hook groups **/
api.declare({
  method:       'get',
  route:        '/hooks',
  name:         'listHookGroups',
  idempotent:   true,
  output:       'list-hook-groups-response.json',
  title:        'List hook groups',
  stability:    'experimental',
  description: [
    "This endpoint will return a list of all hook groups with at least one hook.",
  ].join('\n')
}, async function(req, res) {
  var groups = new Set();
  await this.Hook.scan({},{
    handler: (item) => {
      groups.add(item.hookGroupId);
    }
  });
  return res.reply({groups: Array.from(groups)});
});

/** Get hooks in a given group **/
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId',
  name:         'listHooks',
  idempotent:   true,
  output:       'list-hooks-response.json',
  title:        'List hooks in a given group',
  stability:    'experimental',
  description: [
    "This endpoint will return a list of all the hook definitions within a",
    "given hook group."
  ].join('\n')
}, async function(req, res) {
  var hooks = [];
  await this.Hook.query({
    hookGroupId: req.params.hookGroupId
  }, {
    handler: async (hook) => {
      hooks.push(await hook.definition());
    }
  });
  if (hooks.length == 0) {
    return res.status(404).json({
      message: "No such group"
    });
  }
  return res.reply({hooks: hooks});
});

/** Get hook definition **/
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'hook',
  idempotent:   true,
  output:       'hook-definition.json',
  title:        'Get hook definition',
  stability:    'experimental',
  description: [
    "This endpoint will return the hook defintion for the given `hookGroupId`",
    "and hookId."
  ].join('\n')
}, async function(req, res) {
  let hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Handle the case where the hook doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  // Reply with the hook definition
  let definition = await hook.definition();
  return res.reply(definition);
});

/** Get hook's current status */
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId/status',
  name:         'getHookStatus',
  output:       'hook-status.json',
  title:        'Get hook status',
  stability:    'experimental',
  description: [
    "This endpoint will return the current status of the hook.  This represents a",
    "snapshot in time and may vary from one call to the next."
  ].join('\n')
}, async function(req, res) {
  let hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Handle the case where the hook doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  let reply = {lastFire: hook.lastFire}

  // Return a schedule only if a schedule is defined
  if (hook.schedule.length > 0) {
    reply.nextScheduledDate = hook.nextScheduledDate.toJSON();
    // Remark: nextTaskId cannot be exposed here, it's a secret.
    // If someone could predict the taskId they could use it, breaking this
    // service at best, at worst maybe exploit it to elevate from defineTask
    // to createTask without scope to schedule a task.
  }
  return res.reply(reply);
});

/** Get next scheduled hook date */
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId/schedule',
  name:         'getHookSchedule',
  output:       'hook-schedule.json',
  title:        'Get hook schedule',
  stability:    'experimental',
  description: [
    "This endpoint will return the schedule and next scheduled creation time",
    "for the given hook."
  ].join('\n')
}, async function(req, res) {
  let hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Handle the case where the hook doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  // Return a schedule only if a schedule is defined
  if (hook.schedule.length > 0) {
    return res.reply({
      schedule: hook.schedule,
      nextScheduledDate: hook.nextScheduledDate.toJSON(),
      // Remark: nextTaskId cannot be exposed here, it's a secret.
      // If someone could predict the taskId they could use it, breaking this
      // service at best, at worst maybe exploit it to elevate from defineTask
      // to createTask without scope to schedule a task.
    });
  }
  return res.reply({
    schedule: hook.schedule,
  });
});

/** Create a hook **/
api.declare({
  method:       'put',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'createHook',
  deferAuth:    true,
  idempotent:   true,
  scopes:       [[
                    "hooks:modify-hook:<hookGroupId>/<hookId>",
                    "assume:hook-id:<hookGroupId>/<hookId>",
                ]],
  input:        'create-hook-request.json',
  output:       'hook-definition.json',
  title:        'Create a hook',
  stability:    'experimental',
  description: [
    "This endpoint will create a new hook.",
    "",
    "The caller's credentials must include the role that will be used to",
    "create the task.  That role must satisfy task.scopes as well as the",
    "necessary scopes to add the task to the queue.",
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  var hookDef   = req.body;

  hookDef = _.defaults({hookGroupId, hookId}, hookDef);

  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  // Try to create a Hook entity
  try {
    var hook = await this.Hook.create(
      _.defaults({}, hookDef, {
        bindings:           [], // TODO
        triggerToken:       taskcluster.slugid(),
        lastFire:           {result: 'no-fire'},
        nextTaskId:         taskcluster.slugid(),
        nextScheduledDate:  nextDate(hookDef.schedule)
      }));
  } catch (err) {
    if (!err || err.code !== 'EntityAlreadyExists') {
      throw err;
    }
    let existingHook = await this.Hook.load({hookGroupId, hookId}, true);

    if (!_.isEqual(hookDef, await existingHook.definition())) {
      return res.status(409).json({
        message: "hook `" + hookGroupId + "/" + hookId + "` already exists."
      });
    }
  }

  // Reply with the hook definition
  return res.reply(hookDef);
});

/** Update hook definition**/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'updateHook',
  deferAuth:    true,
  idempotent:   true,
  scopes:       [[
                    "hooks:modify-hook:<hookGroupId>/<hookId>",
                    "assume:hook-id:<hookGroupId>/<hookId>",
                ]],
  input:        'create-hook-request.json',
  output:       'hook-definition.json',
  title:        'Update a hook',
  stability:    'experimental',
  description: [
    "This endpoint will update an existing hook.  All fields except",
    "`hookGroupId` and `hookId` can be modified.",
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId = req.params.hookId;
  var hookDef = req.body;

  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  var hook = await this.Hook.load({hookGroupId, hookId}, true);

  if (!hook) {
    return res.status(404).json({
      message: "Hook not found. " +
        "Use PUT instead of PATCH to create a resource."
    });
  }

  // Attempt to modify properties of the hook
  var schedule = hookDef.schedule ? hookDef.schedule : [];
  await hook.modify((hook) => {
    hook.metadata          = hookDef.metadata;
    hook.task              = hookDef.task;
    hook.deadline          = hookDef.deadline;
    hook.expires           = hookDef.expires ? hookDef.expires : '';
    hook.schedule          = schedule;
    hook.nextTaskId        = taskcluster.slugid();
    hook.nextScheduledDate = nextDate(schedule);
  });

  let definition = await hook.definition();
  return res.reply(definition);
});

/** Delete hook definition**/
api.declare({
  method:       'delete',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'removeHook',
  idempotent:   true,
  deferAuth:    true,
  scopes:       [["hooks:modify-hook:<hookGroupId>/<hookId>"]],
  title:        'Delete a hook',
  stability:    'experimental',
  description: [
    "This endpoint will remove a hook definition."
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId = req.params.hookId;

  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  // Remove the resource if it exists
  await this.Hook.remove({hookGroupId, hookId}, true);

  return res.status(200).json({});
});

// XXX disabled for the first draft of this service
if (0) {

/** Get secret token for a trigger **/
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId/token',
  name:         'getTriggerToken',
  deferAuth:    true,
  scopes:       [["hooks:get-trigger-token:<hookGroupId>/<hookId>"]],
  input:        undefined,
  output:       'trigger-token-response.json',
  title:        'Get a trigger token',
  stability:    'experimental',
  description: [
    "Retrieve a unique secret token for triggering the specified hook. This",
    "token can be deactivated with `resetTriggerToken`."
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  let hook = await this.Hook.load({hookGroupId, hookId}, true);

  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  return res.reply({
    token: hook.triggerToken
  });
});

/** Reset a trigger token **/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId/token',
  name:         'resetTriggerToken',
  deferAuth:    true,
  scopes:       [["hooks:reset-trigger-token:<hookGroupId>/<hookId>"]],
  input:        undefined,
  output:       'trigger-token-response.json',
  title:        'Reset a trigger token',
  stability:    'experimental',
  description: [
    "Reset the token for triggering a given hook. This invalidates token that",
    "may have been issued via getTriggerToken with a new token."
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  let hook = await this.Hook.load({hookGroupId, hookId}, true);

  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  await hook.modify((hook) => {
    hook.triggerToken = taskcluster.slugid();
  });

  return res.reply({
    token: hook.triggerToken
  });
});

/** Trigger hook from a webhook with a token **/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId/trigger/:token',
  name:         'triggerHookWithToken',
  input:        'trigger-payload.json',
  output:       'task-status.json',
  title:        'Trigger a hook with a token',
  stability:    'experimental',
  description: [
    "This endpoint triggers a defined hook with a valid token."
  ].join('\n')
}, async function(req, res) {
  var payload = req.body;

  var hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Return a 404 if the hook entity doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  // Return 401 if the token doesn't exist or doesn't match
  if (req.params.token !== hook.triggerToken) {
    return res.status(401).json({
      message: "Invalid token"
    });
  }

  let resp = await this.taskcreator.fire(hook, payload);
  return res.reply(resp);
});

/** Trigger a hook for debugging **/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId/trigger',
  name:         'triggerHook',
  deferAuth:    true,
  scopes:       [["hooks:trigger-hook:<hookGroupId>/<hookId>"]],
  input:        'trigger-payload.json',
  output:       'task-status.json',
  title:        'Trigger a hook',
  stability:    'experimental',
  description: [
    "Trigger a hook, given that you have the correct scoping for it"
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  var payload = req.body;

  var hook = await this.Hook.load({hookGroupId, hookId}, true);

  // Return a 404 if the hook entity doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  let resp = await this.taskcreator.fire(hook, payload);
  return res.reply(resp);
});
}
<commit_msg>Revert getHookSchedule stability to 'deprecated'<commit_after>var base        = require('taskcluster-base');
var debug       = require('debug')('hooks:routes:v1');
var Promise     = require('promise');
var taskcluster = require('taskcluster-client');
var nextDate    = require('../hooks/nextdate');
var _           = require('lodash');

var api = new base.API({
  title:         "Hooks API Documentation",
  description:   [
    "Hooks are a mechanism for creating tasks in response to events.",
    "",
    "Hooks are identified with a `hookGroupId` and a `hookId`.",
    "",
    "When an event occurs, the resulting task is automatically created.  The",
    "task is created using the scope `assume:hook-id:<hookGroupId>/<hookId>`,",
    "which must have scopes to make the createTask call, including satisfying all",
    "scopes in `task.scopes`.",
    "",
    "Hooks can have a 'schedule' indicating specific times that new tasks should",
    "be created.  Each schedule is in a simple cron format, per ",
    "https://www.npmjs.com/package/cron-parser.  For example:",
    " * `[\"0 0 1 * * *\"]` -- daily at 1:00 UTC",
    " * `[\"0 0 9,21 * * 1-5\", \"0 0 12 * * 0,6\"]` -- weekdays at 9:00 and 21:00 UTC, weekends at noon",
  ].join('\n'),
  schemaPrefix:  'http://schemas.taskcluster.net/hooks/v1/'
});

// Export api
module.exports = api;

/** Get hook groups **/
api.declare({
  method:       'get',
  route:        '/hooks',
  name:         'listHookGroups',
  idempotent:   true,
  output:       'list-hook-groups-response.json',
  title:        'List hook groups',
  stability:    'experimental',
  description: [
    "This endpoint will return a list of all hook groups with at least one hook.",
  ].join('\n')
}, async function(req, res) {
  var groups = new Set();
  await this.Hook.scan({},{
    handler: (item) => {
      groups.add(item.hookGroupId);
    }
  });
  return res.reply({groups: Array.from(groups)});
});

/** Get hooks in a given group **/
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId',
  name:         'listHooks',
  idempotent:   true,
  output:       'list-hooks-response.json',
  title:        'List hooks in a given group',
  stability:    'experimental',
  description: [
    "This endpoint will return a list of all the hook definitions within a",
    "given hook group."
  ].join('\n')
}, async function(req, res) {
  var hooks = [];
  await this.Hook.query({
    hookGroupId: req.params.hookGroupId
  }, {
    handler: async (hook) => {
      hooks.push(await hook.definition());
    }
  });
  if (hooks.length == 0) {
    return res.status(404).json({
      message: "No such group"
    });
  }
  return res.reply({hooks: hooks});
});

/** Get hook definition **/
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'hook',
  idempotent:   true,
  output:       'hook-definition.json',
  title:        'Get hook definition',
  stability:    'experimental',
  description: [
    "This endpoint will return the hook defintion for the given `hookGroupId`",
    "and hookId."
  ].join('\n')
}, async function(req, res) {
  let hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Handle the case where the hook doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  // Reply with the hook definition
  let definition = await hook.definition();
  return res.reply(definition);
});

/** Get hook's current status */
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId/status',
  name:         'getHookStatus',
  output:       'hook-status.json',
  title:        'Get hook status',
  stability:    'experimental',
  description: [
    "This endpoint will return the current status of the hook.  This represents a",
    "snapshot in time and may vary from one call to the next."
  ].join('\n')
}, async function(req, res) {
  let hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Handle the case where the hook doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  let reply = {lastFire: hook.lastFire}

  // Return a schedule only if a schedule is defined
  if (hook.schedule.length > 0) {
    reply.nextScheduledDate = hook.nextScheduledDate.toJSON();
    // Remark: nextTaskId cannot be exposed here, it's a secret.
    // If someone could predict the taskId they could use it, breaking this
    // service at best, at worst maybe exploit it to elevate from defineTask
    // to createTask without scope to schedule a task.
  }
  return res.reply(reply);
});

/** Get next scheduled hook date */
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId/schedule',
  name:         'getHookSchedule',
  output:       'hook-schedule.json',
  title:        'Get hook schedule',
  stability:    'deprecated',
  description: [
    "This endpoint will return the schedule and next scheduled creation time",
    "for the given hook."
  ].join('\n')
}, async function(req, res) {
  let hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Handle the case where the hook doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  // Return a schedule only if a schedule is defined
  if (hook.schedule.length > 0) {
    return res.reply({
      schedule: hook.schedule,
      nextScheduledDate: hook.nextScheduledDate.toJSON(),
      // Remark: nextTaskId cannot be exposed here, it's a secret.
      // If someone could predict the taskId they could use it, breaking this
      // service at best, at worst maybe exploit it to elevate from defineTask
      // to createTask without scope to schedule a task.
    });
  }
  return res.reply({
    schedule: hook.schedule,
  });
});

/** Create a hook **/
api.declare({
  method:       'put',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'createHook',
  deferAuth:    true,
  idempotent:   true,
  scopes:       [[
                    "hooks:modify-hook:<hookGroupId>/<hookId>",
                    "assume:hook-id:<hookGroupId>/<hookId>",
                ]],
  input:        'create-hook-request.json',
  output:       'hook-definition.json',
  title:        'Create a hook',
  stability:    'experimental',
  description: [
    "This endpoint will create a new hook.",
    "",
    "The caller's credentials must include the role that will be used to",
    "create the task.  That role must satisfy task.scopes as well as the",
    "necessary scopes to add the task to the queue.",
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  var hookDef   = req.body;

  hookDef = _.defaults({hookGroupId, hookId}, hookDef);

  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  // Try to create a Hook entity
  try {
    var hook = await this.Hook.create(
      _.defaults({}, hookDef, {
        bindings:           [], // TODO
        triggerToken:       taskcluster.slugid(),
        lastFire:           {result: 'no-fire'},
        nextTaskId:         taskcluster.slugid(),
        nextScheduledDate:  nextDate(hookDef.schedule)
      }));
  } catch (err) {
    if (!err || err.code !== 'EntityAlreadyExists') {
      throw err;
    }
    let existingHook = await this.Hook.load({hookGroupId, hookId}, true);

    if (!_.isEqual(hookDef, await existingHook.definition())) {
      return res.status(409).json({
        message: "hook `" + hookGroupId + "/" + hookId + "` already exists."
      });
    }
  }

  // Reply with the hook definition
  return res.reply(hookDef);
});

/** Update hook definition**/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'updateHook',
  deferAuth:    true,
  idempotent:   true,
  scopes:       [[
                    "hooks:modify-hook:<hookGroupId>/<hookId>",
                    "assume:hook-id:<hookGroupId>/<hookId>",
                ]],
  input:        'create-hook-request.json',
  output:       'hook-definition.json',
  title:        'Update a hook',
  stability:    'experimental',
  description: [
    "This endpoint will update an existing hook.  All fields except",
    "`hookGroupId` and `hookId` can be modified.",
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId = req.params.hookId;
  var hookDef = req.body;

  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  var hook = await this.Hook.load({hookGroupId, hookId}, true);

  if (!hook) {
    return res.status(404).json({
      message: "Hook not found. " +
        "Use PUT instead of PATCH to create a resource."
    });
  }

  // Attempt to modify properties of the hook
  var schedule = hookDef.schedule ? hookDef.schedule : [];
  await hook.modify((hook) => {
    hook.metadata          = hookDef.metadata;
    hook.task              = hookDef.task;
    hook.deadline          = hookDef.deadline;
    hook.expires           = hookDef.expires ? hookDef.expires : '';
    hook.schedule          = schedule;
    hook.nextTaskId        = taskcluster.slugid();
    hook.nextScheduledDate = nextDate(schedule);
  });

  let definition = await hook.definition();
  return res.reply(definition);
});

/** Delete hook definition**/
api.declare({
  method:       'delete',
  route:        '/hooks/:hookGroupId/:hookId',
  name:         'removeHook',
  idempotent:   true,
  deferAuth:    true,
  scopes:       [["hooks:modify-hook:<hookGroupId>/<hookId>"]],
  title:        'Delete a hook',
  stability:    'experimental',
  description: [
    "This endpoint will remove a hook definition."
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId = req.params.hookId;

  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  // Remove the resource if it exists
  await this.Hook.remove({hookGroupId, hookId}, true);

  return res.status(200).json({});
});

// XXX disabled for the first draft of this service
if (0) {

/** Get secret token for a trigger **/
api.declare({
  method:       'get',
  route:        '/hooks/:hookGroupId/:hookId/token',
  name:         'getTriggerToken',
  deferAuth:    true,
  scopes:       [["hooks:get-trigger-token:<hookGroupId>/<hookId>"]],
  input:        undefined,
  output:       'trigger-token-response.json',
  title:        'Get a trigger token',
  stability:    'experimental',
  description: [
    "Retrieve a unique secret token for triggering the specified hook. This",
    "token can be deactivated with `resetTriggerToken`."
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  let hook = await this.Hook.load({hookGroupId, hookId}, true);

  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  return res.reply({
    token: hook.triggerToken
  });
});

/** Reset a trigger token **/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId/token',
  name:         'resetTriggerToken',
  deferAuth:    true,
  scopes:       [["hooks:reset-trigger-token:<hookGroupId>/<hookId>"]],
  input:        undefined,
  output:       'trigger-token-response.json',
  title:        'Reset a trigger token',
  stability:    'experimental',
  description: [
    "Reset the token for triggering a given hook. This invalidates token that",
    "may have been issued via getTriggerToken with a new token."
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  let hook = await this.Hook.load({hookGroupId, hookId}, true);

  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  await hook.modify((hook) => {
    hook.triggerToken = taskcluster.slugid();
  });

  return res.reply({
    token: hook.triggerToken
  });
});

/** Trigger hook from a webhook with a token **/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId/trigger/:token',
  name:         'triggerHookWithToken',
  input:        'trigger-payload.json',
  output:       'task-status.json',
  title:        'Trigger a hook with a token',
  stability:    'experimental',
  description: [
    "This endpoint triggers a defined hook with a valid token."
  ].join('\n')
}, async function(req, res) {
  var payload = req.body;

  var hook = await this.Hook.load({
    hookGroupId: req.params.hookGroupId,
    hookId:      req.params.hookId
  }, true);

  // Return a 404 if the hook entity doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  // Return 401 if the token doesn't exist or doesn't match
  if (req.params.token !== hook.triggerToken) {
    return res.status(401).json({
      message: "Invalid token"
    });
  }

  let resp = await this.taskcreator.fire(hook, payload);
  return res.reply(resp);
});

/** Trigger a hook for debugging **/
api.declare({
  method:       'post',
  route:        '/hooks/:hookGroupId/:hookId/trigger',
  name:         'triggerHook',
  deferAuth:    true,
  scopes:       [["hooks:trigger-hook:<hookGroupId>/<hookId>"]],
  input:        'trigger-payload.json',
  output:       'task-status.json',
  title:        'Trigger a hook',
  stability:    'experimental',
  description: [
    "Trigger a hook, given that you have the correct scoping for it"
  ].join('\n')
}, async function(req, res) {
  var hookGroupId = req.params.hookGroupId;
  var hookId    = req.params.hookId;
  if (!req.satisfies({hookGroupId, hookId})) {
    return;
  }

  var payload = req.body;

  var hook = await this.Hook.load({hookGroupId, hookId}, true);

  // Return a 404 if the hook entity doesn't exist
  if (!hook) {
    return res.status(404).json({
      message: "Hook not found"
    });
  }

  let resp = await this.taskcreator.fire(hook, payload);
  return res.reply(resp);
});
}
<|endoftext|>


<commit_before>var testBase = require('./_test-base');

var BillForward = testBase.BillForward;

context(testBase.getContext(), function () {
	describe('Account', function () {
		describe('::create', function () {
			context('all required entities for chargeable Subscription created', function() {

				var promise;
				before(function() {
					var accountModel = new BillForward.Account({
						'profile': new BillForward.Profile({
							'email': 'u.n.owen@was.her',
		  					'firstName': 'U.N.',
		  					'lastName': 'Owen',
							'addresses': [
								new BillForward.Address({
								    'addressLine1': 'address line 1',
								    'addressLine2': 'address line 2',
								    'addressLine3': 'address line 3',
								    'city': 'London',
								    'country': 'Gensokyo',
								    'province': 'London',
								    'postcode': 'SW1 1AS',
								    'landline': '02000000000',
								    'primaryAddress': true
								})
								]
							})
						});

					var account = BillForward.Account.create(accountModel);

					promise = account;
				});
				it('should have expected deep property', function () {
					return promise
					.should.be.fulfilled;
				});
			});
		});
	});
});<commit_msg>more syntax experimentation<commit_after>var testBase = require('./_test-base');

var BillForward = testBase.BillForward;

context(testBase.getContext(), function () {
	describe('Account', function () {
		describe('::create', function () {
			context('all required entities for chargeable Subscription created', function() {

				var promise;
				before(function() {
					var models = {};
					var promises = {};

					// create an account
					// requires (optionally):
					// - profile
					// - - addresses
					models.address = new BillForward.Address({
					    'addressLine1': 'address line 1',
					    'addressLine2': 'address line 2',
					    'addressLine3': 'address line 3',
					    'city': 'London',
					    'country': 'Gensokyo',
					    'province': 'London',
					    'postcode': 'SW1 1AS',
					    'landline': '02000000000',
					    'primaryAddress': true
					});
					models.profile = new BillForward.Profile({
						'email': 'u.n.owen@was.her',
	  					'firstName': 'U.N.',
	  					'lastName': 'Owen',
						'addresses': [models.address]
					});
					models.account = new BillForward.Account({
						profile: profileModel
					});

					promises.account = BillForward.Account.create(models.account);

					models.creditNote = new BillForward.CreditNote({
						'accountID': account.id,
						'nominalValue': 100,
						'currency': 'USD'
					});

					promises.creditNote = BillForward.CreditNote.create(models.creditNote);

					// create a unit of measure
					models.unitOfMeasure1 = new BillForward.UnitOfMeasure({
						'name': 'CPU',
						'displayedAs': 'Cycles',
						'roundingScheme': 'UP',
					});

					promises.unitOfMeasure1 = BillForward.UnitOfMeasure.create(models.unitOfMeasure1)

					// create another unit of measure
					models.unitOfMeasure2 = new BillForward.UnitOfMeasure({
						'name': 'Bandwidth',
						'displayedAs': 'Mbps',
						'roundingScheme': 'UP',
					});

					promises.unitOfMeasure2 = BillForward.UnitOfMeasure.create(models.unitOfMeasure2)

					promise = account;
				});
				it('should have expected deep property', function () {
					return promise
					.should.be.fulfilled;
				});
			});
		});
	});
});<|endoftext|>


<commit_before>(function (define) {
define(function (require) {
"use strict";

	var when = require('when');

	/**
	 * Returns a network strategy that is a composition of two or more
	 * other strategies.  The strategies are executed in the order
	 * in which they're provided.  If any strategy cancels, the remaining
	 * strategies are never executed and the cancel is sent back to the Hub.
	 *
	 * @param strategies {Array} collection of network strategies.
	 * @return {Function} a composite network strategy function
	 */
	return function composeStrategies (strategies) {
		return function (source, dest, data, type, api) {

			when.reduce(strategies, function(result, strategy) {
				return strategy(source, dest, data, type, api)
			}, data);

		}

	};

});
}(
	typeof define == 'function' && define.amd
		? define
		: function (factory) { module.exports = factory(require); }
));<commit_msg>composeStrategies is now smarter about handling canceled events, and aborts when.reduce early by returning a rejection<commit_after>(function (define) {
define(function (require) {
"use strict";

	var when = require('when');

	/**
	 * Returns a network strategy that is a composition of two or more
	 * other strategies.  The strategies are executed in the order
	 * in which they're provided.  If any strategy cancels, the remaining
	 * strategies are never executed and the cancel is sent back to the Hub.
	 *
	 * @param strategies {Array} collection of network strategies.
	 * @return {Function} a composite network strategy function
	 */
	return function composeStrategies (strategies) {
		return function (source, dest, data, type, api) {

			when.reduce(strategies,
				function(result, strategy) {
					var strategyResult = strategy(source, dest, data, type, api);
					return api.isCanceled()
						? when.reject(strategyResult)
						: strategyResult;
				},
				data
			).always(function(result) { return result });

		}

	};

});
}(
	typeof define == 'function' && define.amd
		? define
		: function (factory) { module.exports = factory(require); }
));<|endoftext|>


<commit_before>//
//    █████╗ ██╗  ██╗██╗██╗     ███████╗███████╗███████╗
//   ██╔══██╗██║ ██╔╝██║██║     ██╔════╝██╔════╝╚══███╔╝
//   ███████║█████╔╝ ██║██║     █████╗  █████╗    ███╔╝
//   ██╔══██║██╔═██╗ ██║██║     ██╔══╝  ██╔══╝   ███╔╝
//   ██║  ██║██║  ██╗██║███████╗███████╗███████╗███████╗
//   ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚══════╝╚══════╝╚══════╝╚══════╝

// Rendr Requirements
// ///////////////////////////////////////////////////////////////////////////////

var extend        = require('toolz/src/object/extend')
var dateFormat    = require('toolz/src/date/dateFormat')
var isNullOrUndef = require('toolz/src/lang/isNullOrUndef')
var iterate       = require('toolz/src/async/iterate')
var concurrent    = require('toolz/src/async/concurrent')
var writeFile     = require('toolz/src/file/writeFile')
var segments      = require('toolz/src/path/segments')
var Map           = require('toolz/src/cache/map')
var matter        = require('./src/matter')
var layouts       = require('./src/layouts')
var handlebars    = require('handlebars')
var resolve       = require('resolve')
var prettify      = require(resolve.sync('js-beautify', {basedir: '/usr/local/lib/node_modules'})).html
var path          = require('path')
var assert        = require('assert')
var logger        = require('./util/logger')

var cache         = new Map()
var frontin       = new Map()
var stackin       = new Map()

// Rendr Process
// ///////////////////////////////////////////////////////////////////////////////

function rendr (files, stack, globals, defaults, cb) {
  iterate.each(files, function (page, key, done) {
    var tmpl = {}
    // read file, normalize output
    // console.log(f, ' processing')
    // var page = matter.read(f)
    var meta = page.data || {}
    var text = page.content || ''

    // simulate gulp-draft plugin
    if (meta.draft === true)  return done(null, key)

    // append destination to metadata
    meta.dest = dest(page.path, meta, defaults)

    // check for and assign default layout
    if (isNullOrUndef(meta.layout) || !stack[meta.layout]) {
      meta.layout = defaults.defaultLayout
    }

    // namespace metadata for primarily for navigation but
    // other uses as well.
    tmpl.tmpl = meta
    // merge context
    var context = extend({}, globals, stack[meta.layout].locals, meta, tmpl)

    // apply layouts to content
    text = layouts(text, meta.layout, stack, {
      layoutDelims: ['{{=', '}}'],
      defaultLayout: [null]
    }).result

    // compile template
    var template = handlebars.compile(text, {
      noEscape: true,
      preventIndent: true,
      assumeObjects: true
    })

    // mkdir if non-existant, write file to dest and prettify rendered template
    return writeFile(meta.dest.fpath, rendrFilez(template(context), defaults), function (err) {
      assert.ifError(err)
      logger.file(meta.dest.fpath, 'rendered')
      done(null, key)
    })
  }, function (err, result) {
    // callback for iterate.each
    assert.ifError(err)
    cb()
  })
}

function rendrFilez (template, defaults) {
  // if (meta.extname === 'xml') return template
  // built for js-beautify and custom enhancements
  if (defaults.engine === 'html') {
    return prettify(template, defaults.pretty[defaults.engine])
      .replace(/(<\/(a|span|strong|em|h1|h2|h3|h4|h5|h6)>(?!(,|\.|!|\?|;|:)))/g, "$1 ")
      .replace(/(\r\n|\n){2,}/g, "\n")
      .replace(/(\s*(?:<!--|\/\*)\s*)(?:(?!.+(\s*(?:<!--|\/\*)\s*)))/g, "\n$1")
  } else if (defaults.engine === 'css') {
    return prettify(template, defaults.pretty[defaults.engine])
      // put future replacements here for further beautifying
  } else if (defaults.engine === 'js') {
    return prettify(template, defaults.pretty[defaults.engine])
      // put future replacements here for further beautifying
  } else {
    return template
  }
}

function dest (file, meta, defaults) {
  // get file information and return (annotated version of frontMatter() )
  // purpose to simulate vinly-fs dest info for appending to metadata
  // of each file. Used to determine relative file path for navigation

  if (cache.has(file)) return cache.get(file)

  var pathSeparator = '/'
  var buildDir      = defaults.destination
  var buildFileExt  = path.basename(file).replace(/hbs$/, meta.extname || defaults.engine)
  var buildFile     = path.basename(file, path.extname(file))
  var buildExt      = path.extname(file).replace(/hbs$/, meta.extname || defaults.engine)
  var buildPath     = path.dirname(file).replace(defaults.templateRoot, '')
  var buildDirFileExt
  var buildDirPathFileExt
  var results

  if (buildPath) {
    buildDirFileExt     = [buildPath, pathSeparator, buildFileExt].join('')
    buildDirPathFileExt = buildDir + buildDirFileExt
  } else {
    buildDirFileExt     = buildFileExt
    buildDirPathFileExt = [buildDir, pathSeparator, buildDirFileExt].join('')
  }

  results = {
    dirname  : buildDir,
    bpath    : buildDir + buildPath,
    fpath    : buildDirPathFileExt,
    basename : buildFileExt,
    name     : buildFile,
    extname  : buildExt
  }

  cache.set(file, results)
  // console.log(cache.get())
  return results
}

// Stack Object
// ///////////////////////////////////////////////////////////////////////////////

function buildLayoutStack (files, reset, cb) {
  // build an array of layouts for use in wrapping templates
  var stack = {}
  iterate.each(files, function(f, key, done) {
    var namespace = segments.last(f).replace(/\.hbs$/, '')

    if (stackin.has(namespace) && f !== reset) {
      stack[namespace] = stackin.get(namespace)
      return done(null, key)
    }

    var page = matter.read(f)
    var meta = page.data
    var text = page.content

    stack[namespace] = {
      content: text,
      locals: meta,
      layout: meta.layout || ''
    }

    // reset internal cache
    stackin.set(stack)
    done(null, key)
  }, function (err, results) {
    assert.ifError(err)
    cb(null, stack)
  })
}

// frontMatter
// ///////////////////////////////////////////////////////////////////////////////

function frontMatter (filenames, reset, defaults, cb) {
  // build an object of all templates' yaml-front-matter and append file path
  // metadata to each key. Used for navigation, collections, sitemaps, sorting
  // and many other things.
  var parsed = {}
  var pathSeparator = '/'

  iterate.each(filenames, function (f, key, done) {
    var baseName = segments.last(f, 2, '-').replace(/\.hbs$/, '')

    if (frontin.has(baseName) && f !== reset) {
      parsed[baseName] = frontin.get(baseName)
      return done(null, key)
    }

    var metadata = matter.read(f).data

    metadata.buildInfoSTRT = '-----------------------'

    if (metadata.pubDate) metadata.iso8601Date = dateFormat(metadata.pubDate, 'iso')

    // set regex to remove path items which will not translate
    // to the build directory in the options
    metadata.buildDir = path.dirname(f).replace(defaults.templateRoot, '')

    if (metadata.buildDir) {
      metadata.buildDirFileExt = metadata.buildDir
        + pathSeparator
        + path.basename(f)
        .replace(/hbs$/, defaults.engine)
    } else {
      metadata.buildDirFileExt = pathSeparator
        + path.basename(f)
        .replace(/hbs$/, defaults.engine)
    }

    metadata.buildFileExt = path.basename(f).replace(/hbs$/, defaults.engine)
    metadata.buildFile    = path.basename(f, path.extname(f))
    metadata.buildExt     = path.extname(f).replace(/hbs$/, defaults.engine)
    metadata.buildPath    = defaults.destination + metadata.buildDir
    metadata.buildDest    = defaults.destination + metadata.buildDirFileExt

    metadata.buildInfoEND = '------------------------'

    parsed[baseName] = metadata

    // reset internal cache
    frontin.set(parsed)
    done(null, key)
  }, function (err, result) {
    assert.ifError(err)
    cb(null, parsed)
  })
}

// readFile
// ///////////////////////////////////////////////////////////////////////////////

function readFile (fn, opt) { // fn = filename;
  var page = {}
  var basename

  if (opt) {
    basename = segments.last(fn).replace(/\.hbs$/, '')
  } else {
    basename = segments.last(fn, 2, '-').replace(/\.hbs$/, '')
  }

  page[basename] = matter.read(fn)
  return page
}

// Expose API
// ///////////////////////////////////////////////////////////////////////////////

exports.rendr = rendr
exports.frontMatter = frontMatter
exports.buildLayoutStack = buildLayoutStack
exports.readFile = readFile
<commit_msg>bug fix app/rendr.js<commit_after>//
//    █████╗ ██╗  ██╗██╗██╗     ███████╗███████╗███████╗
//   ██╔══██╗██║ ██╔╝██║██║     ██╔════╝██╔════╝╚══███╔╝
//   ███████║█████╔╝ ██║██║     █████╗  █████╗    ███╔╝
//   ██╔══██║██╔═██╗ ██║██║     ██╔══╝  ██╔══╝   ███╔╝
//   ██║  ██║██║  ██╗██║███████╗███████╗███████╗███████╗
//   ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚══════╝╚══════╝╚══════╝╚══════╝

// Rendr Requirements
// ///////////////////////////////////////////////////////////////////////////////

var extend        = require('toolz/src/object/extend')
var dateFormat    = require('toolz/src/date/dateFormat')
var isNullOrUndef = require('toolz/src/lang/isNullOrUndef')
var iterate       = require('toolz/src/async/iterate')
var concurrent    = require('toolz/src/async/concurrent')
var writeFile     = require('toolz/src/file/writeFile')
var segments      = require('toolz/src/path/segments')
var Map           = require('toolz/src/cache/map')
var matter        = require('./src/matter')
var layouts       = require('./src/layouts')
var handlebars    = require('handlebars')
var resolve       = require('resolve')
var prettify      = require(resolve.sync('js-beautify', {basedir: '/usr/local/lib/node_modules'})).html
var path          = require('path')
var assert        = require('assert')
var logger        = require('./util/logger')

var cache         = new Map()
var frontin       = new Map()
var stackin       = new Map()

// Rendr Process
// ///////////////////////////////////////////////////////////////////////////////

function rendr (files, stack, globals, defaults, cb) {
  iterate.each(files, function (page, key, done) {
    var tmpl = {}
    // read file, normalize output
    // console.log(f, ' processing')
    // var page = matter.read(f)
    var meta = page.data || {}
    var text = page.content || ''

    // simulate gulp-draft plugin
    if (meta.draft === true)  return done(null, key)

    // append destination to metadata
    meta.dest = dest(page.path, meta, defaults)

    // check for and assign default layout
    if (isNullOrUndef(meta.layout) || !stack[meta.layout]) {
      meta.layout = defaults.defaultLayout
    }

    // namespace metadata for primarily for navigation but
    // other uses as well.
    tmpl.tmpl = meta
    // merge context
    var context = extend({}, globals, stack[meta.layout].locals, meta, tmpl)

    // apply layouts to content
    text = layouts(text, meta.layout, stack, {
      layoutDelims: ['{{=', '}}'],
      defaultLayout: [null]
    }).result

    // compile template
    var template = handlebars.compile(text, {
      noEscape: true,
      preventIndent: true,
      assumeObjects: true
    })

    // mkdir if non-existant, write file to dest and prettify rendered template
    return writeFile(meta.dest.fpath, rendrFilez(template(context), defaults), function (err) {
      assert.ifError(err)
      logger.file(meta.dest.fpath, 'rendered')
      done(null, key)
    })
  }, function (err, result) {
    // callback for iterate.each
    assert.ifError(err)
    cb()
  })
}

function rendrFilez (template, defaults) {
  // if (meta.extname === 'xml') return template
  // built for js-beautify and custom enhancements
  if (defaults.engine === 'html') {
    return prettify(template, defaults.pretty[defaults.engine])
      .replace(/(<\/(a|span|strong|em|h1|h2|h3|h4|h5|h6)>(?!(,|\.|!|\?|;|:)))/g, "$1 ")
      .replace(/(\r\n|\n){2,}/g, "\n")
      .replace(/(\s*(?:<!--|\/\*)\s*)(?:(?!.+(\s*(?:<!--|\/\*)\s*)))/g, "\n$1")
  } else if (defaults.engine === 'css') {
    return prettify(template, defaults.pretty[defaults.engine])
      // put future replacements here for further beautifying
  } else if (defaults.engine === 'js') {
    return prettify(template, defaults.pretty[defaults.engine])
      // put future replacements here for further beautifying
  } else {
    return template
  }
}

function dest (file, meta, defaults) {
  // get file information and return (annotated version of frontMatter() )
  // purpose to simulate vinly-fs dest info for appending to metadata
  // of each file. Used to determine relative file path for navigation

  var base = file.replace(/\.hbs$/, '')
  if (cache.has(base)) return cache.get(base)

  var pathSeparator = '/'
  var buildDir      = defaults.destination
  var buildFileExt  = path.basename(file).replace(/hbs$/, meta.extname || defaults.engine)
  var buildFile     = path.basename(file, path.extname(file))
  var buildExt      = path.extname(file).replace(/hbs$/, meta.extname || defaults.engine)
  var buildPath     = path.dirname(file).replace(defaults.templateRoot, '')
  var buildDirFileExt
  var buildDirPathFileExt
  var results

  if (buildPath) {
    buildDirFileExt     = [buildPath, pathSeparator, buildFileExt].join('')
    buildDirPathFileExt = buildDir + buildDirFileExt
  } else {
    buildDirFileExt     = buildFileExt
    buildDirPathFileExt = [buildDir, pathSeparator, buildDirFileExt].join('')
  }

  results = {
    dirname  : buildDir,
    bpath    : buildDir + buildPath,
    fpath    : buildDirPathFileExt,
    basename : buildFileExt,
    name     : buildFile,
    extname  : buildExt
  }

  cache.set(base, results)
  // console.log(cache.get())
  return results
}

// Stack Object
// ///////////////////////////////////////////////////////////////////////////////

function buildLayoutStack (files, reset, cb) {
  // build an array of layouts for use in wrapping templates
  var stack = {}
  iterate.each(files, function(f, key, done) {
    var namespace = segments.last(f).replace(/\.hbs$/, '')

    if (stackin.has(namespace) && f !== reset) {
      return done(null, key)
    }

    var page = matter.read(f)
    var meta = page.data
    var text = page.content

    stack[namespace] = {
      content: text,
      locals: meta,
      layout: meta.layout || ''
    }

    // reset internal cache
    stackin.set(namespace, stack[namespace])
    done(null, key)
  }, function (err, results) {
    assert.ifError(err)
    cb(null, stackin.get())
  })
}

// frontMatter
// ///////////////////////////////////////////////////////////////////////////////

function frontMatter (filenames, reset, defaults, cb) {
  // build an object of all templates' yaml-front-matter and append file path
  // metadata to each key. Used for navigation, collections, sitemaps, sorting
  // and many other things.
  var parsed = {}
  var pathSeparator = '/'

  iterate.each(filenames, function (f, key, done) {
    var baseName = segments.last(f, 2, '-').replace(/\.hbs$/, '')

    if (frontin.has(baseName) && f !== reset) {
      return done(null, key)
    }

    var metadata = matter.read(f).data

    metadata.buildInfoSTRT = '-----------------------'

    if (metadata.pubDate) metadata.iso8601Date = dateFormat(metadata.pubDate, 'iso')

    // set regex to remove path items which will not translate
    // to the build directory in the options
    metadata.buildDir = path.dirname(f).replace(defaults.templateRoot, '')

    if (metadata.buildDir) {
      metadata.buildDirFileExt = metadata.buildDir
        + pathSeparator
        + path.basename(f)
        .replace(/hbs$/, defaults.engine)
    } else {
      metadata.buildDirFileExt = pathSeparator
        + path.basename(f)
        .replace(/hbs$/, defaults.engine)
    }

    metadata.buildFileExt = path.basename(f).replace(/hbs$/, defaults.engine)
    metadata.buildFile    = path.basename(f, path.extname(f))
    metadata.buildExt     = path.extname(f).replace(/hbs$/, defaults.engine)
    metadata.buildPath    = defaults.destination + metadata.buildDir
    metadata.buildDest    = defaults.destination + metadata.buildDirFileExt

    metadata.buildInfoEND = '------------------------'

    parsed[baseName] = metadata

    // reset internal cache
    frontin.set(baseName, parsed[baseName])

    done(null, key)
  }, function (err, result) {
    assert.ifError(err)
    cb(null, frontin.get())
  })
}

// readFile
// ///////////////////////////////////////////////////////////////////////////////

function readFile (fn, opt) { // fn = filename;
  var page = {}
  var basename

  if (opt) {
    basename = segments.last(fn).replace(/\.hbs$/, '')
  } else {
    basename = segments.last(fn, 2, '-').replace(/\.hbs$/, '')
  }

  page[basename] = matter.read(fn)
  return page
}

// Expose API
// ///////////////////////////////////////////////////////////////////////////////

exports.rendr = rendr
exports.frontMatter = frontMatter
exports.buildLayoutStack = buildLayoutStack
exports.readFile = readFile
<|endoftext|>


<commit_before>/**
 * @ignore
 */
exports['Should correctly stop reconnection attempts after limit reached'] = {
  metadata: { requires: { topology: ['single'] }, ignore: { travis: true } },

  // The actual test we wish to run
  test: function(configuration, test) {
    // Create a new db instance
    var client = configuration.newDbInstance(
      { w: 1 },
      {
        poolSize: 1,
        auto_reconnect: true,
        reconnectTries: 2,
        reconnectInterval: 100
      }
    );

    client.connect(function(err, client) {
      var db = client.db(configuration.database);
      // console.log("----------- 0")
      // Now let's stop the server
      configuration.manager.stop().then(function() {
        // console.log("----------- 1")

        db.collection('waiting_for_reconnect').insert({ a: 1 }, function(err, r) {
          // console.log("----------- 2")
          // console.dir(err)
          // console.dir(err)
          test.ok(err != null);
          client.close();

          configuration.manager.start().then(function() {
            // console.log("----------- 3")
            test.done();
          });
        });
      });
    });
  }
};

/**
 * @ignore
 */
exports['Should correctly recover when bufferMaxEntries: -1 and multiple restarts'] = {
  metadata: { requires: { topology: ['single'] }, ignore: { travis: true } },

  // The actual test we wish to run
  test: function(configuration, test) {
    var MongoClient = configuration.require.MongoClient,
      f = require('util').format;

    var done = false;

    MongoClient.connect(
      'mongodb://localhost:27017/test',
      {
        db: { native_parser: true, bufferMaxEntries: -1 },
        server: {
          poolSize: 20,
          socketOptions: { autoReconnect: true, keepAlive: 50 },
          reconnectTries: 1000,
          reconnectInterval: 1000
        }
      },
      function(err, client) {
        var db = client.db(configuration.database);
        // console.log("======================================= 0")
        var col = db.collection('t');
        var count = 1;
        var allDone = 0;

        var execute = function() {
          if (!done) {
            // console.log("======================================= 1:1")
            col.insertOne({ a: 1, count: count }, function(err, r) {
              // console.log("======================================= 1:2")
              count = count + 1;

              col.findOne({}, function(err, doc) {
                // console.log("======================================= 1:3")
                setTimeout(execute, 500);
              });
            });
          } else {
            // console.log("======================================= 2:1")
            col.insertOne({ a: 1, count: count }, function(err, r) {
              // console.log("======================================= 2:2")
              test.equal(null, err);

              col.findOne({}, function(err, doc) {
                // console.log("======================================= 2:3")
                test.equal(null, err);
                client.close();
                test.done();
              });
            });
          }
        };

        setTimeout(execute, 500);
      }
    );

    var count = 2;

    var restartServer = function() {
      // console.log("==== restartServer 1")
      if (count == 0) {
        // console.log("==== restartServer DONE")
        done = true;
        return;
      }

      count = count - 1;

      configuration.manager.stop().then(function() {
        // console.log("==== restartServer 2")
        setTimeout(function() {
          // console.log("==== restartServer 3")
          configuration.manager.start().then(function() {
            // console.log("==== restartServer 4")
            setTimeout(restartServer, 1000);
          });
        }, 2000);
      });
    };

    setTimeout(restartServer, 1000);
  }
};
<commit_msg>test(reconnect): convert `reconnect` tests to mocha style<commit_after>'use strict';
var test = require('./shared').assert;
var setupDatabase = require('./shared').setupDatabase;

describe('Reconnect', function() {
  before(function() {
    return setupDatabase(this.configuration);
  });

  /**
   * @ignore
   */
  it('Should correctly stop reconnection attempts after limit reached', {
    metadata: { requires: { topology: ['single'] }, ignore: { travis: true } },

    // The actual test we wish to run
    test: function(done) {
      // Create a new db instance
      var configuration = this.configuration;
      var client = configuration.newClient(
        { w: 1 },
        {
          poolSize: 1,
          auto_reconnect: true,
          reconnectTries: 2,
          reconnectInterval: 100
        }
      );

      client.connect(function(err, client) {
        var db = client.db(configuration.db);
        // Now let's stop the server
        configuration.manager.stop().then(function() {
          db.collection('waiting_for_reconnect').insert({ a: 1 }, function(err) {
            test.ok(err != null);
            client.close();

            configuration.manager.start().then(function() {
              done();
            });
          });
        });
      });
    }
  });

  /**
   * @ignore
   */
  it('Should correctly recover when bufferMaxEntries: -1 and multiple restarts', {
    metadata: { requires: { topology: ['single'] }, ignore: { travis: true } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var MongoClient = configuration.require.MongoClient;

      MongoClient.connect(
        'mongodb://localhost:27017/test',
        {
          db: { native_parser: true, bufferMaxEntries: -1 },
          server: {
            poolSize: 20,
            socketOptions: { autoReconnect: true, keepAlive: 50 },
            reconnectTries: 1000,
            reconnectInterval: 1000
          }
        },
        function(err, client) {
          var db = client.db(configuration.db);
          var col = db.collection('t');
          var count = 1;

          var execute = function() {
            if (!done) {
              col.insertOne({ a: 1, count: count }, function(err) {
                test.equal(null, err);
                count = count + 1;

                col.findOne({}, function(err) {
                  test.equal(null, err);
                  setTimeout(execute, 500);
                });
              });
            } else {
              col.insertOne({ a: 1, count: count }, function(err) {
                test.equal(null, err);

                col.findOne({}, function(err) {
                  test.equal(null, err);
                  client.close();
                  done();
                });
              });
            }
          };

          setTimeout(execute, 500);
        }
      );

      var count = 2;

      var restartServer = function() {
        if (count == 0) {
          done = true;
          return;
        }

        count = count - 1;

        configuration.manager.stop().then(function() {
          setTimeout(function() {
            configuration.manager.start().then(function() {
              setTimeout(restartServer, 1000);
            });
          }, 2000);
        });
      };

      setTimeout(restartServer, 1000);
    }
  });
});
<|endoftext|>


<commit_before>angular.module("umbraco.directives").directive('nestedContentEditor', [

    function () {

        var link = function ($scope, element, attrs, ctrl) {
            $scope.model = $scope.ngModel;

            var tab = $scope.ngModel.tabs[0];

            if ($scope.tabAlias) {
                angular.forEach($scope.ngModel.tabs, function (value, key) {
                    if (value.alias.toLowerCase() == $scope.tabAlias.toLowerCase()) {
                        tab = value;
                        return;
                    }
                });
            }

            $scope.tab = tab;
        }

        return {
            restrict: "E",
            replace: true,
            templateUrl: "/App_Plugins/NestedContent/Views/nestedcontent.editor.html",
            scope: {
                ngModel: '=',
                tabAlias: '='
            },
            link: link
        };

    }
]);
<commit_msg>Updated directive to stash node context in scope so child scopes can have a way of detecting an inner node context from that stashed in editorState<commit_after>angular.module("umbraco.directives").directive('nestedContentEditor', [
    "editorState",
    function (editorState) {

        var link = function ($scope, element, attrs, ctrl) {
            $scope.nodeContext = $scope.model = $scope.ngModel;

            var tab = $scope.ngModel.tabs[0];

            if ($scope.tabAlias) {
                angular.forEach($scope.ngModel.tabs, function (value, key) {
                    if (value.alias.toLowerCase() == $scope.tabAlias.toLowerCase()) {
                        tab = value;
                        return;
                    }
                });
            }

            $scope.tab = tab;
        }

        return {
            restrict: "E",
            replace: true,
            templateUrl: "/App_Plugins/NestedContent/Views/nestedcontent.editor.html",
            scope: {
                ngModel: '=',
                tabAlias: '='
            },
            link: link
        };

    }
]);
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 * Generates the PhET-iO API Type documentation which will be shown at https://phet-io.colorado.edu/devguide/
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

var requirejs = require( 'requirejs' ); // eslint-disable-line require-statement-match
var fs = require( 'fs' );

// Mock up window globals for running in node mode

global.assert = require( 'assert' );
global._ = require( '../../../../sherpa/lib/lodash-4.17.4.js' );
global.phet = {
  chipper: {
    queryParameters: {},
    brand: 'phet-io'
  },
  phetio: {
    queryParameters: {
      phetioExpressions: '[]'
    }
  }
};

// This is a hack because we are now loading scenery.js as a namespace.
global.document = { createElement: function() {return { getContext: function() {} };} };

global.window = global; // eslint-disable-line no-native-reassign

var assert = global.assert;

// These paths are relative to this file location because they are used by requirejs.
var paths = {

  // plugins
  image: '../../../../chipper/js/requirejs-plugins/image',
  mipmap: '../../../../chipper/js/requirejs-plugins/mipmap',
  string: '../../../../chipper/js/requirejs-plugins/string',
  text: '../../../../sherpa/lib/text-2.0.12',
  ifphetio: '../../../../chipper/js/requirejs-plugins/ifphetio',

  AXON: '../../../../axon/js',
  BRAND: '../../../../brand/phet-io/js',
  CIRCUIT_CONSTRUCTION_KIT_COMMON: '../../../../circuit-construction-kit-common/js',
  DOT: '../../../../dot/js',
  GRIDDLE: '../../../../griddle/js',
  INVERSE_SQUARE_LAW_COMMON: '../../../../inverse-square-law-common/js',
  JOIST: '../../../../joist/js',
  KITE: '../../../../kite/js',
  PHETCOMMON: '../../../../phetcommon/js',
  REPOSITORY: '../../..',
  PHET_CORE: '../../../../phet-core/js',
  PHET_IO: '../../../../phet-io/js',
  SHRED: '../../../../shred/js',
  SCENERY: '../../../../scenery/js',
  SCENERY_PHET: '../../../../scenery-phet/js',
  SUN: '../../../../sun/js',
  TANDEM: '../../../../tandem/js',
  TWIXT: '../../../../twixt/js',
  VEGAS: '../../../../vegas/js',
  VIBE: '../../../../vibe/js'
};
paths['../../sherpa'] = '../../../../sherpa';
paths['../../chipper'] = '../../../../chipper';

requirejs.config( {
  paths: paths
} );

var TObject = requirejs( 'PHET_IO/types/TObject' );

// Get a list of all of the types in the 'types' directory
function walkSync( dir, filelist ) {
  var files = fs.readdirSync( dir );
  filelist = filelist || [];
  files.forEach( function( file ) {
    if ( fs.statSync( dir + '/' + file ).isDirectory() ) {
      filelist = walkSync( dir + '/' + file, filelist );
    }
    else {
      if ( file.match( /^T[A-Z].*\.js/ ) ) {
        filelist.push( dir + '/' + file.replace( '.js', '' ) );
      }
    }
  } );
  return filelist;
}

function getCommonCodeTTypes() {
  var activeRepos = fs.readFileSync( '../chipper/data/active-repos' ).toString();
  activeRepos = activeRepos.split( /\r?\n/ );
  var activeSims = fs.readFileSync( '../chipper/data/active-sims' ).toString();
  activeSims = activeSims.split( /\r?\n/ );

  // Repos that we don't want to search because they have no js/ directories, and won't have TTypes
  var blackList = [ 'babel', 'exemplar', 'function-basics', 'phet-info', 'phet-io-website', 'phet-ios-app',
    'phet-android-app', 'phet-cafepress', 'sherpa', 'slater', 'tambo', 'tasks', 'yotta', 'phet-io-wrappers', 'phet-io-wrapper' ];

  var commonRepos = [];
  activeRepos.forEach( function( repo ) {

    // Don't include the black list above
    if ( blackList.indexOf( repo ) >= 0 ) {
      return;
    }

    // Don't include any sims in the common repos
    if ( activeSims.indexOf( repo ) >= 0 ) {
      return;
    }

    // There are no types in dedicated wrapper repos, exclude this special and dynamic case
    else if ( repo.indexOf( 'phet-io-wrapper-' ) >= 0 ) {
      return;
    }
    commonRepos.push( repo );
  } );

  var files = [];
  commonRepos.forEach( function( repoName ) {
    walkSync( '../' + repoName + '/js', files );
  } );
  return files;
}

function toHTML( json ) {
  var html = '';
  var types = _.sortBy( _.keys( json ), function( key ) {
    return key;
  } );
  for ( var i = 0; i < types.length; i++ ) {

    var typeName = types[ i ];
    var wrapperType = json[ typeName ];
    var methods = '';
    var sortedMethodNames = _.sortBy( _.keys( wrapperType.methods ), function( key ) {
      return key;
    } );
    for ( var k = 0; k < sortedMethodNames.length; k++ ) {
      var methodName = sortedMethodNames[ k ];
      var method = wrapperType.methods[ methodName ];
      var params = method.parameterTypes.map( function( p ) {
        assert && assert( p.typeName, 'Parameter was not a type in toHTML: ' + typeName + '.' + methodName );
        return p.typeName;
      } );
      methods += '
' + methodName + ': (' + params + ') ➞ ' + method.returnType.typeName + '
' +
                 '
' + method.documentation + '
';
    }
    var eventsString = wrapperType.events ? 'events: ' + ( wrapperType.events || '' ) + '' : '';

    var supertype = wrapperType.supertype;
    html = html + '
' + typeName + ' ' +
           (supertype.typeName ? '(extends ' + supertype.typeName + ')' : '') +
           '
' +
           '
' +
           '' + wrapperType.documentation + '
' +
           eventsString +
           '
' + methods + '

';
  }
  return html;
}

/**
 * Given as list of types files, return a json that holds the necessary information for the type documentation.
 * It will have a structure like this: {
 *    typeName: TType from the requirejs call,
 *    nextTypeName: . . . ,
 *    }
 *
 * Parameterized types like TEmitter and TFunctionWrapper are hard coded because they have different constructors.
 * @param ttypeFiles
 */
function getTypeJSON( ttypeFiles ) {

  var result = {};

  // Load the dependencies one at a time. This is a hack workaround that somehow allows requirejs to proceed.  Batching them
  // (that is, loading them with an array) failed for unknown reasons.  See https://github.com/phetsims/phet-io/issues/972
  for ( var i = 0; i < ttypeFiles.length; i++ ) {
    var filename = ttypeFiles[ i ];

    // The walking if from cwd ( sim repo) but requirejs is from the module ( chipper/js/grunt/phet-io)
    var TType = requirejs( '../../../' + filename );

    // See phetioInherit for more details
    if ( TType.typeName ) {
      result[ TType.typeName ] = TType;
    }
    else {

      // Hack around the parametrized nature of TFunctionWrapper
      if ( filename.indexOf( 'TFunctionWrapper' ) >= 0 ) {
        var returnType = TObject;
        var parameterTypes = [ TObject ];
        var instantiatedType = TType( returnType, parameterTypes );
        result[ instantiatedType.typeName ] = instantiatedType;
      }

      // Hack around the parametrized nature of TEmitter
      else if ( filename.indexOf( 'TEmitter' ) >= 0 ) {
        var emitterType = TType( [ TObject ] );
        result[ emitterType.typeName ] = emitterType;
      }

      else {
        var otherType = TType( TObject );
        result[ otherType.typeName ] = otherType;
      }
    }

    // Hack to clear namespaces so duplicate registration doesn't throw an assertion error.
    for ( var v in global.phet ) {
      if ( global.phet.hasOwnProperty( v ) ) {
        if ( global.phet[ v ].constructor.name === 'Namespace' ) {
          var myObject = global.phet[ v ];

          // Manually clear the namespace so it doesn't complain when getting repopulated in the next run
          for ( var member in myObject ) {
            if ( myObject.hasOwnProperty( member ) ) {
              delete myObject[ member ];
            }
          }
        }
      }
    }
  }
  return result;
}

function getCommonCodeTypeDocs() {
  var commonCodeTTypeFiles = getCommonCodeTTypes();
  var typeJSON = getTypeJSON( commonCodeTTypeFiles );

  return toHTML( typeJSON );
}

function getSimSpecificTypeDocs( simRepoName, simFormalName, simCapsForRequire ) {
  var simHTMLHeader = '
Specific Types for ' + simFormalName + '
';
  var ttypeFiles = [];

  // Get the list of TTypes ( path from process.cwd())
  walkSync( '../' + simRepoName + '/js', ttypeFiles );

  // Path from grunt source code
  paths[ simCapsForRequire ] = '../../../../' + simRepoName + '/js';
  requirejs.config( {
    paths: paths
  } );
  var typeJSON = getTypeJSON( ttypeFiles );
  var typeHTML = toHTML( typeJSON );
  return typeHTML ? simHTMLHeader + typeHTML : simHTMLHeader + '
There are no specific types for this sim
';
}

// Called from the command line, and not required, option support for both modules, used by the grunt build process. This
// can also be used to update the devGuide in the phet-io-website.
if ( require.main === module ) {
  var args = process.argv;

  // Common code docs
  if ( args.length === 3 && args[ 2 ] === '--common' ) {
    console.log( getCommonCodeTypeDocs() );
  }

  // Sim specific docs
  else if ( args.length === 6 && args[ 2 ] === '--sim' ) {
    var repoName = args[ 3 ];
    var simFormalName = args[ 4 ];
    var requirejsName = args[ 5 ];
    console.log( getSimSpecificTypeDocs( repoName, simFormalName, requirejsName ) );
  }
  else {
    console.error( 'Incorrect usage, given args: ', args );
    console.error( 'Usage for common code \'node ../../createTypeDocumentation.js --common\'\n' +
                   'Usage for a specific sim\'s docs \'node createTypeDocumention.js --sim [repo-name] [\"Sim Formal Name\"] [REQUIRE_JS_REPO_NAME]\'' );
    process.exit();
  }
}
<commit_msg>runs from the command line again, https://github.com/phetsims/chipper/issues/603<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * Generates the PhET-iO API Type documentation which will be shown at https://phet-io.colorado.edu/devguide/
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

var requirejs = require( 'requirejs' ); // eslint-disable-line require-statement-match
var fs = require( 'fs' );

// Mock up window globals for running in node mode

global.assert = require( 'assert' );
global._ = require( '../../../../sherpa/lib/lodash-4.17.4.js' );
global.phet = {
  chipper: {
    queryParameters: {},
    brand: 'phet-io'
  },
  phetio: {
    queryParameters: {
      phetioExpressions: '[]'
    }
  }
};

// This is a hack because we are now loading scenery.js as a namespace.
global.document = { createElement: function() {return { getContext: function() {} };} };

global.window = global; // eslint-disable-line no-native-reassign

var assert = global.assert;

// These paths are relative to this file location because they are used by requirejs.
var paths = {

  // plugins
  image: '../../../../chipper/js/requirejs-plugins/image',
  mipmap: '../../../../chipper/js/requirejs-plugins/mipmap',
  string: '../../../../chipper/js/requirejs-plugins/string',
  text: '../../../../sherpa/lib/text-2.0.12',
  ifphetio: '../../../../chipper/js/requirejs-plugins/ifphetio',

  AXON: '../../../../axon/js',
  BRAND: '../../../../brand/phet-io/js',
  CIRCUIT_CONSTRUCTION_KIT_COMMON: '../../../../circuit-construction-kit-common/js',
  DOT: '../../../../dot/js',
  GRIDDLE: '../../../../griddle/js',
  INVERSE_SQUARE_LAW_COMMON: '../../../../inverse-square-law-common/js',
  JOIST: '../../../../joist/js',
  KITE: '../../../../kite/js',
  PHETCOMMON: '../../../../phetcommon/js',
  REPOSITORY: '../../..',
  PHET_CORE: '../../../../phet-core/js',
  PHET_IO: '../../../../phet-io/js',
  SHRED: '../../../../shred/js',
  SCENERY: '../../../../scenery/js',
  SCENERY_PHET: '../../../../scenery-phet/js',
  SUN: '../../../../sun/js',
  TANDEM: '../../../../tandem/js',
  TWIXT: '../../../../twixt/js',
  VEGAS: '../../../../vegas/js',
  VIBE: '../../../../vibe/js'
};
paths['../../sherpa'] = '../../../../sherpa';
paths['../../chipper'] = '../../../../chipper';

requirejs.config( {
  paths: paths
} );

var TObject = requirejs( 'PHET_IO/types/TObject' );

// Get a list of all of the types in the 'types' directory
function walkSync( dir, filelist ) {
  var files = fs.readdirSync( dir );
  filelist = filelist || [];
  files.forEach( function( file ) {
    if ( fs.statSync( dir + '/' + file ).isDirectory() ) {
      filelist = walkSync( dir + '/' + file, filelist );
    }
    else {
      if ( file.match( /^T[A-Z].*\.js/ ) ) {
        filelist.push( dir + '/' + file.replace( '.js', '' ) );
      }
    }
  } );
  return filelist;
}

function getCommonCodeTTypes() {
  var activeRepos = fs.readFileSync( '../../../../chipper/data/active-repos' ).toString();
  activeRepos = activeRepos.split( /\r?\n/ );
  var activeSims = fs.readFileSync( '../../../../chipper/data/active-sims' ).toString();
  activeSims = activeSims.split( /\r?\n/ );

  // Repos that we don't want to search because they have no js/ directories, and won't have TTypes
  var blackList = [ 'smithers', 'babel', 'exemplar', 'function-basics', 'phet-info', 'phet-io-website', 'phet-ios-app',
    'phet-android-app', 'phet-cafepress', 'sherpa', 'slater', 'tambo', 'tasks', 'yotta', 'phet-io-wrappers', 'phet-io-wrapper' ];

  var commonRepos = [];
  activeRepos.forEach( function( repo ) {

    // Don't include the black list above
    if ( blackList.indexOf( repo ) >= 0 ) {
      return;
    }

    // Don't include any sims in the common repos
    if ( activeSims.indexOf( repo ) >= 0 ) {
      return;
    }

    // There are no types in dedicated wrapper repos, exclude this special and dynamic case
    else if ( repo.indexOf( 'phet-io-wrapper-' ) >= 0 ) {
      return;
    }
    commonRepos.push( repo );
  } );

  var files = [];
  commonRepos.forEach( function( repoName ) {
    walkSync( '../../../../' + repoName + '/js', files );
  } );
  return files;
}

function toHTML( json ) {
  var html = '';
  var types = _.sortBy( _.keys( json ), function( key ) {
    return key;
  } );
  for ( var i = 0; i < types.length; i++ ) {

    var typeName = types[ i ];
    var wrapperType = json[ typeName ];
    var methods = '';
    var sortedMethodNames = _.sortBy( _.keys( wrapperType.methods ), function( key ) {
      return key;
    } );
    for ( var k = 0; k < sortedMethodNames.length; k++ ) {
      var methodName = sortedMethodNames[ k ];
      var method = wrapperType.methods[ methodName ];
      var params = method.parameterTypes.map( function( p ) {
        assert && assert( p.typeName, 'Parameter was not a type in toHTML: ' + typeName + '.' + methodName );
        return p.typeName;
      } );
      methods += '
' + methodName + ': (' + params + ') ➞ ' + method.returnType.typeName + '
' +
                 '
' + method.documentation + '
';
    }
    var eventsString = wrapperType.events ? 'events: ' + ( wrapperType.events || '' ) + '' : '';

    var supertype = wrapperType.supertype;
    html = html + '
' + typeName + ' ' +
           (supertype.typeName ? '(extends ' + supertype.typeName + ')' : '') +
           '
' +
           '
' +
           '' + wrapperType.documentation + '
' +
           eventsString +
           '
' + methods + '

';
  }
  return html;
}

/**
 * Given as list of types files, return a json that holds the necessary information for the type documentation.
 * It will have a structure like this: {
 *    typeName: TType from the requirejs call,
 *    nextTypeName: . . . ,
 *    }
 *
 * Parameterized types like TEmitter and TFunctionWrapper are hard coded because they have different constructors.
 * @param ttypeFiles
 */
function getTypeJSON( ttypeFiles ) {

  var result = {};

  // Load the dependencies one at a time. This is a hack workaround that somehow allows requirejs to proceed.  Batching them
  // (that is, loading them with an array) failed for unknown reasons.  See https://github.com/phetsims/phet-io/issues/972
  for ( var i = 0; i < ttypeFiles.length; i++ ) {
    var filename = ttypeFiles[ i ];

    // The walking if from cwd ( sim repo) but requirejs is from the module ( chipper/js/grunt/phet-io)
    var TType = requirejs( filename );

    // See phetioInherit for more details
    if ( TType.typeName ) {
      result[ TType.typeName ] = TType;
    }
    else {

      // Hack around the parametrized nature of TFunctionWrapper
      if ( filename.indexOf( 'TFunctionWrapper' ) >= 0 ) {
        var returnType = TObject;
        var parameterTypes = [ TObject ];
        var instantiatedType = TType( returnType, parameterTypes );
        result[ instantiatedType.typeName ] = instantiatedType;
      }

      // Hack around the parametrized nature of TEmitter
      else if ( filename.indexOf( 'TEmitter' ) >= 0 ) {
        var emitterType = TType( [ TObject ] );
        result[ emitterType.typeName ] = emitterType;
      }

      else {
        var otherType = TType( TObject );
        result[ otherType.typeName ] = otherType;
      }
    }

    // Hack to clear namespaces so duplicate registration doesn't throw an assertion error.
    for ( var v in global.phet ) {
      if ( global.phet.hasOwnProperty( v ) ) {
        if ( global.phet[ v ].constructor.name === 'Namespace' ) {
          var myObject = global.phet[ v ];

          // Manually clear the namespace so it doesn't complain when getting repopulated in the next run
          for ( var member in myObject ) {
            if ( myObject.hasOwnProperty( member ) ) {
              delete myObject[ member ];
            }
          }
        }
      }
    }
  }
  return result;
}

function getCommonCodeTypeDocs() {
  var commonCodeTTypeFiles = getCommonCodeTTypes();
  var typeJSON = getTypeJSON( commonCodeTTypeFiles );

  return toHTML( typeJSON );
}

// Called from the command line, and not required, option support for both modules, used by the grunt build process. This
// can also be used to update the devGuide in the phet-io-website.
if ( require.main === module ) {
    console.log( getCommonCodeTypeDocs() );
}
<|endoftext|>


<commit_before>(function(){

    // Datawrapper.EditableChart
    // -------------------------

    //
    var EditableChart = Datawrapper.EditableChart = function(attributes) {
        var me = this;
        me.__attributes = attributes;
        me.__changed = false;
        me.__changeCallbacks = [];
        me.__saveCallbacks = [];
        me.__syncedElements = [];
    };

    _.extend(EditableChart.prototype, Datawrapper.Chart.prototype, {

        set: function(key, value) {
            var keys = key.split('.'),
                me = this,
                lastKey = keys.pop(),
                pt = me.__attributes;

            // resolve property until the parent dict
            _.each(keys, function(key) {
                if (_.isArray(pt[key]) && pt[key].length === 0) {
                    pt[key] = {};
                }
                pt = pt[key];
            });

            // check if new value is set
            if (pt[lastKey] != value) {
                pt[lastKey] = value;
                me.__changed = true;
                clearTimeout(me.__saveTimeout);
                me.__saveTimeout = setTimeout(function() {
                    me.save();
                }, 300);
                _.each(me.__changeCallbacks, function(cb) {
                    cb.call(this, me, key, value);
                });
            }
            return this;
        },

        onChange: function(callback) {
            this.__changeCallbacks.push(callback);
        },

        sync: function(el, attribute, _default) {
            if (_.isString(el)) el = $(el);
            el.data('sync-attribute', attribute);

            // initialize current state in UI
            var curVal = this.get(attribute, _default);
            if (el.is('input[type=checkbox]')) {
                if (curVal) el.attr('checked', 'checked');
                else el.removeAttr('checked');
            } else if (el.is('input[type=text]') || el.is('textarea') || el.is('select')) {
                el.val(curVal);
            } else if (el.is('input[type=radio]')) {
                if (_.isBoolean(curVal)) {
                    curVal = curVal ? 'yes' : 'no';
                }
                $('input:radio[name='+el.attr('name')+'][value='+curVal+']').attr('checked', 'checked');
            }

            var chart = this;

            chart.__syncedElements.push(el);

            function storeElementValue(el) {
                var attr, val;
                // Resolve attribute string to a pointer to the attribute
                attr = el.data('sync-attribute');

                if (el.is('input[type=checkbox]')) {
                    val = el.attr('checked') == 'checked';
                } else if (el.is('input[type=text]') || el.is('textarea') || el.is('select')) {
                    val = el.val();
                } else if (el.is('input[type=radio]')) {
                    val = $('input:radio[name='+el.attr('name')+']:checked').val();
                    if (val === 'yes') val = true;
                    else if (val === 'no') val = false;
                }
                if (val !== undefined) {
                    chart.set(attr, val);
                }
            }

            el.change(function(evt) {
                storeElementValue($(evt.target));
            });

            window.onbeforeunload = function(e) {
                //console.debug('onbeforeunload()');
                _.each(chart.__syncedElements, storeElementValue);
                var res = chart.save(true);
                if (res === false) return undefined;
                return 'Please wait a second until the data has been saved!';
            };
        },

        onSave: function(callback) {
            this.__saveCallbacks.push(callback);
        },

        save: function(sync) {
            // saves the chart meta data to Datawrapper
            //console.debug('save()', this.__changed);
            if (!this.__changed) return false;
            clearTimeout(this.__saveTimeout);
            var chart = this;
            $.ajax({
                url: '/api/charts/'+this.get('id'),
                type: 'PUT',
                dataType: 'json',
                data: JSON.stringify(this.__attributes),
                processData: false,
                context: this,
                success: function(data) {
                    //console.debug('save completed');
                    if (data.status == "ok") {
                        this.__changed = false;
                        // run callbacks
                        _.each(this.__saveCallbacks, function(cb) {
                            cb.call(this, chart);
                        });
                    } else {
                        console.warn('could not save the chart', data);
                    }
                }
            });
            return true;
        }
    });

}).call(this);<commit_msg>bugfix<commit_after>(function(){

    // Datawrapper.EditableChart
    // -------------------------

    //
    var EditableChart = Datawrapper.EditableChart = function(attributes) {
        var me = this;
        me.__attributes = attributes;
        me.__changed = false;
        me.__changeCallbacks = [];
        me.__saveCallbacks = [];
        me.__syncedElements = [];
    };

    _.extend(EditableChart.prototype, Datawrapper.Chart.prototype, {

        set: function(key, value) {
            var keys = key.split('.'),
                me = this,
                lastKey = keys.pop(),
                pt = me.__attributes;

            // resolve property until the parent dict
            _.each(keys, function(key) {
                if (_.isArray(pt[key]) && pt[key].length === 0) {
                    pt[key] = {};
                }
                pt = pt[key];
            });

            // check if new value is set
            if (pt[lastKey] != value) {
                pt[lastKey] = value;
                me.__changed = true;
                clearTimeout(me.__saveTimeout);
                me.__saveTimeout = setTimeout(function() {
                    me.save();
                }, 300);
                _.each(me.__changeCallbacks, function(cb) {
                    cb.call(this, me, key, value);
                });
            }
            return this;
        },

        onChange: function(callback) {
            this.__changeCallbacks.push(callback);
        },

        sync: function(el, attribute, _default) {
            if (_.isString(el)) el = $(el);
            el.data('sync-attribute', attribute);

            // initialize current state in UI
            var curVal = this.get(attribute, _default);
            if (el.is('input[type=checkbox]')) {
                if (curVal) el.attr('checked', 'checked');
                else el.removeAttr('checked');
            } else if (el.is('input[type=text]') || el.is('textarea') || el.is('select')) {
                el.val(curVal);
            } else if (el.is('input[type=radio]')) {
                if (_.isBoolean(curVal)) {
                    curVal = curVal ? 'yes' : 'no';
                }
                $('input:radio[name='+el.attr('name')+'][value='+curVal+']').attr('checked', 'checked');
            }

            var chart = this;

            chart.__syncedElements.push(el);

            function storeElementValue(el) {
                var attr, val;
                // Resolve attribute string to a pointer to the attribute
                attr = el.data('sync-attribute');

                if (el.is('input[type=checkbox]')) {
                    val = el.attr('checked') == 'checked';
                } else if (el.is('input[type=text]') || el.is('textarea') || el.is('select')) {
                    val = el.val();
                } else if (el.is('input[type=radio]')) {
                    val = $('input:radio[name='+el.attr('name')+']:checked').val();
                    if (val === 'yes') val = true;
                    else if (val === 'no') val = false;
                }
                if (val !== undefined) {
                    chart.set(attr, val);
                }
            }

            el.change(function(evt) {
                storeElementValue($(evt.target));
            });

            window.onbeforeunload = function(e) {
                //console.debug('onbeforeunload()');
                if (chart.__changed) return 'Caution: unsaved changes';
                //_.each(chart.__syncedElements, storeElementValue);
                //var res = chart.save();
                //if (res === false) return undefined;
                //return 'Please wait a second until the data has been saved!';
            };
        },

        onSave: function(callback) {
            this.__saveCallbacks.push(callback);
        },

        save: function(sync) {
            // saves the chart meta data to Datawrapper
            //console.debug('save()', this.__changed);
            if (!this.__changed) return false;
            clearTimeout(this.__saveTimeout);
            var chart = this;
            $.ajax({
                url: '/api/charts/'+this.get('id'),
                type: 'PUT',
                dataType: 'json',
                data: JSON.stringify(this.__attributes),
                processData: false,
                context: this,
                success: function(data) {
                    //console.debug('save completed');
                    if (data.status == "ok") {
                        this.__changed = false;
                        // run callbacks
                        _.each(this.__saveCallbacks, function(cb) {
                            cb.call(this, chart);
                        });
                    } else {
                        console.warn('could not save the chart', data);
                    }
                }
            });
            return true;
        }
    });

}).call(this);<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2013 Torgeir Helgevold

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

var server = require('webserver').create();

var args = require('system').args;
if (args.length !== 2) {
    console.log('Please provide a valid server url (including port) ex: 127.0.0.1:8080');
    phantom.exit();
}

var serverUrl = args[1];

var fileId = new Number(0);;

function parsePostParameters(postCollection) {
    var post =
    {
        format: postCollection["format"] || null,
        url: postCollection["url"] || null,
    };

    post.IsPdf = function () {
        if (post.format === "pdf") {
            return true;
        }
        return false;
    };

    post.IsImage = function () {
        if (post.format === "png" || post.format === "jpeg") {
            return true;
        }
        return false;
    };

    return post;
}

function createTempFileName() {
    fileId++;
    return "tempFile" + fileId + (new Date()).getTime() + ".pdf";
}

function exportFromUrl(response, post) {
    var page = require('webpage').create();
    page.open(post.url, function (status) {

        console.log(status);

        if (status == "fail") {
            response.headers = {
                'Cache': 'no-cache',
                'Content-Type': 'text/plain',
            };

            response.statusCode = 400;
            response.write("Error");
            response.close();
        }

        if (post.IsImage() === true) {
            var content = page.renderBase64(post.format);
            response.write(content);

            response.statusCode = 200;
            response.close();
        }

        else if (post.IsPdf() === true) {

            var tempFileName = createTempFileName();

            page.render(tempFileName);

            var fs = require('fs');

            var pdfStream = fs.open(tempFileName, 'rb')
            var pdfContent = pdfStream.read();

            response.headers = {
                'Cache': 'no-cache',
                'Content-Type': 'application/pdf'
            };

            response.setEncoding("binary");
            response.write(pdfContent);

            pdfStream.close();
            fs.remove(tempFileName);

            response.statusCode = 200;
            response.close();
        }
    });
}

console.log("Starting export server @ " + serverUrl);

var service = server.listen(serverUrl, function (request, response) {

    var post = parsePostParameters(request.post);

    exportFromUrl(response, post);

 });<commit_msg>tweakto file name<commit_after>/*
The MIT License (MIT)

Copyright (c) 2013 Torgeir Helgevold

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

var server = require('webserver').create();

var args = require('system').args;
if (args.length !== 2) {
    console.log('Please provide a valid server url (including port) ex: 127.0.0.1:8080');
    phantom.exit();
}

var serverUrl = args[1];

var fileId = new Number(0);;

function parsePostParameters(postCollection) {
    var post =
    {
        format: postCollection["format"] || null,
        url: postCollection["url"] || null,
    };

    post.IsPdf = function () {
        if (post.format === "pdf") {
            return true;
        }
        return false;
    };

    post.IsImage = function () {
        if (post.format === "png" || post.format === "jpeg") {
            return true;
        }
        return false;
    };

    return post;
}

function createTempFileName() {
    fileId++;
    return "tempFile" + fileId + "_" + (new Date()).getTime() + ".pdf";
}

function exportFromUrl(response, post) {
    var page = require('webpage').create();
    page.open(post.url, function (status) {

        console.log(status);

        if (status == "fail") {
            response.headers = {
                'Cache': 'no-cache',
                'Content-Type': 'text/plain',
            };

            response.statusCode = 400;
            response.write("Error");
            response.close();
        }

        if (post.IsImage() === true) {
            var content = page.renderBase64(post.format);
            response.write(content);

            response.statusCode = 200;
            response.close();
        }

        else if (post.IsPdf() === true) {

            var tempFileName = createTempFileName();

            page.render(tempFileName);

            var fs = require('fs');

            var pdfStream = fs.open(tempFileName, 'rb')
            var pdfContent = pdfStream.read();

            response.headers = {
                'Cache': 'no-cache',
                'Content-Type': 'application/pdf'
            };

            response.setEncoding("binary");
            response.write(pdfContent);

            pdfStream.close();
            fs.remove(tempFileName);

            response.statusCode = 200;
            response.close();
        }
    });
}

console.log("Starting export server @ " + serverUrl);

var service = server.listen(serverUrl, function (request, response) {

    var post = parsePostParameters(request.post);

    exportFromUrl(response, post);

 });<|endoftext|>