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

foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'UserAndGroupAuthService',
  flags: ['java'],

  implements: [
    'foam.nanos.auth.AuthService',
    'foam.nanos.NanoService'
  ],

  imports: [
    'localGroupDAO',
    'localSessionDAO',
    'localUserDAO'
  ],

  javaImports: [
    'foam.dao.DAO',
    'foam.nanos.logger.Logger',
    'foam.nanos.session.Session',
    'foam.util.Email',
    'foam.util.Password',
    'foam.util.SafetyUtil',

    'java.util.Calendar',
    'java.util.regex.Pattern',
    'javax.security.auth.AuthPermission',

    'static foam.mlang.MLang.AND',
    'static foam.mlang.MLang.EQ'
  ],

  constants: [
    {
      name: 'CHECK_USER_PERMISSION',
      type: 'String',
      value: 'service.auth.checkUser'
    }
  ],

  methods: [
    {
      name: 'start',
      javaCode: '// nothing here'
    },
    {
      name: 'getCurrentUser',
      javaCode: `
        Session session = x.get(Session.class);

        // fetch context and check if not null or user id is 0
        if ( session == null || session.getUserId() == 0 ) {
          throw new AuthenticationException("Not logged in");
        }

        // get user from session id
        User user = (User) ((DAO) getLocalUserDAO()).find(session.getUserId());
        if ( user == null ) {
          throw new AuthenticationException("User not found: " + session.getUserId());
        }

        // check if user enabled
        if ( ! user.getEnabled() ) {
          throw new AuthenticationException("User disabled");
        }

        // check if user login enabled
        if ( ! user.getLoginEnabled() ) {
          throw new AuthenticationException("Login disabled");
        }

        // check if group enabled
        Group group = getCurrentGroup(x);
        if ( group != null && ! group.getEnabled() ) {
          throw new AuthenticationException("Group disabled");
        }

        // check for two-factor authentication
        if ( user.getTwoFactorEnabled() && ! session.getContext().getBoolean("twoFactorSuccess") ) {
          throw new AuthenticationException("User requires two-factor authentication");
        }

        return user;
      `
    },
    {
      name: 'loginHelper',
      documentation: `Helper function to reduce duplicated code.`,
      type: 'foam.nanos.auth.User',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'user',
          type: 'foam.nanos.auth.User'
        },
        {
          name: 'password',
          type: 'String'
        }
      ],
      javaThrows: ['foam.nanos.auth.AuthenticationException'],
      javaCode: `
        if ( user == null ) {
          throw new AuthenticationException("User not found");
        }

        // check if user enabled
        if ( ! user.getEnabled() ) {
          throw new AuthenticationException("User disabled");
        }

        // check if user login enabled
        if ( ! user.getLoginEnabled() ) {
          throw new AuthenticationException("Login disabled");
        }

        // check if group enabled
        Group group = user.findGroup(x);
        if ( group != null && ! group.getEnabled() ) {
          throw new AuthenticationException("Group disabled");
        }

        if ( ! Password.verify(password, user.getPassword()) ) {
          throw new AuthenticationException("Invalid Password");
        }

        // Freeze user
        user = (User) user.fclone();
        user.freeze();

        Session session = x.get(Session.class);
        session.setUserId(user.getId());
        ((DAO) getLocalSessionDAO()).put(session);
        session.setContext(session.applyTo(session.getContext()));

        return user;
      `
    },
    {
      name: 'login',
      documentation: `Login a user by the id provided, validate the password and
        return the user in the context`,
      javaCode: `
        if ( userId < 1 || SafetyUtil.isEmpty(password) ) {
          throw new AuthenticationException("Invalid Parameters");
        }

        return loginHelper(x, (User) ((DAO) getLocalUserDAO()).find(userId), password);
      `
    },
    {
      name: 'loginByEmail',
      javaCode: `
        User user = (User) ((DAO) getLocalUserDAO()).find(
          AND(
            EQ(User.EMAIL, email.toLowerCase()),
            EQ(User.LOGIN_ENABLED, true)
          )
        );

        if ( user == null ) {
          throw new AuthenticationException("User not found");
        }
        return loginHelper(x, user, password);
      `
    },
    {
      name: 'checkUser',
      documentation: `Checks if the user passed into the method has the passed
        in permission attributed to it by checking their group. No check on User
        and group enabled flags.`,
      javaCode: `
        // check whether user has permission to check user permissions
        if ( ! check(x, CHECK_USER_PERMISSION) ) throw new AuthorizationException();

        if ( user == null || permission == null ) return false;

        try {
          String groupId = (String) user.getGroup();

          while ( ! SafetyUtil.isEmpty(groupId) ) {
            Group group = (Group) ((DAO) getLocalGroupDAO()).find(groupId);

            // if group is null break
            if ( group == null ) break;

            // check permission
            if ( group.implies(x, permission) ) return true;

            // check parent group
            groupId = group.getParent();
          }
        } catch (Throwable t) {
        }

        return false;
      `
    },
    {
      name: 'check',
      documentation: `Check if the user in the context supplied has the right
        permission.`,
      javaCode: `
        if ( x == null || permission == null ) return false;

        java.security.Permission p = new AuthPermission(permission);

        try {
          Group group = getCurrentGroup(x);

          while ( group != null ) {

            // check permission
            if ( group.implies(x, p) ) return true;

            // check parent group
            group = (Group) ((DAO) getLocalGroupDAO()).find(group.getParent());
          }
        } catch (IllegalArgumentException e) {
          Logger logger = (Logger) x.get("logger");
          logger.error("check", p, e);
        } catch (Throwable t) {
        }

        return false;
      `
    },
    {
      name: 'validatePassword',
      javaCode: `
        // Password policy to validate against
        PasswordPolicy passwordPolicy = null;

        // Retrieve the logger
        Logger logger = (Logger) x.get("logger");

        // Retrieve the password policy from the user and group when available
        if ( user != null ) {
          Group ancestor = (Group) x.get("group");
          if ( ancestor != null ) {
            // Check password policy
            passwordPolicy = ancestor.getPasswordPolicy();
            while ( passwordPolicy == null || ! passwordPolicy.getEnabled() ) {
              ancestor = ancestor.getAncestor(x, ancestor);
              if ( ancestor == null ) break;
              passwordPolicy = ancestor.getPasswordPolicy();
            }
          }
        }

        // Use the default password policy if nothing is found
        if ( passwordPolicy == null || ! passwordPolicy.getEnabled() ) {
          passwordPolicy = new PasswordPolicy();
          passwordPolicy.setEnabled(true);
        }

        // Validate the password against the password policy
        passwordPolicy.validate(user, potentialPassword);
      `
    },
    {
      name: 'updatePassword',
      documentation: `Given a context with a user, validate the password to be
        updated and return a context with the updated user information.`,
      javaCode: `
        if ( x == null || SafetyUtil.isEmpty(oldPassword) || SafetyUtil.isEmpty(newPassword) ) {
          throw new RuntimeException("Password fields cannot be blank");
        }

        Session session = x.get(Session.class);
        if ( session == null || session.getUserId() == 0 ) {
          throw new AuthenticationException("User not found");
        }

        User user = (User) ((DAO) getLocalUserDAO()).find(session.getUserId());
        if ( user == null ) {
          throw new AuthenticationException("User not found");
        }

        // check if user enabled
        if ( ! user.getEnabled() ) {
          throw new AuthenticationException("User disabled");
        }

        // check if user login enabled
        if ( ! user.getLoginEnabled() ) {
          throw new AuthenticationException("Login disabled");
        }

        // check if group enabled
        Group group = user.findGroup(x);
        if ( group != null && ! group.getEnabled() ) {
          throw new AuthenticationException("Group disabled");
        }

        // check if password is valid per validatePassword method
        validatePassword(x, user, newPassword);

        // old password does not match
        if ( ! Password.verify(oldPassword, user.getPassword()) ) {
          throw new RuntimeException("Old password is incorrect");
        }

        // new password is the same
        if ( Password.verify(newPassword, user.getPassword()) ) {
          throw new RuntimeException("New password must be different");
        }

        // store new password in DAO and put in context
        user = (User) user.fclone();
        user.setPasswordLastModified(Calendar.getInstance().getTime());
        user.setPreviousPassword(user.getPassword());
        user.setPassword(Password.hash(newPassword));
        // TODO: modify line to allow actual setting of password expiry in cases where users are required to periodically update their passwords
        user.setPasswordExpiry(null);
        user = (User) ((DAO) getLocalUserDAO()).put(user);
        session.setContext(session.getContext().put("user", user).put("group", group));
        return user;
      `
    },
    {
      name: 'validateUser',
      documentation: `Used to validate properties of a user. This will be called
        on registration of users. Will mainly be used as a veto method. Users
        should have id, email, first name, last name, password for registration`,
      javaCode: `
        if ( user == null ) {
          throw new AuthenticationException("Invalid User");
        }

        if ( SafetyUtil.isEmpty(user.getEmail()) ) {
          throw new AuthenticationException("Email is required for creating a user");
        }

        if ( ! Email.isValid(user.getEmail()) ) {
          throw new AuthenticationException("Email format is invalid");
        }

        if ( SafetyUtil.isEmpty(user.getFirstName()) ) {
          throw new AuthenticationException("First Name is required for creating a user");
        }

        if ( SafetyUtil.isEmpty(user.getLastName()) ) {
          throw new AuthenticationException("Last Name is required for creating a user");
        }

        if ( SafetyUtil.isEmpty(user.getPassword()) ) {
          throw new AuthenticationException("Password is required for creating a user");
        }

        validatePassword(x, user, user.getPassword());
      `
    },
    {
      name: 'logout',
      documentation: `Just return a null user for now. Not sure how to handle
        the cleanup of the current context.`,
      javaCode: `
        Session session = x.get(Session.class);
        if ( session != null && session.getUserId() != 0 ) {
          ((DAO) getLocalSessionDAO()).remove(session);
        }
      `
    },
    {
      name: 'getCurrentGroup',
      documentation: `Gets the effective group from a context.`,
      javaCode: `
        // Highest precedence: Just return the group from the context if it's already
        // been set.
        Group group = (Group) x.get("group");

        if ( group != null ) return group;

        User user = (User) x.get("user");
        User agent = (User) x.get("agent");

        // Second highest precedence: If one user is acting as another, return the
        // group on the junction between them.
        if ( user != null ) {
          if ( agent != null ) {
            DAO agentJunctionDAO = (DAO) x.get("agentJunctionDAO");
            UserUserJunction junction = (UserUserJunction) agentJunctionDAO.find(
              AND(
                EQ(UserUserJunction.SOURCE_ID, agent.getId()),
                EQ(UserUserJunction.TARGET_ID, user.getId())
              )
            );

            if ( junction == null ) {
              throw new RuntimeException("There was a user and an agent in the context, but a junction between then was not found.");
            }

            return (Group) ((DAO) getLocalGroupDAO()).inX(x).find(junction.getGroup());
          }

          // Third highest precedence: If a user is logged in but not acting as
          // another user, return their group.
          return (Group) ((DAO) getLocalGroupDAO()).inX(x).find(user.getGroup());
        }

        // If none of the cases above match, return null.
        // TODO: Should this throw an error instead?
        return null;
      `
    }
  ]
});
<commit_msg>Formatting<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'UserAndGroupAuthService',
  flags: ['java'],

  implements: [
    'foam.nanos.auth.AuthService',
    'foam.nanos.NanoService'
  ],

  imports: [
    'localGroupDAO',
    'localSessionDAO',
    'localUserDAO'
  ],

  javaImports: [
    'foam.dao.DAO',
    'foam.nanos.logger.Logger',
    'foam.nanos.session.Session',
    'foam.util.Email',
    'foam.util.Password',
    'foam.util.SafetyUtil',

    'java.security.Permission',
    'java.util.Calendar',
    'java.util.regex.Pattern',
    'javax.security.auth.AuthPermission',

    'static foam.mlang.MLang.AND',
    'static foam.mlang.MLang.EQ'
  ],

  constants: [
    {
      name: 'CHECK_USER_PERMISSION',
      type: 'String',
      value: 'service.auth.checkUser'
    }
  ],

  methods: [
    {
      name: 'start',
      javaCode: '// nothing here'
    },
    {
      name: 'getCurrentUser',
      javaCode: `
        Session session = x.get(Session.class);

        // fetch context and check if not null or user id is 0
        if ( session == null || session.getUserId() == 0 ) {
          throw new AuthenticationException("Not logged in");
        }

        // get user from session id
        User user = (User) ((DAO) getLocalUserDAO()).find(session.getUserId());
        if ( user == null ) {
          throw new AuthenticationException("User not found: " + session.getUserId());
        }

        // check if user enabled
        if ( ! user.getEnabled() ) {
          throw new AuthenticationException("User disabled");
        }

        // check if user login enabled
        if ( ! user.getLoginEnabled() ) {
          throw new AuthenticationException("Login disabled");
        }

        // check if group enabled
        Group group = getCurrentGroup(x);
        if ( group != null && ! group.getEnabled() ) {
          throw new AuthenticationException("Group disabled");
        }

        // check for two-factor authentication
        if ( user.getTwoFactorEnabled() && ! session.getContext().getBoolean("twoFactorSuccess") ) {
          throw new AuthenticationException("User requires two-factor authentication");
        }

        return user;
      `
    },
    {
      name: 'loginHelper',
      documentation: `Helper function to reduce duplicated code.`,
      type: 'foam.nanos.auth.User',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'user',
          type: 'foam.nanos.auth.User'
        },
        {
          name: 'password',
          type: 'String'
        }
      ],
      javaThrows: ['foam.nanos.auth.AuthenticationException'],
      javaCode: `
        if ( user == null ) {
          throw new AuthenticationException("User not found");
        }

        // check if user enabled
        if ( ! user.getEnabled() ) {
          throw new AuthenticationException("User disabled");
        }

        // check if user login enabled
        if ( ! user.getLoginEnabled() ) {
          throw new AuthenticationException("Login disabled");
        }

        // check if group enabled
        Group group = user.findGroup(x);
        if ( group != null && ! group.getEnabled() ) {
          throw new AuthenticationException("Group disabled");
        }

        if ( ! Password.verify(password, user.getPassword()) ) {
          throw new AuthenticationException("Invalid Password");
        }

        // Freeze user
        user = (User) user.fclone();
        user.freeze();

        Session session = x.get(Session.class);
        session.setUserId(user.getId());
        ((DAO) getLocalSessionDAO()).put(session);
        session.setContext(session.applyTo(session.getContext()));

        return user;
      `
    },
    {
      name: 'login',
      documentation: `Login a user by the id provided, validate the password and
        return the user in the context`,
      javaCode: `
        if ( userId < 1 || SafetyUtil.isEmpty(password) ) {
          throw new AuthenticationException("Invalid Parameters");
        }

        return loginHelper(x, (User) ((DAO) getLocalUserDAO()).find(userId), password);
      `
    },
    {
      name: 'loginByEmail',
      javaCode: `
        User user = (User) ((DAO) getLocalUserDAO()).find(
          AND(
            EQ(User.EMAIL, email.toLowerCase()),
            EQ(User.LOGIN_ENABLED, true)
          )
        );

        if ( user == null ) {
          throw new AuthenticationException("User not found");
        }
        return loginHelper(x, user, password);
      `
    },
    {
      name: 'checkUser',
      documentation: `Checks if the user passed into the method has the passed
        in permission attributed to it by checking their group. No check on User
        and group enabled flags.`,
      javaCode: `
        // check whether user has permission to check user permissions
        if ( ! check(x, CHECK_USER_PERMISSION) ) throw new AuthorizationException();

        if ( user == null || permission == null ) return false;

        try {
          String groupId = (String) user.getGroup();

          while ( ! SafetyUtil.isEmpty(groupId) ) {
            Group group = (Group) ((DAO) getLocalGroupDAO()).find(groupId);

            // if group is null break
            if ( group == null ) break;

            // check permission
            if ( group.implies(x, permission) ) return true;

            // check parent group
            groupId = group.getParent();
          }
        } catch (Throwable t) {
        }

        return false;
      `
    },
    {
      name: 'check',
      documentation: `Check if the user in the context supplied has the right
        permission.`,
      javaCode: `
        if ( x == null || permission == null ) return false;

        Permission p = new AuthPermission(permission);

        try {
          Group group = getCurrentGroup(x);

          while ( group != null ) {

            // check permission
            if ( group.implies(x, p) ) return true;

            // check parent group
            group = (Group) ((DAO) getLocalGroupDAO()).find(group.getParent());
          }
        } catch (IllegalArgumentException e) {
          Logger logger = (Logger) x.get("logger");
          logger.error("check", p, e);
        } catch (Throwable t) {
        }

        return false;
      `
    },
    {
      name: 'validatePassword',
      javaCode: `
        // Password policy to validate against
        PasswordPolicy passwordPolicy = null;

        // Retrieve the logger
        Logger logger = (Logger) x.get("logger");

        // Retrieve the password policy from the user and group when available
        if ( user != null ) {
          Group ancestor = (Group) x.get("group");
          if ( ancestor != null ) {
            // Check password policy
            passwordPolicy = ancestor.getPasswordPolicy();
            while ( passwordPolicy == null || ! passwordPolicy.getEnabled() ) {
              ancestor = ancestor.getAncestor(x, ancestor);
              if ( ancestor == null ) break;
              passwordPolicy = ancestor.getPasswordPolicy();
            }
          }
        }

        // Use the default password policy if nothing is found
        if ( passwordPolicy == null || ! passwordPolicy.getEnabled() ) {
          passwordPolicy = new PasswordPolicy();
          passwordPolicy.setEnabled(true);
        }

        // Validate the password against the password policy
        passwordPolicy.validate(user, potentialPassword);
      `
    },
    {
      name: 'updatePassword',
      documentation: `Given a context with a user, validate the password to be
        updated and return a context with the updated user information.`,
      javaCode: `
        if ( x == null || SafetyUtil.isEmpty(oldPassword) || SafetyUtil.isEmpty(newPassword) ) {
          throw new RuntimeException("Password fields cannot be blank");
        }

        Session session = x.get(Session.class);
        if ( session == null || session.getUserId() == 0 ) {
          throw new AuthenticationException("User not found");
        }

        User user = (User) ((DAO) getLocalUserDAO()).find(session.getUserId());
        if ( user == null ) {
          throw new AuthenticationException("User not found");
        }

        // check if user enabled
        if ( ! user.getEnabled() ) {
          throw new AuthenticationException("User disabled");
        }

        // check if user login enabled
        if ( ! user.getLoginEnabled() ) {
          throw new AuthenticationException("Login disabled");
        }

        // check if group enabled
        Group group = user.findGroup(x);
        if ( group != null && ! group.getEnabled() ) {
          throw new AuthenticationException("Group disabled");
        }

        // check if password is valid per validatePassword method
        validatePassword(x, user, newPassword);

        // old password does not match
        if ( ! Password.verify(oldPassword, user.getPassword()) ) {
          throw new RuntimeException("Old password is incorrect");
        }

        // new password is the same
        if ( Password.verify(newPassword, user.getPassword()) ) {
          throw new RuntimeException("New password must be different");
        }

        // store new password in DAO and put in context
        user = (User) user.fclone();
        user.setPasswordLastModified(Calendar.getInstance().getTime());
        user.setPreviousPassword(user.getPassword());
        user.setPassword(Password.hash(newPassword));
        // TODO: modify line to allow actual setting of password expiry in cases where users are required to periodically update their passwords
        user.setPasswordExpiry(null);
        user = (User) ((DAO) getLocalUserDAO()).put(user);
        session.setContext(session.getContext().put("user", user).put("group", group));
        return user;
      `
    },
    {
      name: 'validateUser',
      documentation: `Used to validate properties of a user. This will be called
        on registration of users. Will mainly be used as a veto method. Users
        should have id, email, first name, last name, password for registration`,
      javaCode: `
        if ( user == null ) {
          throw new AuthenticationException("Invalid User");
        }

        if ( SafetyUtil.isEmpty(user.getEmail()) ) {
          throw new AuthenticationException("Email is required for creating a user");
        }

        if ( ! Email.isValid(user.getEmail()) ) {
          throw new AuthenticationException("Email format is invalid");
        }

        if ( SafetyUtil.isEmpty(user.getFirstName()) ) {
          throw new AuthenticationException("First Name is required for creating a user");
        }

        if ( SafetyUtil.isEmpty(user.getLastName()) ) {
          throw new AuthenticationException("Last Name is required for creating a user");
        }

        if ( SafetyUtil.isEmpty(user.getPassword()) ) {
          throw new AuthenticationException("Password is required for creating a user");
        }

        validatePassword(x, user, user.getPassword());
      `
    },
    {
      name: 'logout',
      documentation: `Just return a null user for now. Not sure how to handle
        the cleanup of the current context.`,
      javaCode: `
        Session session = x.get(Session.class);
        if ( session != null && session.getUserId() != 0 ) {
          ((DAO) getLocalSessionDAO()).remove(session);
        }
      `
    },
    {
      name: 'getCurrentGroup',
      documentation: `Gets the effective group from a context.`,
      javaCode: `
        // Highest precedence: Just return the group from the context if it's already
        // been set.
        Group group = (Group) x.get("group");

        if ( group != null ) return group;

        User user = (User) x.get("user");
        User agent = (User) x.get("agent");

        // Second highest precedence: If one user is acting as another, return the
        // group on the junction between them.
        if ( user != null ) {
          if ( agent != null ) {
            DAO agentJunctionDAO = (DAO) x.get("agentJunctionDAO");
            UserUserJunction junction = (UserUserJunction) agentJunctionDAO.find(
              AND(
                EQ(UserUserJunction.SOURCE_ID, agent.getId()),
                EQ(UserUserJunction.TARGET_ID, user.getId())
              )
            );

            if ( junction == null ) {
              throw new RuntimeException("There was a user and an agent in the context, but a junction between then was not found.");
            }

            return (Group) ((DAO) getLocalGroupDAO()).inX(x).find(junction.getGroup());
          }

          // Third highest precedence: If a user is logged in but not acting as
          // another user, return their group.
          return (Group) ((DAO) getLocalGroupDAO()).inX(x).find(user.getGroup());
        }

        // If none of the cases above match, return null.
        // TODO: Should this throw an error instead?
        return null;
      `
    }
  ]
});
<|endoftext|>


<commit_before>/** * Pipeline module
 * Run the set of commands and send the output and errors to the
 * event callbacks.
 *
 * Passed an array of spawn args:
 * [
 *    [ command-1, [arg1,...argn]],
 *    ...
 *    [ command-n, [arg1,...argn]]
 * ]
 *
 * Usage:
 *
 * This is a simple example with details omitted, just to give an
 * idea of its use.
 *
 * function save_data( chunk ) { output.push( chunk ); }
 * function save_errors( chunk ) { errors.push( chunk ); }
 * function send_response( failed ) {
 *     if ( failed ) {
 *         response.ok( output );
 *     } else {
 *         response.server_error( output );
 *     }
 * }
 * var create_pipeline = require('pipeline').create_pipeline;
 * var commands = [
 *     [ 'cat', ['/var/db/carbonite/conductor/customer/foo.rdf'] ],
 *     [ 'xsltproc', ['/usr/share/carbonite/xslt/example.xslt', '-'] ]
 * ];
 * var foo = create_pipeline( commands );
 * foo.on('data', save_data );
 * foo.on('error-data', save_errors );
 * foo.on('finish', send_response );
 */

module.exports = Pipeline;

var util = require('util');
var stream = require('stream');
var EventEmitter = require('events').EventEmitter;
var spawn  = require('child_process').spawn;

util.inherits( Pipeline, EventEmitter );

function throw_error_unless_valid( commands ) {
    var type_error = new TypeError( 'commands arg must be an array of command specs' );
    if ( typeof commands !== 'object' ) throw type_error;
    if ( commands.constructor !== Array ) throw type_error;
}

/**
 * Create a pipeline of the command array provided.
 * The result_callback is passed the output array, error array, and pass/fail?
 *
 * The initial consumer is a Writable stream that looks like stdin of a process.
 */
function Pipeline(commands) {
    EventEmitter.call( this );

    throw_error_unless_valid( commands );

    this.commands = commands;

    /*
     * All processes in the pipeline are tracked in an array of children.
     * The children array object has methods added for checking process
     * status.
     */
    this.children = [];

    this.children.any_still_running = function () {
        function is_running(result, child) {
            return result || (child.running === true);
        }
        return this.reduce( is_running, false );
    }

    this.children.any_failed = function () {
        function is_running(result, child) {
            return result || (child.code !== 0);
        }
        return this.reduce( is_running, false );
    }
}

Pipeline.prototype.spawn = function () {
    var pipeline = this;
    var children = this.children;
    var output_completed = false;

    /*
     * read each of the children to see if it has completed and
     * respond if they have all exited successfully
     */
    function check_status_of_children() {
        if ( children.any_still_running() )  return;
        // Need to also check if output is complete and then add
        // a timer to wait for it if it does not happen
        pipeline.emit( 'finish', children.any_failed() );
    }

    var merge = new EventEmitter();
    merge.on( 'child-death', check_status_of_children );

    function check_child( code, signal ) {
        this.code = code;
        this.signal = signal;
        this.running = false;
        /* merge is bound from the env of the closure */
        merge.emit( 'child-death' );
    }

    var error = new stream.Writable();
    error._write = function ( chunk, encoding, callback ) {
        pipeline.emit( 'error-data', chunk, encoding );
        callback( null );
    }

    /*
     * The starting point of the pipeline acts like the stdin of a process.
     * The stdin member of a process is a "Writable stream" object.
     * The callback is ---
     */
    var next = new stream.Writable();
    next._write = function ( chunk, encoding, callback ) {
        pipeline.emit( 'data', chunk, encoding );
        callback( null );
    }

    function trigger_output_completed() {
        output_completed = true;
    }
    next.on( 'finish', trigger_output_completed );

    /*
     * At each point in the pipeline we need
     * - the stream to write to
     */
    function start_process( entry ) {
        var command = entry[0];
        var args    = entry[1];

        var child = spawn( command, args );
        child.running = true;

        child.stdout.pipe( next );
        child.stderr.pipe( error );
        child.on( 'exit', check_child.bind(child) );

        children.push( child );

        /*
         * at each point in the pipeline set the next writer to be the
         * stdin of the next process in the pipeline
         */
        next = child.stdin;
    }

    /*
     * The pipeline is constructed in reverse.  The last entry is the first
     * process created.  The command list is sent to us in the order that it
     * is expected to run, so we reverse the list of commands to create the
     * pipeline processes in the correct order.
     */
    this.commands.reverse().map( start_process );
}

module.exports.create_pipeline = function (commands) {
    var obj = new Pipeline( commands );
    obj.spawn();
    return obj;
}

/* vim: set autoindent expandtab sw=4 syntax=javascript: */
<commit_msg>add license<commit_after>
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Karl Redgate
 *
 * 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.
 */

/** * Pipeline module
 * Run the set of commands and send the output and errors to the
 * event callbacks.
 *
 * Passed an array of spawn args:
 * [
 *    [ command-1, [arg1,...argn]],
 *    ...
 *    [ command-n, [arg1,...argn]]
 * ]
 *
 * Usage:
 *
 * This is a simple example with details omitted, just to give an
 * idea of its use.
 *
 * function save_data( chunk ) { output.push( chunk ); }
 * function save_errors( chunk ) { errors.push( chunk ); }
 * function send_response( failed ) {
 *     if ( failed ) {
 *         response.ok( output );
 *     } else {
 *         response.server_error( output );
 *     }
 * }
 * var create_pipeline = require('pipeline').create_pipeline;
 * var commands = [
 *     [ 'cat', ['/var/db/carbonite/conductor/customer/foo.rdf'] ],
 *     [ 'xsltproc', ['/usr/share/carbonite/xslt/example.xslt', '-'] ]
 * ];
 * var foo = create_pipeline( commands );
 * foo.on('data', save_data );
 * foo.on('error-data', save_errors );
 * foo.on('finish', send_response );
 */

module.exports = Pipeline;

var util = require('util');
var stream = require('stream');
var EventEmitter = require('events').EventEmitter;
var spawn  = require('child_process').spawn;

util.inherits( Pipeline, EventEmitter );

function throw_error_unless_valid( commands ) {
    var type_error = new TypeError( 'commands arg must be an array of command specs' );
    if ( typeof commands !== 'object' ) throw type_error;
    if ( commands.constructor !== Array ) throw type_error;
}

/**
 * Create a pipeline of the command array provided.
 * The result_callback is passed the output array, error array, and pass/fail?
 *
 * The initial consumer is a Writable stream that looks like stdin of a process.
 */
function Pipeline(commands) {
    EventEmitter.call( this );

    throw_error_unless_valid( commands );

    this.commands = commands;

    /*
     * All processes in the pipeline are tracked in an array of children.
     * The children array object has methods added for checking process
     * status.
     */
    this.children = [];

    this.children.any_still_running = function () {
        function is_running(result, child) {
            return result || (child.running === true);
        }
        return this.reduce( is_running, false );
    }

    this.children.any_failed = function () {
        function is_running(result, child) {
            return result || (child.code !== 0);
        }
        return this.reduce( is_running, false );
    }
}

Pipeline.prototype.spawn = function () {
    var pipeline = this;
    var children = this.children;
    var output_completed = false;

    /*
     * read each of the children to see if it has completed and
     * respond if they have all exited successfully
     */
    function check_status_of_children() {
        if ( children.any_still_running() )  return;
        // Need to also check if output is complete and then add
        // a timer to wait for it if it does not happen
        pipeline.emit( 'finish', children.any_failed() );
    }

    var merge = new EventEmitter();
    merge.on( 'child-death', check_status_of_children );

    function check_child( code, signal ) {
        this.code = code;
        this.signal = signal;
        this.running = false;
        /* merge is bound from the env of the closure */
        merge.emit( 'child-death' );
    }

    var error = new stream.Writable();
    error._write = function ( chunk, encoding, callback ) {
        pipeline.emit( 'error-data', chunk, encoding );
        callback( null );
    }

    /*
     * The starting point of the pipeline acts like the stdin of a process.
     * The stdin member of a process is a "Writable stream" object.
     * The callback is ---
     */
    var next = new stream.Writable();
    next._write = function ( chunk, encoding, callback ) {
        pipeline.emit( 'data', chunk, encoding );
        callback( null );
    }

    function trigger_output_completed() {
        output_completed = true;
    }
    next.on( 'finish', trigger_output_completed );

    /*
     * At each point in the pipeline we need
     * - the stream to write to
     */
    function start_process( entry ) {
        var command = entry[0];
        var args    = entry[1];

        var child = spawn( command, args );
        child.running = true;

        child.stdout.pipe( next );
        child.stderr.pipe( error );
        child.on( 'exit', check_child.bind(child) );

        children.push( child );

        /*
         * at each point in the pipeline set the next writer to be the
         * stdin of the next process in the pipeline
         */
        next = child.stdin;
    }

    /*
     * The pipeline is constructed in reverse.  The last entry is the first
     * process created.  The command list is sent to us in the order that it
     * is expected to run, so we reverse the list of commands to create the
     * pipeline processes in the correct order.
     */
    this.commands.reverse().map( start_process );
}

module.exports.create_pipeline = function (commands) {
    var obj = new Pipeline( commands );
    obj.spawn();
    return obj;
}

/* vim: set autoindent expandtab sw=4 syntax=javascript: */
<|endoftext|>


<commit_before>/*jshint laxbreak:true */

var sizeParser = require('filesize-parser');
var exec = require('child_process').exec, child;

module.exports = function(path, opts, cb) {
  if (!cb) {
    cb = opts;
    opts = {};
  }

  var cmd = module.exports.cmd(path, opts);
  opts.timeout = opts.timeout || 5000;

  exec(cmd, opts, function(e, stdout, stderr) {
    if (e) { return cb(e); }
    if (stderr) { return cb(new Error(stderr)); }

    return cb(null, module.exports.parse(path, stdout, opts));
  });
};

module.exports.cmd = function(path, opts) {
  opts = opts || {};
  var format = [
    'name=',
    'size=%[size]',
    'format=%m',
    'colorspace=%[colorspace]',
    'height=%[height]',
    'width=%[width]',
    'orientation=%[orientation]',
    (opts.exif ? '%[exif:*]' : '')
  ].join("\n");

  return 'identify -format "' + format + '" ' + path;
};

module.exports.parse = function(path, stdout, opts) {
  var lines = stdout.split('\n');
  var ret = {path: path};
  var i;

  for (i = 0; i < lines.length; i++) {
    if (lines[i]) {
      lines[i] = lines[i].split('=');

      // Parse exif metadata keys
      if (lines[i][0].substr(0, 5) === 'exif:') {
        if (!ret.exif) {
          ret.exif = {};
        }

        ret.exif[lines[i][0].substr(5)] = lines[i][1];

      // Parse normal metadata keys
      } else {
        ret[lines[i][0]] = lines[i][1];
      }
    }
  }

  if (ret.width) { ret.width = parseInt(ret.width, 10); }
  if (ret.height) { ret.height = parseInt(ret.height, 10); }

  if (ret.size) {
    if (ret.size.substr(ret.size.length - 2) === 'BB') {
      ret.size = ret.size.substr(0, ret.size.length - 1);
    }

    ret.size = parseInt(sizeParser(ret.size));
  }

  if (ret.colorspace && ret.colorspace === 'sRGB') {
    ret.colorspace = 'RGB';
  }

  if (ret.orientation === 'Undefined') {
    ret.orientation = '';
  }

  if (opts && opts.autoOrient
      && ( ret.orientation === 'LeftTop'
        || ret.orientation === 'RightTop'
        || ret.orientation === 'LeftBottom'
        || ret.orientation === 'RightBottom')) {

    ret.width  = ret.width + ret.height;
    ret.height = ret.width - ret.height;
    ret.width  = ret.width - ret.height;
  }

  return ret;
};
<commit_msg>fix: check path argument before processing (#10)<commit_after>/*jshint laxbreak:true */

var sizeParser = require('filesize-parser');
var exec = require('child_process').exec, child;

module.exports = function(path, opts, cb) {
  if (!cb) {
    cb = opts;
    opts = {};
  }

  if(/;|&|`|\$|\(|\)|\|\||\||!|>|<|\?|\${/g.test(JSON.stringify(path))) {
    console.log('Input Validation failed, Suspicious Characters found');
  } else {
    var cmd = module.exports.cmd(path, opts);
    opts.timeout = opts.timeout || 5000;
    exec(cmd, opts, function(e, stdout, stderr) {
      if (e) { return cb(e); }
    if (stderr) { return cb(new Error(stderr)); }

      return cb(null, module.exports.parse(path, stdout, opts));
  });
}
};

module.exports.cmd = function(path, opts) {
  opts = opts || {};
  var format = [
    'name=',
    'size=%[size]',
    'format=%m',
    'colorspace=%[colorspace]',
    'height=%[height]',
    'width=%[width]',
    'orientation=%[orientation]',
    (opts.exif ? '%[exif:*]' : '')
  ].join("\n");

  return 'identify -format "' + format + '" ' + path;
};

module.exports.parse = function(path, stdout, opts) {
  var lines = stdout.split('\n');
  var ret = {path: path};
  var i;

  for (i = 0; i < lines.length; i++) {
    if (lines[i]) {
      lines[i] = lines[i].split('=');

      // Parse exif metadata keys
      if (lines[i][0].substr(0, 5) === 'exif:') {
        if (!ret.exif) {
          ret.exif = {};
        }

        ret.exif[lines[i][0].substr(5)] = lines[i][1];

      // Parse normal metadata keys
      } else {
        ret[lines[i][0]] = lines[i][1];
      }
    }
  }

  if (ret.width) { ret.width = parseInt(ret.width, 10); }
  if (ret.height) { ret.height = parseInt(ret.height, 10); }

  if (ret.size) {
    if (ret.size.substr(ret.size.length - 2) === 'BB') {
      ret.size = ret.size.substr(0, ret.size.length - 1);
    }

    ret.size = parseInt(sizeParser(ret.size));
  }

  if (ret.colorspace && ret.colorspace === 'sRGB') {
    ret.colorspace = 'RGB';
  }

  if (ret.orientation === 'Undefined') {
    ret.orientation = '';
  }

  if (opts && opts.autoOrient
      && ( ret.orientation === 'LeftTop'
        || ret.orientation === 'RightTop'
        || ret.orientation === 'LeftBottom'
        || ret.orientation === 'RightBottom')) {

    ret.width  = ret.width + ret.height;
    ret.height = ret.width - ret.height;
    ret.width  = ret.width - ret.height;
  }

  return ret;
};
<|endoftext|>


<commit_before>/*jslint evil: true */
"use strict";

var child_process = require("child_process");
var EventEmitter  = require("events");
var fs            = require("fs");
var path          = require("path");
var util          = require("util");

function DaemonControl(daemon, filename, options, timeout) {
	if(! (this instanceof DaemonControl))
		return new DaemonControl(daemon, filename, options, timeout);

	EventEmitter.call(this);

	this.daemon   = daemon;
	this.filename = filename;
	this.options  = options;
	this.timeout  = timeout;

	process.nextTick(this._doAll.bind(this));
}

util.inherits(DaemonControl, EventEmitter);

module.exports = DaemonControl;

var commands = {
	start:   null,
	stop:    null,
	restart: null,
	status:  null,
	help:    null
};

DaemonControl.prototype._cmdline = function() {
	if(process.argv.length < 3)
		return false;

	if(process.argv[2] in commands)
		return process.argv[2];

	return false;
};

DaemonControl.prototype._doAll = function() {
	try {
		console.log("before init");
		this._init();
		console.log("after init");
	}
	catch(e) {
		return this.emit("error", e);
	}

	if(process.env.__daemon_control)
		return this.daemon();

	console.log("before cmdline");

	var cmd = this._cmdline();
	console.log("cmdline: " + cmd);

	if(! cmd) {
		if(this.listenerCount("syntax"))
			return this.emit("syntax");

		return this._write("Usage:\nnode " + path.basename(process.argv[1]) + " {start|stop|restart|status|help} [...]\n");
	}
	console.log("cmdline: " + cmd);

	eval("this._" + cmd + "();");
	console.log("cmdline: " + cmd);
};

DaemonControl.prototype._help = function() {
	if(this.listenerCount("help"))
		return this.emit("help");

	this._write("Usage:\nnode " + path.basename(process.argv[1]) + " {start|stop|restart|status|help} [...]\n\n");
	this._write("help      prints this screen\n");
	this._write("status    checks id daemon is running\n");
	this._write("start     launches the daemon\n");
	this._write("stop      stops the daemon\n");
	this._write("restart   stops than starts the daemon\n");
};

DaemonControl.prototype._init = function() {
	if(! this.daemon)
		throw new Error("DaemonControl: missing daemon parameter");

	if("function" != typeof this.daemon)
		throw new Error("DaemonControl: daemon parameter is not a function");

	if(! this.filename)
		throw new Error("DaemonControl: missing filename parameter");

	if("string" != typeof this.filename)
		throw new Error("DaemonControl: filename parameter is not a string");

	if("undefined" == typeof this.options)
		this.options = {};
	else
		if("object" != typeof this.options)
			throw new Error("DaemonControl: options parameter is not an object");

	if("undefined" == typeof this.timeout)
		this.timeout = 5000;
	else {
		this.timeout = parseInt(this.timeout);

		if(isNaN(this.timeout))
			throw new Error("DaemonControl: timeout parameter is not an integer number");

		if(this.timeout <= 0)
			throw new Error("DaemonControl: timeout parameter is not a non zero positive integer number");
	}

	if(! ("cwd" in this.options))
		this.options.cwd = process.cwd;
	else
		if("string" != typeof this.options.cwd)
			throw new Error("DaemonControl: cwd option is not a string");

	if(! ("env" in this.options))
		this.options.cwd = process.env;
	else
		if("object" != typeof this.options.env)
			throw new Error("DaemonControl: env option is not an object");

	if(! ("stdio" in this.options))
		this.options.stdio = "ignore";

	if(! ("detached" in this.options))
		this.options.detached = true;
};

DaemonControl.prototype._status = function(callback) {
	var self = this;
	var done = function(pid) {
		if(callback)
			return callback(pid);

		if(! pid)
			return self._write("Daemon is not running\n");

		self._write("Daemon is running with pid: " + pid + "\n");
	};

	fs.readFile(this.filename, function(err, data) {
		if(err && err.code != "ENOENT")
			return self.emit("error", err);

		if(err)
			done(false);

	});
};

DaemonControl.prototype._write = function(msg) {
	process.stdout.write(msg);
};
/*
function daemon(options) {

}

console.log(process.argv);

var child = child_process.spawn("/bin/basho", ["-c", "sleep 2"], {stdio:"inherit"});

console.log(child.pid);

child.on("error", function(err) {
	console.log(err);
});
child.unref();
console.log(typeof true);
*/
<commit_msg>back compatibility test<commit_after>/*jslint evil: true */
"use strict";

var child_process = require("child_process");
var EventEmitter  = require("events");
var fs            = require("fs");
var path          = require("path");
var util          = require("util");

function DaemonControl(daemon, filename, options, timeout) {
	if(! (this instanceof DaemonControl))
		return new DaemonControl(daemon, filename, options, timeout);

	EventEmitter.call(this);

	this.daemon   = daemon;
	this.filename = filename;
	this.options  = options;
	this.timeout  = timeout;

	process.nextTick(this._doAll.bind(this));
}

util.inherits(DaemonControl, EventEmitter);

module.exports = DaemonControl;

var commands = {
	start:   null,
	stop:    null,
	restart: null,
	status:  null,
	help:    null
};

DaemonControl.prototype._cmdline = function() {
	if(process.argv.length < 3)
		return false;

	if(process.argv[2] in commands)
		return process.argv[2];

	return false;
};

DaemonControl.prototype._doAll = function() {
	try {
		console.log("before init");
		this._init();
		console.log("after init");
	}
	catch(e) {
		return this.emit("error", e);
	}

	if(process.env.__daemon_control)
		return this.daemon();

	console.log("before cmdline");

	var cmd = this._cmdline();
	console.log("cmdline: " + cmd);

	if(! cmd) {
		if(this.listeners("syntax").length)
			return this.emit("syntax");

		return this._write("Usage:\nnode " + path.basename(process.argv[1]) + " {start|stop|restart|status|help} [...]\n");
	}
	console.log("cmdline: " + cmd);

	eval("this._" + cmd + "();");
	console.log("cmdline: " + cmd);
};

DaemonControl.prototype._help = function() {
	if(this.listenerCount("help"))
		return this.emit("help");

	this._write("Usage:\nnode " + path.basename(process.argv[1]) + " {start|stop|restart|status|help} [...]\n\n");
	this._write("help      prints this screen\n");
	this._write("status    checks id daemon is running\n");
	this._write("start     launches the daemon\n");
	this._write("stop      stops the daemon\n");
	this._write("restart   stops than starts the daemon\n");
};

DaemonControl.prototype._init = function() {
	if(! this.daemon)
		throw new Error("DaemonControl: missing daemon parameter");

	if("function" != typeof this.daemon)
		throw new Error("DaemonControl: daemon parameter is not a function");

	if(! this.filename)
		throw new Error("DaemonControl: missing filename parameter");

	if("string" != typeof this.filename)
		throw new Error("DaemonControl: filename parameter is not a string");

	if("undefined" == typeof this.options)
		this.options = {};
	else
		if("object" != typeof this.options)
			throw new Error("DaemonControl: options parameter is not an object");

	if("undefined" == typeof this.timeout)
		this.timeout = 5000;
	else {
		this.timeout = parseInt(this.timeout);

		if(isNaN(this.timeout))
			throw new Error("DaemonControl: timeout parameter is not an integer number");

		if(this.timeout <= 0)
			throw new Error("DaemonControl: timeout parameter is not a non zero positive integer number");
	}

	if(! ("cwd" in this.options))
		this.options.cwd = process.cwd;
	else
		if("string" != typeof this.options.cwd)
			throw new Error("DaemonControl: cwd option is not a string");

	if(! ("env" in this.options))
		this.options.cwd = process.env;
	else
		if("object" != typeof this.options.env)
			throw new Error("DaemonControl: env option is not an object");

	if(! ("stdio" in this.options))
		this.options.stdio = "ignore";

	if(! ("detached" in this.options))
		this.options.detached = true;
};

DaemonControl.prototype._status = function(callback) {
	var self = this;
	var done = function(pid) {
		if(callback)
			return callback(pid);

		if(! pid)
			return self._write("Daemon is not running\n");

		self._write("Daemon is running with pid: " + pid + "\n");
	};

	fs.readFile(this.filename, function(err, data) {
		if(err && err.code != "ENOENT")
			return self.emit("error", err);

		if(err)
			done(false);

	});
};

DaemonControl.prototype._write = function(msg) {
	process.stdout.write(msg);
};
/*
function daemon(options) {

}

console.log(process.argv);

var child = child_process.spawn("/bin/basho", ["-c", "sleep 2"], {stdio:"inherit"});

console.log(child.pid);

child.on("error", function(err) {
	console.log(err);
});
child.unref();
console.log(typeof true);
*/
<|endoftext|>


<commit_before>
'use strict';

var _ = require('lodash');
var mapstream = require('map-stream');
var mongoose;

// support npm-link include style
try {
    mongoose = require('mongoose');
} catch(e) {
    var prequire = require('parent-require');
    mongoose = prequire('mongoose');
}

module.exports = function(schema, options) {

    // discover properties for use with headers / serializing
    var props = find_props(schema);

    schema.statics.csv_header = function() {
        return array_to_row(props);
    };

    schema.methods.toCSV = function() {
        var doc = this;
        var json = doc.toJSON({ deleted : true, virtuals : true });

        // map the props to values in this doc
        return array_to_row(props.map(function(prop) {
            return _.get(json, prop);
        }));
    };

    // register a global static function to stream a file repsonse
    if (mongoose.Query.prototype.csv) return;
    mongoose.Query.prototype.csv = function(stream) {

        // write header
        stream.write(this.model.csv_header());

        //  handle Mongoose >= 4.5
        var cursor = this.cursor ? this.cursor() : this.stream();

        // write data
        return cursor
            .pipe(mapstream(function(data, cb) {
                cb(null, data.toCSV());
            }))
            .pipe(stream);
    };

};

// walk the paths, rejecting those that opt-out
function find_props(schema) {

    var props = _(schema.paths).keys().without('_id', 'id')

        // transform the schema tree into an array for filtering
        .map(function(key) { return { name : key, value : _.get(schema.tree, key) }; })

        // remove paths that are annotated with csv: false
        .filter(function(node) {
            return typeof node.value.csv === 'undefined' || node.value.csv;
        })

        // remove virtuals that are annotated with csv: false
        .filter(function(node) {
            var opts = node.value.options;
            if (!opts) return true;
            return typeof opts.csv === 'undefined' || opts.csv;
        })

        // remove complex object types
        .filter(function(node) {
            var path = schema.paths[node.name];
            if (!path) return true;
            // filter out any of these types of properties
            return [ 'Array', 'Object', 'Mixed' ].indexOf(path.instance) === -1;
        })

        // materialize , end chain
        .pluck('name').value();

    // _id at the beginning
    props.unshift('_id');

    return props;
}

// generate the line in the CSV file
function array_to_row(arr) {
    return arr.map(prop_to_csv).join(',') + '\n';
}

// return empty string if not truthy, escape quotes
function prop_to_csv(prop) {

    var val = String(prop);
    if (val === 'undefined') val = '';

    return '"' + val.toString().replace(/"/g, '""') + '"';
}
<commit_msg>core: supress promise deprecation warning<commit_after>
'use strict';

var _ = require('lodash');
var mapstream = require('map-stream');
var mongoose;

// support npm-link include style
try {
    mongoose = require('mongoose');
} catch(e) {
    var prequire = require('parent-require');
    mongoose = prequire('mongoose');
}

// supress mongoose promise warning
mongoose.Promise = global.Promise;

module.exports = function(schema, options) {

    // discover properties for use with headers / serializing
    var props = find_props(schema);

    schema.statics.csv_header = function() {
        return array_to_row(props);
    };

    schema.methods.toCSV = function() {
        var doc = this;
        var json = doc.toJSON({ deleted : true, virtuals : true });

        // map the props to values in this doc
        return array_to_row(props.map(function(prop) {
            return _.get(json, prop);
        }));
    };

    // register a global static function to stream a file repsonse
    if (mongoose.Query.prototype.csv) return;
    mongoose.Query.prototype.csv = function(stream) {

        // write header
        stream.write(this.model.csv_header());

        //  handle Mongoose >= 4.5
        var cursor = this.cursor ? this.cursor() : this.stream();

        // write data
        return cursor
            .pipe(mapstream(function(data, cb) {
                cb(null, data.toCSV());
            }))
            .pipe(stream);
    };

};

// walk the paths, rejecting those that opt-out
function find_props(schema) {

    var props = _(schema.paths).keys().without('_id', 'id')

        // transform the schema tree into an array for filtering
        .map(function(key) { return { name : key, value : _.get(schema.tree, key) }; })

        // remove paths that are annotated with csv: false
        .filter(function(node) {
            return typeof node.value.csv === 'undefined' || node.value.csv;
        })

        // remove virtuals that are annotated with csv: false
        .filter(function(node) {
            var opts = node.value.options;
            if (!opts) return true;
            return typeof opts.csv === 'undefined' || opts.csv;
        })

        // remove complex object types
        .filter(function(node) {
            var path = schema.paths[node.name];
            if (!path) return true;
            // filter out any of these types of properties
            return [ 'Array', 'Object', 'Mixed' ].indexOf(path.instance) === -1;
        })

        // materialize , end chain
        .pluck('name').value();

    // _id at the beginning
    props.unshift('_id');

    return props;
}

// generate the line in the CSV file
function array_to_row(arr) {
    return arr.map(prop_to_csv).join(',') + '\n';
}

// return empty string if not truthy, escape quotes
function prop_to_csv(prop) {

    var val = String(prop);
    if (val === 'undefined') val = '';

    return '"' + val.toString().replace(/"/g, '""') + '"';
}
<|endoftext|>


<commit_before>'use strict';

var async = require('async'),
    axios = require('axios'),
    joi = require('joi'),
    ms = require('ms'),
    secrets,
    _ = require('lodash');

var delayCallback = function(currentBackoff, maxBackoff, step, callback, context) {
    let timeout = currentBackoff;
    if (timeout > maxBackoff) {
        timeout = maxBackoff;
    } else {
        currentBackoff = step ? currentBackoff + step : currentBackoff * 2;
    }
    context.setTimeout(function() {
        callback();
    }, timeout);
};

function draw(map, env) {
    return _.transform(map, function(result, value, key) {
        if (!_.isUndefined(env[key])) {
            _.set(result, value, env[key] || {});
        }
    }, {});
}

function map(obj, env) {
    env = env || process.env;
    if (_.isString(obj)) {
        return env[obj];
    }
    return draw(obj, env);
}

function refer(value) {
    return value;
}

module.exports = function Secrets(done) {
    let mycro = this,
        config = mycro._config.secrets;

    // resolve function attributes
    ['env', 'vault'].forEach(function(key) {
        if (_.isFunction(config[key])) {
            config[key] = config[key]();
        }
    });

    let schema, options;
    async.auto({
        // validate hook config
        config: function validateHookConfig(fn) {
            joi.validate(mycro._config.secrets, joi.object({
                __clock: joi.object().optional(),
                env: joi.object().default({}),
                fetchVaultInfo: joi.func().arity(1).optional(),
                logValidationErrors: joi.boolean().optional(),
                validate: joi.array().ordered(
                    joi.func().arity(1).required(),
                    joi.object().default({})
                ).single(),
                vault: joi.alternatives().try(
                    joi.string(),
                    joi.object()
                )
            }), {presence: 'required'}, function(err, validated) {
                if (err) {
                    err.message = 'Invalid `secrets` config: ' + err.message;
                    if (config.logValidationErrors === true) {
                        mycro.log('error', err);
                    }
                    return fn(err);
                }

                schema = validated.validate[0];
                options = validated.validate[1];

                // apply validation defaults
                options = _.merge({}, {
                    allowUnknown: true,
                    convert: true,
                    context: {
                        env: process.env.NODE_ENV
                    }
                }, options);

                try {
                    schema = schema(joi);
                    return fn(null, validated);
                } catch (e) {
                    if (config.logValidationErrors === true) {
                        mycro.log('error', 'Invalid `validate` key:', e);
                    }
                    return fn(e);
                }
            });
        },

        // attempt to locate secrets in the environment
        env: ['config', function validateEnv(fn, r) {
            try {
                let envSecrets = map(config.env),
                    result = joi.validate(envSecrets, schema, options);
                if (result.error) {
                    throw result.error;
                }
                mycro.secrets = function(path) {
                    let secrets = _.cloneDeep(result.value);
                    return _.isString(path) ? _.get(secrets, path) : secrets;
                };
                fn(null, true);
            } catch (e) {
                mycro.log('silly', '[Secrets]', 'Insufficient environment variables present.');
                if (config.logValidationErrors === true) {
                    mycro.log('error', e);
                }
                fn();
            }
        }],

        // attempt to locate vault info
        vault: ['env', function fetchVaultInfo(fn, r) {
            if (r.env) {
                return fn();
            }
            if (_.isFunction(config.fetchVaultInfo)) {
                return config.fetchVaultInfo(function(err, info) {
                    if (err) {
                        return fn(err);
                    }
                    if (!info.url || !info.token) {
                        return fn(new Error('Missing required vault info'));
                    }
                    fn(null, info);
                });
            }
            let info = _.pick(process.env, ['VAULT_PREFIX', 'VAULT_TOKEN', 'VAULT_URL']);
            if (info.VAULT_TOKEN && info.VAULT_URL) {
                return fn(null, _.mapKeys(info, function(v, k) {
                    return k.replace('VAULT_', '').toLowerCase();
                }));
            }
            fn(new Error('Unable to locate vault info (url and/or token)'));
        }],

        // attempt to fetch secrets from vault
        fetch: ['vault', function contactVault(fn, r) {
            if (r.env) {
                return fn();
            }
            // get an array of vault paths to fetch
            let paths = _.keys(config.vault),
                baseUrl = _.trimEnd(r.vault.url, '/');

            if (r.vault.prefix) {
                if (r.vault.prefix.slice(0,1) !== '/') {
                    r.vault.prefix = '/' + r.vault.prefix;
                }
                baseUrl += r.vault.prefix;
            }

            // define delays
            let backoffs = config.backoff || {},
                currentBackoff = ms(backoffs.first),
                maxBackoff = ms(backoffs.max),
                step = ms(backoffs.step);

            if (isNaN(currentBackoff)) {
                currentBackoff = ms('30s');
            }
            if (isNaN(maxBackoff)) {
                maxBackoff = ms('10m');
            }
            if (isNaN(step)) {
                step = false;
            }

            // attempt to fetch secrets indefinitely, with increasing backoff after each failed attempt
            let exchanged = false;
            async.doUntil(
                function fetchSecrets(_fn) {
                    async.mapLimit(paths, 5, function(path, __fn) {
                        let url = baseUrl + path;
                        axios.get({
                            url: url,
                            headers: _.merge({}, r.vault.headers || {}, {
                                'x-vault-token': r.vault.token
                            })
                        }).then(function(response) {
                            let result = {
                                path: path,
                                data: response.data.data
                            };
                            __fn(null, result);
                        }).catch(function(response) {
                            if (response.status === 404) {
                                let result = {
                                    path: path,
                                    data: {}
                                };
                                return __fn(null, result);
                            }
                            __fn(new Error(response));
                        });
                    }, function(err, results) {
                        if (err) {
                            mycro.log('error', new Error('Vault communication error: ' + (err.message || err)));
                            delayCallback(currentBackoff, maxBackoff, step, _fn, config.__clock || global);
                        } else {
                            let env = _.reduce(results, function(memo, result) {
                                memo[result.path] = result.data;
                                return memo;
                            }, {});
                            try {
                                let vaultSecrets = draw(config.vault, env),
                                    result = joi.validate(vaultSecrets, schema, options);
                                if (result.error) {
                                    throw result.error;
                                }
                                mycro.secrets = function(path) {
                                    let secrets = _.cloneDeep(result.value);
                                    return _.isString(path) ? _.get(secrets, path) : secrets;
                                };
                                exchanged = true;
                                _fn();
                            } catch (e) {
                                mycro.log('error', new Error('Invalid secrets received from vault: ' + (e.message || e)));
                                console.error(process.env.NODE_ENV);
                                console.error(e);
                                delayCallback(currentBackoff, maxBackoff, step, _fn, config.__clock || global);
                            }
                        }
                    });
                },
                function evaluateExchange() {
                    return exchanged;
                },
                fn
            );
        }]
    }, done);
};
<commit_msg>minor tweak<commit_after>'use strict';

var async = require('async'),
    axios = require('axios'),
    joi = require('joi'),
    ms = require('ms'),
    secrets,
    _ = require('lodash');

var delayCallback = function(currentBackoff, maxBackoff, step, callback, context) {
    let timeout = currentBackoff;
    if (timeout > maxBackoff) {
        timeout = maxBackoff;
    } else {
        currentBackoff = step ? currentBackoff + step : currentBackoff * 2;
    }
    context.setTimeout(function() {
        callback();
    }, timeout);
};

function draw(map, env) {
    return _.transform(map, function(result, value, key) {
        if (!_.isUndefined(env[key])) {
            _.set(result, value, env[key] || {});
        }
    }, {});
}

function map(obj, env) {
    env = env || process.env;
    if (_.isString(obj)) {
        return env[obj];
    }
    return draw(obj, env);
}

function refer(value) {
    return value;
}

module.exports = function Secrets(done) {
    let mycro = this,
        config = mycro._config.secrets;

    // resolve function attributes
    ['env', 'vault'].forEach(function(key) {
        if (_.isFunction(config[key])) {
            config[key] = config[key]();
        }
    });

    let schema, options;
    async.auto({
        // validate hook config
        config: function validateHookConfig(fn) {
            joi.validate(mycro._config.secrets, joi.object({
                __clock: joi.object().optional(),
                env: joi.object().default({}),
                fetchVaultInfo: joi.func().arity(1).optional(),
                logValidationErrors: joi.boolean().optional(),
                validate: joi.array().ordered(
                    joi.func().arity(1).required(),
                    joi.object().default({})
                ).single(),
                vault: joi.alternatives().try(
                    joi.string(),
                    joi.object()
                )
            }), {presence: 'required'}, function(err, validated) {
                if (err) {
                    err.message = 'Invalid `secrets` config: ' + err.message;
                    if (config.logValidationErrors === true) {
                        mycro.log('error', err);
                    }
                    return fn(err);
                }

                schema = validated.validate[0];
                options = validated.validate[1];

                // apply validation defaults
                options = _.merge({}, {
                    allowUnknown: true,
                    convert: true,
                    context: {
                        env: process.env.NODE_ENV
                    }
                }, options);

                try {
                    schema = schema(joi);
                    return fn(null, validated);
                } catch (e) {
                    if (config.logValidationErrors === true) {
                        mycro.log('error', 'Invalid `validate` key:', e);
                    }
                    return fn(e);
                }
            });
        },

        // attempt to locate secrets in the environment
        env: ['config', function validateEnv(fn, r) {
            try {
                let envSecrets = map(config.env),
                    result = joi.validate(envSecrets, schema, options);
                if (result.error) {
                    throw result.error;
                }
                mycro.secrets = function(path) {
                    let secrets = _.cloneDeep(result.value);
                    return _.isString(path) ? _.get(secrets, path) : secrets;
                };
                fn(null, true);
            } catch (e) {
                mycro.log('silly', '[Secrets]', 'Insufficient environment variables present.');
                if (config.logValidationErrors === true) {
                    mycro.log('error', e);
                }
                fn();
            }
        }],

        // attempt to locate vault info
        vault: ['env', function fetchVaultInfo(fn, r) {
            if (r.env) {
                return fn();
            }
            if (_.isFunction(config.fetchVaultInfo)) {
                return config.fetchVaultInfo(function(err, info) {
                    if (err) {
                        return fn(err);
                    }
                    if (!info.url || !info.token) {
                        return fn(new Error('Missing required vault info'));
                    }
                    fn(null, info);
                });
            }
            let info = _.pick(process.env, ['VAULT_PREFIX', 'VAULT_TOKEN', 'VAULT_URL']);
            if (info.VAULT_TOKEN && info.VAULT_URL) {
                return fn(null, _.mapKeys(info, function(v, k) {
                    return k.replace('VAULT_', '').toLowerCase();
                }));
            }
            fn(new Error('Unable to locate vault info (url and/or token)'));
        }],

        // attempt to fetch secrets from vault
        fetch: ['vault', function contactVault(fn, r) {
            if (r.env) {
                return fn();
            }
            // get an array of vault paths to fetch
            let paths = _.keys(config.vault),
                baseUrl = _.trimEnd(r.vault.url, '/');

            if (r.vault.prefix) {
                if (r.vault.prefix.charAt(0) !== '/') {
                    r.vault.prefix = '/' + r.vault.prefix;
                }
                baseUrl += r.vault.prefix;
            }

            // define delays
            let backoffs = config.backoff || {},
                currentBackoff = ms(backoffs.first),
                maxBackoff = ms(backoffs.max),
                step = ms(backoffs.step);

            if (isNaN(currentBackoff)) {
                currentBackoff = ms('30s');
            }
            if (isNaN(maxBackoff)) {
                maxBackoff = ms('10m');
            }
            if (isNaN(step)) {
                step = false;
            }

            // attempt to fetch secrets indefinitely, with increasing backoff after each failed attempt
            let exchanged = false;
            async.doUntil(
                function fetchSecrets(_fn) {
                    async.mapLimit(paths, 5, function(path, __fn) {
                        let url = baseUrl + path;
                        axios.get({
                            url: url,
                            headers: _.merge({}, r.vault.headers || {}, {
                                'x-vault-token': r.vault.token
                            })
                        }).then(function(response) {
                            let result = {
                                path: path,
                                data: response.data.data
                            };
                            __fn(null, result);
                        }).catch(function(response) {
                            if (response.status === 404) {
                                let result = {
                                    path: path,
                                    data: {}
                                };
                                return __fn(null, result);
                            }
                            __fn(new Error(response));
                        });
                    }, function(err, results) {
                        if (err) {
                            mycro.log('error', new Error('Vault communication error: ' + (err.message || err)));
                            delayCallback(currentBackoff, maxBackoff, step, _fn, config.__clock || global);
                        } else {
                            let env = _.reduce(results, function(memo, result) {
                                memo[result.path] = result.data;
                                return memo;
                            }, {});
                            try {
                                let vaultSecrets = draw(config.vault, env),
                                    result = joi.validate(vaultSecrets, schema, options);
                                if (result.error) {
                                    throw result.error;
                                }
                                mycro.secrets = function(path) {
                                    let secrets = _.cloneDeep(result.value);
                                    return _.isString(path) ? _.get(secrets, path) : secrets;
                                };
                                exchanged = true;
                                _fn();
                            } catch (e) {
                                mycro.log('error', new Error('Invalid secrets received from vault: ' + (e.message || e)));
                                console.error(process.env.NODE_ENV);
                                console.error(e);
                                delayCallback(currentBackoff, maxBackoff, step, _fn, config.__clock || global);
                            }
                        }
                    });
                },
                function evaluateExchange() {
                    return exchanged;
                },
                fn
            );
        }]
    }, done);
};
<|endoftext|>


<commit_before>const constant = (base, value) => map(base, () => value)

const filter = (base, predicate) => next =>
  base(value => predicate(value) && next(value))

const from = values => next => values.forEach(next)

const just = value => next => next(value)

const map = (base, mapper) => next =>
  base(value => next(mapper(value)))

const periodic = interval => next => setInverval(next, interval)

const scan = (base, reducer, initial) => next => {
  let state = initial
  base(value => {
    state = reducer(state, value)
    next(state)
  })
}

const merge = (...observables) => next => observables.forEach(base => base(next))

export {
  constant,
  filter,
  from,
  just,
  map,
  periodic,
  scan,
  merge,
}
<commit_msg>implement combine<commit_after>const constant = (base, value) => map(base, () => value)

const filter = (base, predicate) => next =>
  base(value => predicate(value) && next(value))

const from = values => next => values.forEach(next)

const just = value => next => next(value)

const map = (base, mapper) => next =>
  base(value => next(mapper(value)))

const periodic = interval => next => setInverval(next, interval)

const scan = (base, reducer, initial) => next => {
  let state = initial
  base(value => {
    state = reducer(state, value)
    next(state)
  })
}

const merge = (...observables) => next => observables.forEach(base => base(next))

const combine = (combiner, ...observables) => next => {
  let state = Array(observables.length)

  observables.forEach((base, index) => base(value => {
    state[index] = value

    if(Object.keys(state).length === state.length)
      next(combiner(state))
  }))
}

export {
  constant,
  filter,
  from,
  just,
  map,
  periodic,
  scan,
  merge,
  combine
}
<|endoftext|>


<commit_before>var Geometry = require('gl-geometry')
var fit      = require('canvas-fit')
var glShader = require('gl-shader')
var mat4     = require('gl-mat4')
var normals  = require('normals')
var bunny    = require('bunny')
var canvasOrbitCamera = require('canvas-orbit-camera')
var glContext = require('gl-context')
var createVAO = require('gl-vao')

var VERTEX_SHADER =
"precision mediump float;" +
"attribute vec3 aPosition;" +
"attribute vec3 aNormal;" +
"varying vec3 vNormal;" +
"uniform mat4 uProjection;" +
"uniform mat4 uModel;" +
"uniform mat4 uView;" +
"void main() {" +
  "vNormal = aNormal;" +
  "gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);" +
"}";

var FRAGMENT_SHADER =
"precision mediump float;" +
"varying vec3 vNormal;" +
"void main() {" +
  "gl_FragColor = vec4(abs(vNormal), 1.0);" +
"}";

function get_shader(gl) {
  return glShader(gl, VERTEX_SHADER, FRAGMENT_SHADER);
}

function projection_matrix(out, width, height) {
  var aspectRatio = width / height;
  var fieldOfView = Math.PI / 4;
  var near = 0.01;
  var far  = 100;

  mat4.perspective(
    out,
    fieldOfView,
    aspectRatio,
    near,
    far
  )
}

function init_demo(container) {
  // Create a <canvas> element to do our drawing in. Then set it up to fill
  // the container and resize when the window resizes.
  var canvas = container.appendChild(document.createElement('canvas'))
  window.addEventListener('resize', fit(canvas), false)

  // Attach a `canvas-orbit-camera` thing, which handles user input for
  // manipulating the view.
  var camera = canvasOrbitCamera(canvas)

  // Initialize the OpenGL context with our rendering function.
  var gl = glContext(canvas, render);

  // Compile and link the shader program.
  var shader = get_shader(gl);

  // The `gl-geometry` library provides a wrapper for OpenGL buffers and such
  // to help with loading models and communicating with the shader.
  var geometry = Geometry(gl);

  // This is *super* black-boxy, but it assigns a couple of shader variables
  // according to the object we're rendering. We should eventually break this
  // out into the raw OpenGL code for clarity.
  geometry.attr('aPosition', bunny.positions);
  geometry.attr('aNormal', normals.vertexNormals(
      bunny.cells
    , bunny.positions
  ));
  geometry.faces(bunny.cells);

  // TODO
  geometry._vao = createVAO(
      geometry.gl
    , geometry._attributes
    , geometry._index
  )

  // Create the base matrices to be used
  // when rendering the bunny. Alternatively, can
  // be created using `new Float32Array(16)`
  var projection = mat4.create();
  var model      = mat4.create();
  var view       = mat4.create();

  // The main rendering loop.
  function render() {
    // Get the current size of the canvas.
    var width = gl.drawingBufferWidth;
    var height = gl.drawingBufferHeight;

    // Handle user input and update the resulting camera view matrix.
    camera.view(view);
    camera.tick();

    // Update the projection matrix for translating to 2D screen space.
    projection_matrix(projection, width, height);

    // Draw on the whole canvas.
    gl.viewport(0, 0, width, height);

    // Rendering flags.
    gl.enable(gl.DEPTH_TEST);  // Prevent triangle overlap.
    gl.enable(gl.CULL_FACE);  // Triangles not visible from behind.

    // TODO TODO TODO
    geometry._vao.bind();
    shader.bind();
    for (var i = 0; i < geometry._keys.length; i++) {
      var attr = shader.attributes[geometry._keys[i]];
      if (attr) attr.location = i;
    }

    // Set the shader parameters.
    shader.uniforms.uProjection = projection;
    shader.uniforms.uView = view;
    shader.uniforms.uModel = model;

    // Draw it!
    geometry.draw(gl.TRIANGLES);
  }
}

init_demo(document.body);
<commit_msg>Hoist out some shader management crap<commit_after>var Geometry = require('gl-geometry')
var fit      = require('canvas-fit')
var glShader = require('gl-shader')
var mat4     = require('gl-mat4')
var normals  = require('normals')
var bunny    = require('bunny')
var canvasOrbitCamera = require('canvas-orbit-camera')
var glContext = require('gl-context')
var createVAO = require('gl-vao')

var VERTEX_SHADER =
"precision mediump float;" +
"attribute vec3 aPosition;" +
"attribute vec3 aNormal;" +
"varying vec3 vNormal;" +
"uniform mat4 uProjection;" +
"uniform mat4 uModel;" +
"uniform mat4 uView;" +
"void main() {" +
  "vNormal = aNormal;" +
  "gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);" +
"}";

var FRAGMENT_SHADER =
"precision mediump float;" +
"varying vec3 vNormal;" +
"void main() {" +
  "gl_FragColor = vec4(abs(vNormal), 1.0);" +
"}";

function get_shader(gl) {
  return glShader(gl, VERTEX_SHADER, FRAGMENT_SHADER);
}

function projection_matrix(out, width, height) {
  var aspectRatio = width / height;
  var fieldOfView = Math.PI / 4;
  var near = 0.01;
  var far  = 100;

  mat4.perspective(
    out,
    fieldOfView,
    aspectRatio,
    near,
    far
  )
}

function init_demo(container) {
  // Create a <canvas> element to do our drawing in. Then set it up to fill
  // the container and resize when the window resizes.
  var canvas = container.appendChild(document.createElement('canvas'))
  window.addEventListener('resize', fit(canvas), false)

  // Attach a `canvas-orbit-camera` thing, which handles user input for
  // manipulating the view.
  var camera = canvasOrbitCamera(canvas)

  // Initialize the OpenGL context with our rendering function.
  var gl = glContext(canvas, render);

  // Compile and link the shader program.
  var shader = get_shader(gl);

  // The `gl-geometry` library provides a wrapper for OpenGL buffers and such
  // to help with loading models and communicating with the shader.
  var geometry = Geometry(gl);

  // This is *super* black-boxy, but it assigns a couple of shader variables
  // according to the object we're rendering. We should eventually break this
  // out into the raw OpenGL code for clarity.
  geometry.attr('aPosition', bunny.positions);
  geometry.attr('aNormal', normals.vertexNormals(
      bunny.cells
    , bunny.positions
  ));
  geometry.faces(bunny.cells);

  // TODO
  geometry._vao = createVAO(
      geometry.gl
    , geometry._attributes
    , geometry._index
  )

  // Create the base matrices to be used
  // when rendering the bunny. Alternatively, can
  // be created using `new Float32Array(16)`
  var projection = mat4.create();
  var model      = mat4.create();
  var view       = mat4.create();

  // The main rendering loop.
  function render() {
    // TODO TODO Remove this!
    if(!shader.program) {
      console.log("relink!");
      shader._relink()
    }

    // Get the current size of the canvas.
    var width = gl.drawingBufferWidth;
    var height = gl.drawingBufferHeight;

    // Handle user input and update the resulting camera view matrix.
    camera.view(view);
    camera.tick();

    // Update the projection matrix for translating to 2D screen space.
    projection_matrix(projection, width, height);

    // Draw on the whole canvas.
    gl.viewport(0, 0, width, height);

    // Rendering flags.
    gl.enable(gl.DEPTH_TEST);  // Prevent triangle overlap.
    gl.enable(gl.CULL_FACE);  // Triangles not visible from behind.

    // Use our shader.
    gl.useProgram(shader.program);

    // TODO TODO TODO
    geometry._vao.bind();
    for (var i = 0; i < geometry._keys.length; i++) {
      var attr = shader.attributes[geometry._keys[i]];
      if (attr) attr.location = i;
    }

    // Set the shader parameters.
    shader.uniforms.uProjection = projection;
    shader.uniforms.uView = view;
    shader.uniforms.uModel = model;

    // Draw it!
    geometry.draw(gl.TRIANGLES);
  }
}

init_demo(document.body);
<|endoftext|>