<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'Group',

  implements: [ 'foam.nanos.auth.EnabledAware' ],

  requires: [ 'foam.nanos.app.AppConfig' ],

  documentation: 'A Group of Users.',

  tableColumns: [ 'id', 'description', 'defaultMenu', 'parent' ],

  searchColumns: [ 'id', 'description' ],

  properties: [
    {
      class: 'String',
      name: 'id',
      documentation: 'Unique name of the Group.'
    },
    {
      class: 'Boolean',
      name: 'enabled',
      value: true
    },
    {
      class: 'String',
      name: 'description',
      documentation: 'Description of the Group.'
    },
    {
      class: 'Reference',
      name: 'parent',
      targetDAOKey: 'groupDAO',
      of: 'foam.nanos.auth.Group',
      view: {
        class: 'foam.u2.view.ReferenceView',
        placeholder: '--'
      },
      documentation: 'Parent group to inherit permissions from.'
    },
    {
      class: 'FObjectArray',
      of: 'foam.nanos.auth.Permission',
      name: 'permissions',
      documentation: 'Permissions set on group.'
    },
    // {
    //   class: 'StringArray',
    //   of: 'foam.nanos.auth.Permission',
    //   name: 'permissions2',
    //   hidden: true,
    //   view: 'foam.u2.view.StringArrayRowView',
    //   documentation: 'Permissions set on group.'
    // },
    {
      class: 'Reference',
      targetDAOKey: 'menuDAO',
      name: 'defaultMenu',
      documentation: 'Menu user redirects to after login.',
      of: 'foam.nanos.menu.Menu'
    },
    {
      class: 'Image',
      name: 'logo',
      documentation: 'Group logo.',
      displayWidth: 60
    },
    {
      class: 'String',
      name: 'topNavigation',
      value: 'foam.nanos.u2.navigation.TopNavigation',
      displayWidth: 45
    },
    {
      class: 'String',
      name: 'footerView',
      value: 'foam.nanos.u2.navigation.FooterView',
      displayWidth: 45
    },
    {
      class: 'String',
      name: 'groupCSS',
      view: { class: 'foam.u2.tag.TextArea', rows: 16, cols: 60 },
    },
    {
      class: 'Color',
      name: 'primaryColor',
      documentation: 'The following color properties can determine the color scheme of the GUI.'
    },
    { class: 'Color', name: 'secondaryColor' },
    { class: 'Color', name: 'accentColor' },
    { class: 'Color', name: 'tableColor' },
    { class: 'Color', name: 'tableHoverColor' },
    {
      class: 'String',
      name: 'url',
      value: null
    },
    {
      class: 'String',
      name: 'from',
      value: null
    },
    {
      class: 'String',
      name: 'displayName',
      value: null
    },
    {
      class: 'String',
      name: 'replyTo',
      value: null
    },
    {
      class: 'Long',
      name: 'softSessionLimit',
      documentation: `
        Users will be asked for providing a feedback once the soft session limit has been reached.
        If the user doesn't provide any feedback, system will force the user logout.

        The unit is milliseconds, so if you want to set the time limit to 10 mins, the value would be:
          600000 = 1000 * 60 * 10.

        Set the value to 0 to turn off this feature.
      `
    },
    {
      class: 'String',
      name: 'supportEmail'
    }
    /*
      FUTURE
    {
      class: 'FObjectProperty',
      of: 'AuthConfig',
      documentation: 'Custom authentication settings for this group.'
    }
    */
  ],

  javaImports: [
    'foam.core.X',
    'foam.dao.DAO',
    'foam.nanos.app.AppConfig',
    'foam.nanos.session.Session',
    'foam.util.SafetyUtil',
    'org.eclipse.jetty.server.Request',
    'javax.servlet.http.HttpServletRequest'
  ],

  methods: [
    {
      name: 'implies',
      type: 'Boolean',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'permission',
          javaType: 'java.security.Permission'
        }
      ],
      javaCode: `
        if ( getPermissions() == null ) return false;

        for ( int i = 0 ; i < permissions_.length ; i++ ) {
          foam.nanos.auth.Permission p = permissions_[i];

          if ( p.getId().startsWith("@") ) {
            DAO   dao   = (DAO) x.get("groupDAO");
            Group group = (Group) dao.find(p.getId().substring(1));

            if ( group != null && group.implies(x, permission) ) {
              return true;
            }
          } else {
            if ( new javax.security.auth.AuthPermission(p.getId()).implies(permission) ) {
              return true;
            }
          }
        }
        return false;`
      ,
      code: function(x, permissionId) {
        if ( arguments.length != 2 ) debugger;

        if ( this.permissions == null ) return false;

        for ( var i = 0 ; i < this.permissions.length ; i++ )
          if ( this.permissions[i].implies(permissionId) ) return true;

        return false;
      }
    },
    {
      name: 'getAppConfig',
      type: 'foam.nanos.app.AppConfig',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
        AppConfig config = (AppConfig) ((AppConfig) x.get("appConfig")).fclone();
        String configUrl = "";

        // FIND URL
        Group group = (Group) x.get("group");
        if ( ! SafetyUtil.isEmpty(group.getUrl()) ) {
          configUrl = group.getUrl();
        } else {
          // populate AppConfig url with request's RootUrl
          HttpServletRequest req = x.get(HttpServletRequest.class);
          if ( (req != null) && ! SafetyUtil.isEmpty(req.getRequestURI()) ) {
            configUrl = ((Request) req).getRootURL().toString();
          }
        }
        // FORCE HTTPS IN URL?
        if ( config.getForceHttps() ) {
          if ( ! configUrl.startsWith("https://") ) {
            if ( configUrl.startsWith("http://") ) {
              configUrl = "https" + configUrl.substring(4);
            } else {
              configUrl = "https://" + configUrl;
            }
          }
        }
        // SET URL
        config.setUrl(configUrl);

        // SET SupportEmail
        if ( ! SafetyUtil.isEmpty(group.getSupportEmail()) ) {
          config.setSupportEmail(group.getSupportEmail());
        }

        return config;
        `
    },
    {
      name: 'isDescendantOf',
      code: async function(groupId, groupDAO) {
        /**
         * Returns a promise that resolves to true if this group is a
         * descendant of the given group or false if it is not.
         */
        if ( ! groupId ) return false;
        if ( this.id === groupId || this.parent === groupId ) return true;
        var parent = await groupDAO.find(this.parent);
        if ( parent == null ) return false;
        return parent.isDescendantOf(groupId, groupDAO);
      },
      args: [
        { name: 'groupId',  type: 'String' },
        { name: 'groupDAO', type: 'foam.dao.DAO' }
      ],
      type: 'Boolean',
      javaCode: `
        if ( SafetyUtil.isEmpty(groupId) ) return false;
        if (
          SafetyUtil.equals(this.getId(), groupId) ||
          SafetyUtil.equals(this.getParent(), groupId)
        ) return true;
        Group parent = (Group) groupDAO.find(this.getParent());
        if ( parent == null ) return false;
        return parent.isDescendantOf(groupId, groupDAO);
      `
    }
  ]
});
<commit_msg>group.appConfig now picks up the group objects properties for appConfig<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'Group',

  implements: [ 'foam.nanos.auth.EnabledAware' ],

  requires: [ 'foam.nanos.app.AppConfig' ],

  documentation: 'A Group of Users.',

  tableColumns: [ 'id', 'description', 'defaultMenu', 'parent' ],

  searchColumns: [ 'id', 'description' ],

  properties: [
    {
      class: 'String',
      name: 'id',
      documentation: 'Unique name of the Group.'
    },
    {
      class: 'Boolean',
      name: 'enabled',
      value: true
    },
    {
      class: 'String',
      name: 'description',
      documentation: 'Description of the Group.'
    },
    {
      class: 'Reference',
      name: 'parent',
      targetDAOKey: 'groupDAO',
      of: 'foam.nanos.auth.Group',
      view: {
        class: 'foam.u2.view.ReferenceView',
        placeholder: '--'
      },
      documentation: 'Parent group to inherit permissions from.'
    },
    {
      class: 'FObjectArray',
      of: 'foam.nanos.auth.Permission',
      name: 'permissions',
      documentation: 'Permissions set on group.'
    },
    // {
    //   class: 'StringArray',
    //   of: 'foam.nanos.auth.Permission',
    //   name: 'permissions2',
    //   hidden: true,
    //   view: 'foam.u2.view.StringArrayRowView',
    //   documentation: 'Permissions set on group.'
    // },
    {
      class: 'Reference',
      targetDAOKey: 'menuDAO',
      name: 'defaultMenu',
      documentation: 'Menu user redirects to after login.',
      of: 'foam.nanos.menu.Menu'
    },
    {
      class: 'Image',
      name: 'logo',
      documentation: 'Group logo.',
      displayWidth: 60
    },
    {
      class: 'String',
      name: 'topNavigation',
      value: 'foam.nanos.u2.navigation.TopNavigation',
      displayWidth: 45
    },
    {
      class: 'String',
      name: 'footerView',
      value: 'foam.nanos.u2.navigation.FooterView',
      displayWidth: 45
    },
    {
      class: 'String',
      name: 'groupCSS',
      view: { class: 'foam.u2.tag.TextArea', rows: 16, cols: 60 },
    },
    {
      class: 'Color',
      name: 'primaryColor',
      documentation: 'The following color properties can determine the color scheme of the GUI.'
    },
    { class: 'Color', name: 'secondaryColor' },
    { class: 'Color', name: 'accentColor' },
    { class: 'Color', name: 'tableColor' },
    { class: 'Color', name: 'tableHoverColor' },
    {
      class: 'String',
      name: 'url',
      value: null
    },
    {
      class: 'String',
      name: 'from',
      value: null
    },
    {
      class: 'String',
      name: 'displayName',
      value: null
    },
    {
      class: 'String',
      name: 'replyTo',
      value: null
    },
    {
      class: 'Long',
      name: 'softSessionLimit',
      documentation: `
        Users will be asked for providing a feedback once the soft session limit has been reached.
        If the user doesn't provide any feedback, system will force the user logout.

        The unit is milliseconds, so if you want to set the time limit to 10 mins, the value would be:
          600000 = 1000 * 60 * 10.

        Set the value to 0 to turn off this feature.
      `
    },
    {
      class: 'String',
      name: 'supportEmail'
    }
    /*
      FUTURE
    {
      class: 'FObjectProperty',
      of: 'AuthConfig',
      documentation: 'Custom authentication settings for this group.'
    }
    */
  ],

  javaImports: [
    'foam.core.X',
    'foam.dao.DAO',
    'foam.nanos.app.AppConfig',
    'foam.nanos.session.Session',
    'foam.util.SafetyUtil',
    'org.eclipse.jetty.server.Request',
    'javax.servlet.http.HttpServletRequest'
  ],

  methods: [
    {
      name: 'implies',
      type: 'Boolean',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'permission',
          javaType: 'java.security.Permission'
        }
      ],
      javaCode: `
        if ( getPermissions() == null ) return false;

        for ( int i = 0 ; i < permissions_.length ; i++ ) {
          foam.nanos.auth.Permission p = permissions_[i];

          if ( p.getId().startsWith("@") ) {
            DAO   dao   = (DAO) x.get("groupDAO");
            Group group = (Group) dao.find(p.getId().substring(1));

            if ( group != null && group.implies(x, permission) ) {
              return true;
            }
          } else {
            if ( new javax.security.auth.AuthPermission(p.getId()).implies(permission) ) {
              return true;
            }
          }
        }
        return false;`
      ,
      code: function(x, permissionId) {
        if ( arguments.length != 2 ) debugger;

        if ( this.permissions == null ) return false;

        for ( var i = 0 ; i < this.permissions.length ; i++ )
          if ( this.permissions[i].implies(permissionId) ) return true;

        return false;
      }
    },
    {
      name: 'getAppConfig',
      type: 'foam.nanos.app.AppConfig',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
        // Find Group details, by iterating up through group.parent
        AppConfig config          = (AppConfig) ((AppConfig) x.get("appConfig")).fclone();
        String configUrl          = "";
        String configSupportEmail = "";
        Boolean urlFound          = false;
        Boolean supportEmailFound = false;
        Group group               = this;
        String grp                = "";
        DAO groupDAO              = (DAO) x.get("groupDAO");

        while ( group != null && ! (urlFound && supportEmailFound)) {
          configUrl          = urlFound ? configUrl : group.getUrl();
          configSupportEmail = supportEmailFound ? configSupportEmail : group.getSupportEmail();

          // Once true, stay true
          urlFound          = urlFound   ? urlFound   : ! SafetyUtil.isEmpty(configUrl);
          supportEmailFound = supportEmailFound ? supportEmailFound : ! SafetyUtil.isEmpty(configSupportEmail);

          grp   = group.getParent();
          group = (Group)groupDAO.find(grp);
        }

        // FIND URL
        if ( ! urlFound ) {
          // populate AppConfig url with request's RootUrl
          HttpServletRequest req = x.get(HttpServletRequest.class);
          if ( (req != null) && ! SafetyUtil.isEmpty(req.getRequestURI()) ) {
            configUrl = ((Request) req).getRootURL().toString();
          }
        }

        // FORCE HTTPS IN URL?
        if ( config.getForceHttps() ) {
          if ( ! configUrl.startsWith("https://") ) {
            if ( configUrl.startsWith("http://") ) {
              configUrl = "https" + configUrl.substring(4);
            } else {
              configUrl = "https://" + configUrl;
            }
          }
        }

        // SET URL
        config.setUrl(configUrl);

        // SET SupportEmail
        if ( supportEmailFound ) {
          config.setSupportEmail(configSupportEmail);
        }

        return config;
        `
    },
    {
      name: 'isDescendantOf',
      code: async function(groupId, groupDAO) {
        /**
         * Returns a promise that resolves to true if this group is a
         * descendant of the given group or false if it is not.
         */
        if ( ! groupId ) return false;
        if ( this.id === groupId || this.parent === groupId ) return true;
        var parent = await groupDAO.find(this.parent);
        if ( parent == null ) return false;
        return parent.isDescendantOf(groupId, groupDAO);
      },
      args: [
        { name: 'groupId',  type: 'String' },
        { name: 'groupDAO', type: 'foam.dao.DAO' }
      ],
      type: 'Boolean',
      javaCode: `
        if ( SafetyUtil.isEmpty(groupId) ) return false;
        if (
          SafetyUtil.equals(this.getId(), groupId) ||
          SafetyUtil.equals(this.getParent(), groupId)
        ) return true;
        Group parent = (Group) groupDAO.find(this.getParent());
        if ( parent == null ) return false;
        return parent.isDescendantOf(groupId, groupDAO);
      `
    }
  ]
});
<|endoftext|>


<commit_before><commit_msg>Only delete visit.generated if it exists<commit_after><|endoftext|>


<commit_before><commit_msg>Beginning to flesh out display class<commit_after><|endoftext|>


<commit_before><commit_msg>eventemitter: lint<commit_after><|endoftext|>


<commit_before><commit_msg>converting dates again<commit_after><|endoftext|>


<commit_before>var UUID = require('./../uuid' );

function forceLogin(keycloak, request, response) {
  var host = request.hostname;
  var headerHost = request.headers.host.split(':');
  var port = headerHost[1] || '';
  var protocol = request.protocol;

  var redirectUrl = protocol + '://' + host + ( port === '' ? '' : ':' + port ) + request.url + '?auth_callback=1';

  if ( request.session ) {
    request.session.auth_redirect_uri = redirectUrl;
  }

  var uuid = UUID();
  var loginURL = keycloak.loginUrl( uuid, redirectUrl );
  response.redirect( loginURL );
}

function simpleGuard(role,token) {
  return token.hasRole(role);
}

module.exports = function(keycloak, spec) {

  var guard;

  if ( typeof spec == 'function' ) {
    guard = spec;
  } else if ( typeof spec == 'string' ) {
    guard = simpleGuard.bind(undefined, spec);
  }

  return function protect(request, response, next) {
    if ( request.kauth && request.kauth.grant ) {
      if ( ! guard || guard( request.kauth.grant.access_token, request, response ) ) {
        return next();
      }

      return keycloak.accessDenied(request,response,next);
    }

    if (keycloak.config.bearerOnly){
      return keycloak.accessDenied(request,response,next);
    }else{
      forceLogin(keycloak, request, response);
    }

  };
};
<commit_msg>Changed redirect URL to use originalUrl instead of url as it was stripping the baseUrl<commit_after>var UUID = require('./../uuid' );

function forceLogin(keycloak, request, response) {
  var host = request.hostname;
  var headerHost = request.headers.host.split(':');
  var port = headerHost[1] || '';
  var protocol = request.protocol;

  var redirectUrl = protocol + '://' + host + ( port === '' ? '' : ':' + port ) + request.originalUrl + '?auth_callback=1';

  if ( request.session ) {
    request.session.auth_redirect_uri = redirectUrl;
  }

  var uuid = UUID();
  var loginURL = keycloak.loginUrl( uuid, redirectUrl );
  response.redirect( loginURL );
}

function simpleGuard(role,token) {
  return token.hasRole(role);
}

module.exports = function(keycloak, spec) {

  var guard;

  if ( typeof spec == 'function' ) {
    guard = spec;
  } else if ( typeof spec == 'string' ) {
    guard = simpleGuard.bind(undefined, spec);
  }

  return function protect(request, response, next) {
    if ( request.kauth && request.kauth.grant ) {
      if ( ! guard || guard( request.kauth.grant.access_token, request, response ) ) {
        return next();
      }

      return keycloak.accessDenied(request,response,next);
    }

    if (keycloak.config.bearerOnly){
      return keycloak.accessDenied(request,response,next);
    }else{
      forceLogin(keycloak, request, response);
    }

  };
};
<|endoftext|>


<commit_before><commit_msg>moved event#improve, color, whats to basetheme<commit_after><|endoftext|>


<commit_before><commit_msg>debugging<commit_after><|endoftext|>


<commit_before><commit_msg>bug fixes<commit_after><|endoftext|>


<commit_before><commit_msg>adding each and if, refactoring and removing old style method of processing to help my ability to support<commit_after><|endoftext|>


<commit_before><commit_msg>Remove weird space char<commit_after><|endoftext|>


<commit_before><commit_msg>fix error, when cpu metrics are temporary missing<commit_after><|endoftext|>


<commit_before>// REACT COMPONENTS
import React from 'react';
import {
  Form,
  Radio,
  Checkbox,
  Header,
} from 'semantic-ui-react';

// PROJECT COMPONENTS
import {
  FormPartsContainer,
  FormHeading,
  IntervalColumnHeadings,
  CashFlowRow,
  ControlledRadioYesNo,
  AttentionArrow,
} from './formHelpers';
import {
  ContractRentField,
  RentShareField,
  PlainRentRow,
} from './rentFields';
import CashFlowRowAfterConfirm from './CashFlowRowAfterConfirm';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// LOGIC
import {
  getEveryMember,
  isHeadOrSpouse,
  getDependentMembers,
  isDisabled,
  isUnder13,
} from '../utils/getMembers';
import { getUnder13Expenses } from '../utils/cashflow';

// ========================================
// COMPONENTS
// ========================================
const EarnedFrom = function ({ hasExpenses, cashflowProps, children }) {

  if (hasExpenses) {

    // Because we're familiar with the benefit code, we
    // happen to know these values don't need to be reset
    // to 0, even if the client erases childcare expenses.
    // Not sure if that's a great general practice, though.
    return (

        <AttentionArrow/>
        <CashFlowRowAfterConfirm { ...cashflowProps }>
          { children }
        </CashFlowRowAfterConfirm>

    );

  } else {
    return null;
  }

};  // End EarnedFrom

const Utilities = function ({ current, type, time, setClientProperty }) {

  let climate     = current.climateControl,
      electricity = current.nonHeatElectricity,
      phone       = current.phone,
      fuelAssist  = current.fuelAssistance;

  let setChecked = function (evnt, inputProps) {
    var obj = { ...inputProps, value: inputProps.checked };
    setClientProperty(evnt, obj);
  };  // End setChecked()

  return (

      Which of these utilities do you pay for?

      <Checkbox
        name={ 'climateControl' }
        label={ 'Heating or cooling (e.g. A/C during summer)' }
        checked={ climate }
        onChange={ setChecked } />

      <Checkbox
        name={ 'nonHeatElectricity' }
        label={ 'Electricity for non-heating purposes' }
        checked={ electricity }
        onChange={ setChecked } />

      <Checkbox
        name={ 'phone' }
        label={ 'Telephone service' }
        checked={ phone }
        onChange={ setChecked } />

      <ControlledRadioYesNo
        labelText = { 'Do you get Fuel Assistance?' }
        checked   = { fuelAssist }
        name      = { 'fuelAssistance' }
        onChange  = { setClientProperty } />

  );
};  // End Utilities(<>)

const HousingDetails = function ({ current, type, time, setClientProperty }) {

  let housing = current.housing,
      sharedProps = {
        timeState:         current,
        current:           current,
        type:              type,
        time:              time,
        setClientProperty: setClientProperty,
      };

  if (current.housing === 'voucher') {
    return (

        <ContractRentField { ...sharedProps } />
        <RentShareField { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeless') {
    return null;

  } else if (housing === 'renter') {
    return (

        <PlainRentRow { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeowner') {
    return (

        <IntervalColumnHeadings type={ type } />
        <CashFlowRow
          { ...sharedProps }
          generic={ 'mortgage' }> Mortgage
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'housingInsurance' }> Insurance Costs
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'propertyTax' }> Property Tax
        </CashFlowRow>
        <Utilities { ...sharedProps } />

    );

  }  // end which expenses
};  // End HousingDetails(<>)

const HousingRadio = function ({ currentValue, label, time, setClientProperty }) {

  var value = label.toLowerCase();

  return (
    <Form.Field>
      <Radio
        name={ 'housing' }
        label={ label }
        value={ value }
        checked={ currentValue === value }
        onChange={ setClientProperty } />
    </Form.Field>
  );

};  // End HousingRadio(<>)

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {string} props.type - 'expense' or 'income', etc., for classes
 * @param {string} props.time - 'current' or 'future'
 * @param {function} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const Housing = function ({ current, type, time, setClientProperty }) {

  // We're using a bunch of radio buttons. Since `checked` is defined
  // in Radio components, `setClientProperty()` would store it, but we
  // want the value, so get rid of checked.
  /** Makes sure values are propagated to 'current' properties if needed. */
  let ensureRouteAndValue = function (evnt, inputProps) {
    var obj = { ...inputProps, name: inputProps.name, value: inputProps.value, checked: null };
    setClientProperty(evnt, obj);
  };

  let sharedProps = {
    current:           current,
    type:              type,
    time:              time,
    setClientProperty: ensureRouteAndValue,
  };

  return (

      <FormHeading>Housing</FormHeading>

      { current.housing === 'voucher'
        ? null
        :

          What is your housing situation?
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeless' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Renter' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeowner' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />

}

      <HousingDetails { ...sharedProps } />

  );

};  // End Housing()

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {object} props.time - 'current' or 'future'
 * @param {object} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const ExpensesFormContent = function ({ current, time, setClientProperty }) {

  let type        = 'expense',
      household   = current.household,
      sharedProps = { timeState: current, type: type, time: time, setClientProperty: setClientProperty };

  /** @todo Make an age-checking function to
   *     keep household data structure under
   *     control in one place. */
  var isOver12 = function (member) {
    return !isUnder13(member);
  };

  // Won't include head or spouse
  var allDependents = getDependentMembers(household),
      under13       = getEveryMember(allDependents, isUnder13),
      over12        = getEveryMember(allDependents, isOver12);

  // 'Elderly' here is using the lowest common denominator - SNAP standards.
  var isElderlyOrDisabled = function (member) {
    return isDisabled(member) || member.m_age >= 60;
  };
  var elderlyOrDisabled = getEveryMember(household, isElderlyOrDisabled),
      elderlyOrDisabledHeadOrSpouse = getEveryMember(elderlyOrDisabled, isHeadOrSpouse);

  return (

      { under13.length > 0
        ?

          <FormHeading subheading = { 'A "child" is a person 12 or younger. Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Reasonable Unreimbursed Non-Medical Child Care
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childBeforeAndAfterSchoolCare' }> Before- and after-school care
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childOtherCare' }> Other care
          </CashFlowRow>

          <EarnedFrom
            hasExpenses   ={ getUnder13Expenses(current) !== 0 }
            cashflowProps ={{
              ...sharedProps,
              generic:      'earnedBecauseOfChildCare',
              confirmLabel: 'If you didn\'t have that child care, would it affect your current job?',
            }}>
            How much less would you make?
          </EarnedFrom>

        : null
      }

      { current.hasSnap
        ?

          <FormHeading>Child Support</FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childSupportPaidOut' }> Legally obligated child support
          </CashFlowRow>

        : null
      }

      {/* Head or spouse can't be a dependent, so they don't count. */}
      { over12.length > 0
        ?

          <FormHeading subheading = { 'For the care of people who are older than 12, but are still dependents (those under 18 or disabled). Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Dependent Care of Persons Over 12 Years of Age
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultOtherCare' }> Other care
          </CashFlowRow>

        : null
      }

      { elderlyOrDisabled.length > 0
        ?

          <FormHeading>Unreimbursed Disabled/Handicapped/Elderly Assistance</FormHeading>

Unreimbursed expenses to cover care attendants and auxiliary apparatus for any family member who is elderly or is a person with disabilities. Auxiliary apparatus are items such as wheelchairs, ramps, adaptations to vehicles, or special equipment to enable a blind person to read or type, but only if these items are directly related to permitting the disabled person or other family member to work.

Examples of eligible disability assistance expenses:

The payments made on a motorized wheelchair for the 42 year old son of the head of household enable the son to leave the house and go to work each day on his own. Prior to the purchase of the motorized wheelchair, the son was unable to make the commute to work. These payments are an eligible disability assistance expense.

Payments to a care attendant to stay with a disabled 16-year-old child allow the child’s mother to go to work every day. These payments are an eligible disability assistance allowance.

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'disabledAssistance' }> Disabled/Handicapped assistance
          </CashFlowRow>

          <EarnedFrom
            hasExpenses   ={ current.disabledAssistance !== 0 }
            cashflowProps ={{
              ...sharedProps,
              generic:      'earnedBecauseOfAdultCare',
              confirmLabel: 'If you didn\'t have that assistance, would it affect your current job?',
            }}>
            How much less would you make?
          </EarnedFrom>

        : null
      }

      {/** These medical expenses don't count for Section 8 unless
        *     the disabled person is the head or spouse. From
        *     {@link http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf}
        *     Appendix B, item (D) */}
      { elderlyOrDisabledHeadOrSpouse.length > 0 || (current.hasSnap && elderlyOrDisabled.length > 0)
        ?

          <FormHeading>Unreimbursed Medical Expenses</FormHeading>

Do not repeat anything you already listed in the section above. Examples of allowable medical expenses:

The orthodontist expenses for a child’s braces.

Services of doctors and health care professionals.

Services of health care facilities.

Medical insurance premiums.

Prescription/non-prescription medicines (prescribed by a physician).

Transportation to treatment (cab fare, bus fare, mileage).

Dental expenses, eyeglasses, hearing aids, batteries.

Live-in or periodic medical assistance.

Monthly payment on accumulated medical bills (regular monthly payments on a bill that was previously incurred).

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic='disabledMedical'> Disabled/Elderly medical expenses
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic='otherMedical'> Medical expenses of other members
          </CashFlowRow>

        : null
      }

      <Housing
        current={ current }
        time={ time }
        type={ type }
        setClientProperty={ setClientProperty } />

  );

};  // End ExpensesFormContent()

/**
 * @todo SNAP: Does a medical assistant's payments count as a medical expense?
 *     (Answer: Yes. @see {@link https://www.mass.gov/service-details/snap-verifications})
 * @todo SNAP: Medical expense only matters if household has elder/disabled, but
 *     are they any medical expenses or only those of the disabled person? "Medical
 *     Expenses for Disabled or Elderly". Also, do they sometimes need to
 *     enter medical expenses even if they don't have an elderly or disabled
 *     household member?
 */

/**
  * @function
  * @param {object} props
  * @param {function} props.changeClient - Setting client state
  * @param {function} props.previousStep - Go to previous form step
  * @param {function} props.nextStep - Go to next form step
  * @param {object} props.client - Object will all the data for calculating benefits
  *
  * @returns React element
  */
// `props` is a cloned version of the original props. References broken.
const CurrentExpensesStep = function ({ changeClient, previousStep, nextStep, client }) {

  const setTimeProp = getTimeSetter('current', changeClient);

  return (
    <Form className = 'expense-form flex-item flex-column'>
      <FormPartsContainer
        title     = { 'Current Household Expenses' }
        clarifier = { null }
        left      = {{ name: 'Previous', func: previousStep }}
        right     = {{ name: 'Next', func: nextStep }}>
        <ExpensesFormContent
          setClientProperty={ setTimeProp }
          current={ client.current }
          time={ 'current' } />
      </FormPartsContainer>
    </Form>
  );

};  // End CurrentExpensesStep()

export { CurrentExpensesStep };
<commit_msg>Linting<commit_after>// REACT COMPONENTS
import React from 'react';
import {
  Form,
  Radio,
  Checkbox,
  Header,
} from 'semantic-ui-react';

// PROJECT COMPONENTS
import {
  FormPartsContainer,
  FormHeading,
  IntervalColumnHeadings,
  CashFlowRow,
  ControlledRadioYesNo,
  AttentionArrow,
} from './formHelpers';
import {
  ContractRentField,
  RentShareField,
  PlainRentRow,
} from './rentFields';
import CashFlowRowAfterConfirm from './CashFlowRowAfterConfirm';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// LOGIC
import {
  getEveryMember,
  isHeadOrSpouse,
  getDependentMembers,
  isDisabled,
  isUnder13,
} from '../utils/getMembers';
import { getUnder13Expenses } from '../utils/cashflow';

// ========================================
// COMPONENTS
// ========================================
const EarnedFrom = function ({ hasExpenses, cashflowProps, children }) {

  if (hasExpenses) {

    // Because we're familiar with the benefit code, we
    // happen to know these values don't need to be reset
    // to 0, even if the client erases childcare expenses.
    // Not sure if that's a great general practice, though.
    return (

        <AttentionArrow />
        <CashFlowRowAfterConfirm { ...cashflowProps }>
          { children }
        </CashFlowRowAfterConfirm>

    );

  } else {
    return null;
  }

};  // End EarnedFrom

const Utilities = function ({ current, type, time, setClientProperty }) {

  let climate     = current.climateControl,
      electricity = current.nonHeatElectricity,
      phone       = current.phone,
      fuelAssist  = current.fuelAssistance;

  let setChecked = function (evnt, inputProps) {
    var obj = { ...inputProps, value: inputProps.checked };
    setClientProperty(evnt, obj);
  };  // End setChecked()

  return (

      Which of these utilities do you pay for?

      <Checkbox
        name={ 'climateControl' }
        label={ 'Heating or cooling (e.g. A/C during summer)' }
        checked={ climate }
        onChange={ setChecked } />

      <Checkbox
        name={ 'nonHeatElectricity' }
        label={ 'Electricity for non-heating purposes' }
        checked={ electricity }
        onChange={ setChecked } />

      <Checkbox
        name={ 'phone' }
        label={ 'Telephone service' }
        checked={ phone }
        onChange={ setChecked } />

      <ControlledRadioYesNo
        labelText = { 'Do you get Fuel Assistance?' }
        checked   = { fuelAssist }
        name      = { 'fuelAssistance' }
        onChange  = { setClientProperty } />

  );
};  // End Utilities(<>)

const HousingDetails = function ({ current, type, time, setClientProperty }) {

  let housing = current.housing,
      sharedProps = {
        timeState:         current,
        current:           current,
        type:              type,
        time:              time,
        setClientProperty: setClientProperty,
      };

  if (current.housing === 'voucher') {
    return (

        <ContractRentField { ...sharedProps } />
        <RentShareField { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeless') {
    return null;

  } else if (housing === 'renter') {
    return (

        <PlainRentRow { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeowner') {
    return (

        <IntervalColumnHeadings type={ type } />
        <CashFlowRow
          { ...sharedProps }
          generic={ 'mortgage' }> Mortgage
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'housingInsurance' }> Insurance Costs
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'propertyTax' }> Property Tax
        </CashFlowRow>
        <Utilities { ...sharedProps } />

    );

  }  // end which expenses
};  // End HousingDetails(<>)

const HousingRadio = function ({ currentValue, label, time, setClientProperty }) {

  var value = label.toLowerCase();

  return (
    <Form.Field>
      <Radio
        name={ 'housing' }
        label={ label }
        value={ value }
        checked={ currentValue === value }
        onChange={ setClientProperty } />
    </Form.Field>
  );

};  // End HousingRadio(<>)

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {string} props.type - 'expense' or 'income', etc., for classes
 * @param {string} props.time - 'current' or 'future'
 * @param {function} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const Housing = function ({ current, type, time, setClientProperty }) {

  // We're using a bunch of radio buttons. Since `checked` is defined
  // in Radio components, `setClientProperty()` would store it, but we
  // want the value, so get rid of checked.
  /** Makes sure values are propagated to 'current' properties if needed. */
  let ensureRouteAndValue = function (evnt, inputProps) {
    var obj = { ...inputProps, name: inputProps.name, value: inputProps.value, checked: null };
    setClientProperty(evnt, obj);
  };

  let sharedProps = {
    current:           current,
    type:              type,
    time:              time,
    setClientProperty: ensureRouteAndValue,
  };

  return (

      <FormHeading>Housing</FormHeading>

      { current.housing === 'voucher'
        ? null
        :

          What is your housing situation?
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeless' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Renter' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeowner' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />

}

      <HousingDetails { ...sharedProps } />

  );

};  // End Housing()

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {object} props.time - 'current' or 'future'
 * @param {object} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const ExpensesFormContent = function ({ current, time, setClientProperty }) {

  let type        = 'expense',
      household   = current.household,
      sharedProps = { timeState: current, type: type, time: time, setClientProperty: setClientProperty };

  /** @todo Make an age-checking function to
   *     keep household data structure under
   *     control in one place. */
  var isOver12 = function (member) {
    return !isUnder13(member);
  };

  // Won't include head or spouse
  var allDependents = getDependentMembers(household),
      under13       = getEveryMember(allDependents, isUnder13),
      over12        = getEveryMember(allDependents, isOver12);

  // 'Elderly' here is using the lowest common denominator - SNAP standards.
  var isElderlyOrDisabled = function (member) {
    return isDisabled(member) || member.m_age >= 60;
  };
  var elderlyOrDisabled = getEveryMember(household, isElderlyOrDisabled),
      elderlyOrDisabledHeadOrSpouse = getEveryMember(elderlyOrDisabled, isHeadOrSpouse);

  return (

      { under13.length > 0
        ?

          <FormHeading subheading = { 'A "child" is a person 12 or younger. Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Reasonable Unreimbursed Non-Medical Child Care
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childBeforeAndAfterSchoolCare' }> Before- and after-school care
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childOtherCare' }> Other care
          </CashFlowRow>

          <EarnedFrom
            hasExpenses   ={ getUnder13Expenses(current) !== 0 }
            cashflowProps ={{
              ...sharedProps,
              generic:      'earnedBecauseOfChildCare',
              confirmLabel: 'If you didn\'t have that child care, would it affect your current job?',
            }}>
            How much less would you make?
          </EarnedFrom>

        : null
      }

      { current.hasSnap
        ?

          <FormHeading>Child Support</FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childSupportPaidOut' }> Legally obligated child support
          </CashFlowRow>

        : null
      }

      {/* Head or spouse can't be a dependent, so they don't count. */}
      { over12.length > 0
        ?

          <FormHeading subheading = { 'For the care of people who are older than 12, but are still dependents (those under 18 or disabled). Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Dependent Care of Persons Over 12 Years of Age
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultOtherCare' }> Other care
          </CashFlowRow>

        : null
      }

      { elderlyOrDisabled.length > 0
        ?

          <FormHeading>Unreimbursed Disabled/Handicapped/Elderly Assistance</FormHeading>

Unreimbursed expenses to cover care attendants and auxiliary apparatus for any family member who is elderly or is a person with disabilities. Auxiliary apparatus are items such as wheelchairs, ramps, adaptations to vehicles, or special equipment to enable a blind person to read or type, but only if these items are directly related to permitting the disabled person or other family member to work.

Examples of eligible disability assistance expenses:

The payments made on a motorized wheelchair for the 42 year old son of the head of household enable the son to leave the house and go to work each day on his own. Prior to the purchase of the motorized wheelchair, the son was unable to make the commute to work. These payments are an eligible disability assistance expense.

Payments to a care attendant to stay with a disabled 16-year-old child allow the child’s mother to go to work every day. These payments are an eligible disability assistance allowance.

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'disabledAssistance' }> Disabled/Handicapped assistance
          </CashFlowRow>

          <EarnedFrom
            hasExpenses   ={ current.disabledAssistance !== 0 }
            cashflowProps ={{
              ...sharedProps,
              generic:      'earnedBecauseOfAdultCare',
              confirmLabel: 'If you didn\'t have that assistance, would it affect your current job?',
            }}>
            How much less would you make?
          </EarnedFrom>

        : null
      }

      {/** These medical expenses don't count for Section 8 unless
        *     the disabled person is the head or spouse. From
        *     {@link http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf}
        *     Appendix B, item (D) */}
      { elderlyOrDisabledHeadOrSpouse.length > 0 || (current.hasSnap && elderlyOrDisabled.length > 0)
        ?

          <FormHeading>Unreimbursed Medical Expenses</FormHeading>

Do not repeat anything you already listed in the section above. Examples of allowable medical expenses:

The orthodontist expenses for a child’s braces.

Services of doctors and health care professionals.

Services of health care facilities.

Medical insurance premiums.

Prescription/non-prescription medicines (prescribed by a physician).

Transportation to treatment (cab fare, bus fare, mileage).

Dental expenses, eyeglasses, hearing aids, batteries.

Live-in or periodic medical assistance.

Monthly payment on accumulated medical bills (regular monthly payments on a bill that was previously incurred).

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic='disabledMedical'> Disabled/Elderly medical expenses
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic='otherMedical'> Medical expenses of other members
          </CashFlowRow>

        : null
      }

      <Housing
        current={ current }
        time={ time }
        type={ type }
        setClientProperty={ setClientProperty } />

  );

};  // End ExpensesFormContent()

/**
 * @todo SNAP: Does a medical assistant's payments count as a medical expense?
 *     (Answer: Yes. @see {@link https://www.mass.gov/service-details/snap-verifications})
 * @todo SNAP: Medical expense only matters if household has elder/disabled, but
 *     are they any medical expenses or only those of the disabled person? "Medical
 *     Expenses for Disabled or Elderly". Also, do they sometimes need to
 *     enter medical expenses even if they don't have an elderly or disabled
 *     household member?
 */

/**
  * @function
  * @param {object} props
  * @param {function} props.changeClient - Setting client state
  * @param {function} props.previousStep - Go to previous form step
  * @param {function} props.nextStep - Go to next form step
  * @param {object} props.client - Object will all the data for calculating benefits
  *
  * @returns React element
  */
// `props` is a cloned version of the original props. References broken.
const CurrentExpensesStep = function ({ changeClient, previousStep, nextStep, client }) {

  const setTimeProp = getTimeSetter('current', changeClient);

  return (
    <Form className = 'expense-form flex-item flex-column'>
      <FormPartsContainer
        title     = { 'Current Household Expenses' }
        clarifier = { null }
        left      = {{ name: 'Previous', func: previousStep }}
        right     = {{ name: 'Next', func: nextStep }}>
        <ExpensesFormContent
          setClientProperty={ setTimeProp }
          current={ client.current }
          time={ 'current' } />
      </FormPartsContainer>
    </Form>
  );

};  // End CurrentExpensesStep()

export { CurrentExpensesStep };
<|endoftext|>


<commit_before>'use strict';

var Promise = require('bluebird');
var path = require('path');
var fs = require('fs');
var tildify = require('tildify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var stringUtils = require('../utils/string');

function validateComponentName (filename) {
  if (filename.indexOf('-') === -1) {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(filename),
      gutil.colors.red(' must be a dashize string. ex: my-component')
    );
    gutil.log(
      gutil.colors.red('[-Error:]  Generate task has been canceled')
    );
    process.exit(0);
  }
}

function injectSrcPath (srcPath, type) {
  var injectTemplateGenerator;
  // when `type` is `route` or `component`, will generate its template file
  if (type === 'route' || type === 'component') {
    injectTemplateGenerator = [{
      type:          'template',
      injection:     ( type === 'component' ) ? 'components' : true,
      generatorPath: path.join(__dirname, '..', 'skeletons/generators/template.js')
    }];
    srcPath = srcPath.concat(injectTemplateGenerator);
  }

  // Default, it will generate whatever user inserts the `type`
  srcPath = srcPath.concat([{
    type:          type,
    generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '.js'
  }]);
  // return the modified `srcPath` array
  return srcPath;
}

function generatorEngine
(type, srcPath, injection, moduleName, moduleDashedName, fileName, destPath) {
  var ext = (type === 'template') ? '.hbs' : '.js';
  var fullFilePath = destPath + '/' + fileName + ext;

  // if the file has existed, it will abort the task
  // if return true, mean that it is an injection file, which already exist in the system
  // handle the case in the next condition
  var stopGenerateFile = checkFileExisted(fullFilePath, injection, fileName, ext, destPath);
  // check if template is existed or not, not going to kill the process
  // only stop the generator task on this operation
  if (stopGenerateFile) {
    return;
  }

  // Classify the plain module name without its type
  var classifyName = stringUtils.classify(moduleDashedName);

  return gulp.src(srcPath)
    .pipe(replace(/__NAMESPACE__/g, moduleName))
    // __DASHERIZE_NAMESPACE__  mainly used in `-test` generator
    .pipe(replace(/__DASHERIZE_NAMESPACE__/g, moduleDashedName))
    // __CLASSIFY_NAMESPACE__ mainly used in regular generator
    .pipe(replace(/__CLASSIFY_NAMESPACE__/g, classifyName))
    .pipe(rename({
      basename: fileName,
      extname:  ext
    }))
    .on('end', function () {
      gutil.log(
        gutil.colors.green('[-done:] Generate'),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.green('at'),
        gutil.colors.magenta(tildify(destPath))
      );
    })
    .pipe(gulp.dest(destPath));
}

// @describe	generate an model,view,store,controller from base template
function runTasks (generator, options) {
  // check for the options mode, to generate an unit test file or not
  var isGeneratingTest = options.test || false;

  var type = generator.type;
  var name = generator.name;
  var pathName = '';
  var moduleName = '';
  var moduleDashedName = '';
  var pathNested; // Boolean
  var fileName; // setup the fileName which used for rename module
  var srcPath = []; // the filePath/srcPath would be used to generate files

  // based on the passing name arguments, to determine it is an nested folder structure
  // or it is a simple file generation. assign a var `fileName` for current file name
  if (name.indexOf('/') > -1) {
    name = name.split('/');
    pathNested = true;
    fileName = name.pop();
  } else {
    pathNested = false;
    fileName = name;
  }

  // handle the error case when arg is `component:foo`
  // component name has to be dash separated string
  // case 1: `em g component:name`         <= simple case
  // case 2: `em g component:nested/name`  <= nested case

  // when type is template, name[0] is component, name of nest path has to be dashized string
  // case 3: `em g template:component/name`  <= nested case in template. defined after "||"
  if (type === 'component' ||
    type === 'template' && pathNested && name[0] === 'component') {
    validateComponentName(fileName);
  }

  // Setup `pathName`
  // `moduleName` would be used inside replacement of template placeholder
  if (pathNested) {
    // build up the nested path
    for (var i = 0; i < name.length; i++) {
      // 'component' and 'components' resolve as a 'app/templates/components/'
      if (type === 'template' && name[0] === 'component') {
        name[i] = 'components';
      }
      pathName += '/' + name[i];
      moduleName += name[i] + '-';
    }
    // append fileName to the moduleName string
    moduleName += fileName;
  } else {
    pathName += name;
    moduleName = name;
  }

  // dash separated moduleName used in template replacement
  moduleDashedName += moduleName;
  // Classify the moduleName in format of `MattMaController`
  moduleName = stringUtils.classify(moduleName + '-' + type);

  // ignore the 'store' case, since it is already created
  var typeFolder = path.resolve('client/app', type + 's');

  // if client/app/[type](s) is not existed and it is not a test generator, simply create one
  if (!fs.existsSync(typeFolder) && type.indexOf('test') === -1) {
    fs.mkdirSync(typeFolder);
    gutil.log(
      gutil.colors.gray('[-log:] Created a new folder at '),
      gutil.colors.cyan('~/client/app/' + type + 's')
    );
  }

  // Handle `flag` of `--test` case, and other special case
  // like generate template when the type is route or component, etc
  if (isGeneratingTest) {
    var injectTestFile = [{
      type:          type + '-test',
      injection:     true,
      generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '-test.js',
      testGenerator: true
    }];
    // flag `-T` or `--test`, will generate the unit test file
    srcPath = srcPath.concat(injectTestFile);

    srcPath = injectSrcPath(srcPath, type);
  } else {
    if (type === 'route' || type === 'component') {
      srcPath = injectSrcPath(srcPath, type);
    } else {
      // convert the array into string.
      srcPath = path.join(__dirname, '..', 'skeletons/generators', type) + '.js';
    }
  }

  // if type is test, or route-test or any sorts, it should append `-test` to the filename
  fileName = (type.indexOf('test') > -1) ? fileName + '-test' : fileName;

  // if it is a string, simple call generatorEngine once
  // else it is an object(array), repeat the generatorEngine call
  if (typeof srcPath === 'string') {
    generateSimpleFile(type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested);
  } else {
    generateNestedFile(type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested, isGeneratingTest);
  }
}

function generateSimpleFile (type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested) {
  var dirName = (type === 'store') ? type : (type.slice(-1) === 's') ? type : type + 's';
  var finalDirName;
  var finalPath;

  // Figure out the type is testing generator
  if (type.indexOf('test') > -1) {
    // Is it an Unit Test generator or Integration Test generator
    if (type.indexOf('-test') > -1) {
      var typeArray = type.split('-');
      finalDirName = 'tests/unit/' + typeArray[0] + 's';
    } else {
      finalDirName = dirName + '/integration';
    }
  } else {
    finalDirName = dirName;
  }

  finalPath = pathNested ? finalDirName + pathName : finalDirName;

  var destPath = (type.indexOf('test') > -1) ?
  path.resolve('client') + '/' + finalPath :
  path.resolve('client/app') + '/' + finalPath;

  generatorEngine(type, srcPath, null, moduleName, moduleDashedName, fileName, destPath);
}

function generateNestedFile (type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested, isGeneratingTest) {
  var dirName, finalPath, destPath;

  for (var j = 0, l = srcPath.length; j < l; j++) {
    var _type = srcPath[j].type;
    var testDirName;
    // when original type is 'component'
    // it will create a template file at 'templates/components' folder
    var injection = srcPath[j].injection;
    var finalFileName;

    dirName = (_type === 'store') ? _type : (_type.slice(-1) === 's') ? _type : _type + 's';
    dirName = (injection === 'components') ? dirName + '/' + injection : dirName;
    testDirName = (type === 'store') ? type : (type.slice(-1) === 's') ? type : type + 's';

    finalPath = pathNested ? dirName + pathName : dirName;

    if (isGeneratingTest && srcPath[j].testGenerator) {
      destPath = path.resolve('client/tests/unit') + '/' + testDirName;
      finalFileName = fileName + '-test';
    } else {
      destPath = path.resolve('client/app') + '/' + finalPath;
      finalFileName = fileName;
    }

    generatorEngine(
      _type, srcPath[j].generatorPath, injection, moduleName,
      moduleDashedName, finalFileName, destPath
    );
  }
}

// Check the fullname attribute is correct or not
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;

var generate = function (generator, options) {
  // if the folder 'client/app' is not existed
  // can assume that the project may not be created by Ember Rocks
  if (!fs.existsSync('client') && !fs.existsSync('client/app')) {
    gutil.log(
      gutil.colors.red(
        '[-Error:] This project may not be created by \'Ember-Rocks\'\n'
      ),
      gutil.colors.red(
        '[-Error:] `em new [dirName]` does not install the NPM packages dependencies correctly'
      )
    );
    process.exit(1);
  }

  // Error out when user did not provide any argument
  if (!generator || typeof generator !== 'string') {
    gutil.log(gutil.colors.red('[-Error:] Missing type:name argument.'), 'ex: em new route:post');
    gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
    process.exit(0);
  }

  if (!VALID_FULL_NAME_REGEXP.test(generator)) {
    errorHandler(generator);
  }

  var validTypes = [
    'adapter', 'component', 'controller', 'helper', 'initializer', 'mixin', 'model',
    'route', 'serializer', 'template', 'transform', 'util', 'view',
    'test', 'adapter-test', 'component-test', 'controller-test', 'helper-test',
    'initializer-test', 'mixin-test', 'model-test', 'route-test', 'serializer-test',
    'transform-test', 'util-test', 'view-test'
  ];
  var gen;
  var generatorAndTasks = generator.split(':', 2);
  var type = generatorAndTasks[0];
  var name = generatorAndTasks[1];

  // type could be either route or routes
  type = (type.slice(-1) === 's') ? type.substring(0, type.length - 1) : type;

  // Type must be in the `validTypes` array
  if (validTypes.indexOf(type) > -1) {
    // Name must be a valid string
    if (name.length > 0) {
      gen = {
        type: type,
        name: name
      };
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(name),
        gutil.colors.red(' must be a valid string.')
      );
      gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
      process.exit(0);
    }
  } else {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(type),
      gutil.colors.red(' is not a valid type.')
    );
    gutil.log(
      gutil.colors.bold('[-note:] valid types are'),
      gutil.colors.cyan(validTypes.join(', '))
    );
    process.exit(0);
  }

  runTasks(gen, options);
};

module.exports = generate;

function checkFileExisted (fullFilePath, injection, fileName, ext, destPath) {
  if (fs.existsSync(fullFilePath)) {
    if (!!injection) {
      gutil.log(
        gutil.colors.red('[-Warning:] '),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.red('has existed at '),
        gutil.colors.magenta(tildify(destPath))
      );
      // Does not continue to generate file, but won't stop the process
      return true;
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.red('has existed at '),
        gutil.colors.magenta(tildify(destPath))
      );
      gutil.log(
        gutil.colors.red('[-Error:]  Generate task has been canceled')
      );
      // File is existed in the system, kill the process
      process.exit(0);
    }
  }
}

function errorHandler (fullName) {
  gutil.log(
    gutil.colors.red('[-Error:] Invalid argument, expected: `type:name` got: '),
    gutil.colors.bold(fullName)
  );

  gutil.log(
    '[-Syntax:]',
    gutil.colors.cyan('type:name'), ' ex: em generate route:post'
  );

  gutil.log(
    gutil.colors.red('[-Error:]'),
    'See \'em generate --help\''
  );
  process.exit(0);
}
<commit_msg>move the client folder creation out of runTasks function<commit_after>'use strict';

var Promise = require('bluebird');
var path = require('path');
var fs = require('fs');
var tildify = require('tildify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var stringUtils = require('../utils/string');

function validateComponentName (filename) {
  if (filename.indexOf('-') === -1) {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(filename),
      gutil.colors.red(' must be a dashize string. ex: my-component')
    );
    gutil.log(
      gutil.colors.red('[-Error:]  Generate task has been canceled')
    );
    process.exit(0);
  }
}

function injectSrcPath (srcPath, type) {
  var injectTemplateGenerator;
  // when `type` is `route` or `component`, will generate its template file
  if (type === 'route' || type === 'component') {
    injectTemplateGenerator = [{
      type:          'template',
      injection:     ( type === 'component' ) ? 'components' : true,
      generatorPath: path.join(__dirname, '..', 'skeletons/generators/template.js')
    }];
    srcPath = srcPath.concat(injectTemplateGenerator);
  }

  // Default, it will generate whatever user inserts the `type`
  srcPath = srcPath.concat([{
    type:          type,
    generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '.js'
  }]);
  // return the modified `srcPath` array
  return srcPath;
}

function generatorEngine
(type, srcPath, injection, moduleName, moduleDashedName, fileName, destPath) {
  var ext = (type === 'template') ? '.hbs' : '.js';
  var fullFilePath = destPath + '/' + fileName + ext;

  // if the file has existed, it will abort the task
  // if return true, mean that it is an injection file, which already exist in the system
  // handle the case in the next condition
  var stopGenerateFile = checkFileExisted(fullFilePath, injection, fileName, ext, destPath);
  // check if template is existed or not, not going to kill the process
  // only stop the generator task on this operation
  if (stopGenerateFile) {
    return;
  }

  // Classify the plain module name without its type
  var classifyName = stringUtils.classify(moduleDashedName);

  return gulp.src(srcPath)
    .pipe(replace(/__NAMESPACE__/g, moduleName))
    // __DASHERIZE_NAMESPACE__  mainly used in `-test` generator
    .pipe(replace(/__DASHERIZE_NAMESPACE__/g, moduleDashedName))
    // __CLASSIFY_NAMESPACE__ mainly used in regular generator
    .pipe(replace(/__CLASSIFY_NAMESPACE__/g, classifyName))
    .pipe(rename({
      basename: fileName,
      extname:  ext
    }))
    .on('end', function () {
      gutil.log(
        gutil.colors.green('[-done:] Generate'),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.green('at'),
        gutil.colors.magenta(tildify(destPath))
      );
    })
    .pipe(gulp.dest(destPath));
}

// @describe	generate an model,view,store,controller from base template
function runTasks (generator, options) {
  // check for the options mode, to generate an unit test file or not
  var isGeneratingTest = options.test || false;

  var type = generator.type;
  var name = generator.name;
  var pathName = '';
  var moduleName = '';
  var moduleDashedName = '';
  var pathNested; // Boolean
  var fileName; // setup the fileName which used for rename module
  var srcPath = []; // the filePath/srcPath would be used to generate files

  // based on the passing name arguments, to determine it is an nested folder structure
  // or it is a simple file generation. assign a var `fileName` for current file name
  if (name.indexOf('/') > -1) {
    name = name.split('/');
    pathNested = true;
    fileName = name.pop();
  } else {
    pathNested = false;
    fileName = name;
  }

  // handle the error case when arg is `component:foo`
  // component name has to be dash separated string
  // case 1: `em g component:name`         <= simple case
  // case 2: `em g component:nested/name`  <= nested case

  // when type is template, name[0] is component, name of nest path has to be dashized string
  // case 3: `em g template:component/name`  <= nested case in template. defined after "||"
  if (type === 'component' ||
    type === 'template' && pathNested && name[0] === 'component') {
    validateComponentName(fileName);
  }

  // Setup `pathName`
  // `moduleName` would be used inside replacement of template placeholder
  if (pathNested) {
    // build up the nested path
    for (var i = 0; i < name.length; i++) {
      // 'component' and 'components' resolve as a 'app/templates/components/'
      if (type === 'template' && name[0] === 'component') {
        name[i] = 'components';
      }
      pathName += '/' + name[i];
      moduleName += name[i] + '-';
    }
    // append fileName to the moduleName string
    moduleName += fileName;
  } else {
    pathName += name;
    moduleName = name;
  }

  // dash separated moduleName used in template replacement
  moduleDashedName += moduleName;
  // Classify the moduleName in format of `MattMaController`
  moduleName = stringUtils.classify(moduleName + '-' + type);

  // ignore the 'store' case, since it is already created
  // create a folder if it is not existed in the "client/app/"
  createFolderWhenMissing(type);

  // Handle `flag` of `--test` case, and other special case
  // like generate template when the type is route or component, etc
  if (isGeneratingTest) {
    var injectTestFile = [{
      type:          type + '-test',
      injection:     true,
      generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '-test.js',
      testGenerator: true
    }];
    // flag `-T` or `--test`, will generate the unit test file
    srcPath = srcPath.concat(injectTestFile);

    srcPath = injectSrcPath(srcPath, type);
  } else {
    if (type === 'route' || type === 'component') {
      srcPath = injectSrcPath(srcPath, type);
    } else {
      // convert the array into string.
      srcPath = path.join(__dirname, '..', 'skeletons/generators', type) + '.js';
    }
  }

  // if type is test, or route-test or any sorts, it should append `-test` to the filename
  fileName = (type.indexOf('test') > -1) ? fileName + '-test' : fileName;

  // if it is a string, simple call generatorEngine once
  // else it is an object(array), repeat the generatorEngine call
  if (typeof srcPath === 'string') {
    generateSimpleFile(type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested);
  } else {
    generateNestedFile(type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested, isGeneratingTest);
  }
}

function generateSimpleFile (type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested) {
  var dirName = (type === 'store') ? type : (type.slice(-1) === 's') ? type : type + 's';
  var finalDirName;
  var finalPath;

  // Figure out the type is testing generator
  if (type.indexOf('test') > -1) {
    // Is it an Unit Test generator or Integration Test generator
    if (type.indexOf('-test') > -1) {
      var typeArray = type.split('-');
      finalDirName = 'tests/unit/' + typeArray[0] + 's';
    } else {
      finalDirName = dirName + '/integration';
    }
  } else {
    finalDirName = dirName;
  }

  finalPath = pathNested ? finalDirName + pathName : finalDirName;

  var destPath = (type.indexOf('test') > -1) ?
  path.resolve('client') + '/' + finalPath :
  path.resolve('client/app') + '/' + finalPath;

  generatorEngine(type, srcPath, null, moduleName, moduleDashedName, fileName, destPath);
}

function generateNestedFile (type, srcPath, moduleName, moduleDashedName, fileName, pathName, pathNested, isGeneratingTest) {
  var dirName, finalPath, destPath;

  for (var j = 0, l = srcPath.length; j < l; j++) {
    var _type = srcPath[j].type;
    var testDirName;
    // when original type is 'component'
    // it will create a template file at 'templates/components' folder
    var injection = srcPath[j].injection;
    var finalFileName;

    dirName = (_type === 'store') ? _type : (_type.slice(-1) === 's') ? _type : _type + 's';
    dirName = (injection === 'components') ? dirName + '/' + injection : dirName;
    testDirName = (type === 'store') ? type : (type.slice(-1) === 's') ? type : type + 's';

    finalPath = pathNested ? dirName + pathName : dirName;

    if (isGeneratingTest && srcPath[j].testGenerator) {
      destPath = path.resolve('client/tests/unit') + '/' + testDirName;
      finalFileName = fileName + '-test';
    } else {
      destPath = path.resolve('client/app') + '/' + finalPath;
      finalFileName = fileName;
    }

    generatorEngine(
      _type, srcPath[j].generatorPath, injection, moduleName,
      moduleDashedName, finalFileName, destPath
    );
  }
}

// Check the fullname attribute is correct or not
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;

var generate = function (generator, options) {
  // if the folder 'client/app' is not existed
  // can assume that the project may not be created by Ember Rocks
  if (!fs.existsSync('client') && !fs.existsSync('client/app')) {
    gutil.log(
      gutil.colors.red(
        '[-Error:] This project may not be created by \'Ember-Rocks\'\n'
      ),
      gutil.colors.red(
        '[-Error:] `em new [dirName]` does not install the NPM packages dependencies correctly'
      )
    );
    process.exit(1);
  }

  // Error out when user did not provide any argument
  if (!generator || typeof generator !== 'string') {
    gutil.log(gutil.colors.red('[-Error:] Missing type:name argument.'), 'ex: em new route:post');
    gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
    process.exit(0);
  }

  if (!VALID_FULL_NAME_REGEXP.test(generator)) {
    errorHandler(generator);
  }

  var validTypes = [
    'adapter', 'component', 'controller', 'helper', 'initializer', 'mixin', 'model',
    'route', 'serializer', 'template', 'transform', 'util', 'view',
    'test', 'adapter-test', 'component-test', 'controller-test', 'helper-test',
    'initializer-test', 'mixin-test', 'model-test', 'route-test', 'serializer-test',
    'transform-test', 'util-test', 'view-test'
  ];
  var gen;
  var generatorAndTasks = generator.split(':', 2);
  var type = generatorAndTasks[0];
  var name = generatorAndTasks[1];

  // type could be either route or routes
  type = (type.slice(-1) === 's') ? type.substring(0, type.length - 1) : type;

  // Type must be in the `validTypes` array
  if (validTypes.indexOf(type) > -1) {
    // Name must be a valid string
    if (name.length > 0) {
      gen = {
        type: type,
        name: name
      };
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(name),
        gutil.colors.red(' must be a valid string.')
      );
      gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
      process.exit(0);
    }
  } else {
    gutil.log(
      gutil.colors.red('[-Error:] '),
      gutil.colors.cyan(type),
      gutil.colors.red(' is not a valid type.')
    );
    gutil.log(
      gutil.colors.bold('[-note:] valid types are'),
      gutil.colors.cyan(validTypes.join(', '))
    );
    process.exit(0);
  }

  runTasks(gen, options);
};

module.exports = generate;

function createFolderWhenMissing(type) {
  var typeFolder = path.resolve('client/app', type + 's');

  // if client/app/[type](s) is not existed and it is not a test generator, simply create one
  if (!fs.existsSync(typeFolder) && type.indexOf('test') === -1) {
    fs.mkdirSync(typeFolder);
    gutil.log(
      gutil.colors.gray('[-log:] Created a new folder at '),
      gutil.colors.cyan('~/client/app/' + type + 's')
    );
  }
}

function checkFileExisted (fullFilePath, injection, fileName, ext, destPath) {
  if (fs.existsSync(fullFilePath)) {
    if (!!injection) {
      gutil.log(
        gutil.colors.red('[-Warning:] '),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.red('has existed at '),
        gutil.colors.magenta(tildify(destPath))
      );
      // Does not continue to generate file, but won't stop the process
      return true;
    } else {
      gutil.log(
        gutil.colors.red('[-Error:] '),
        gutil.colors.cyan(fileName + ext),
        gutil.colors.red('has existed at '),
        gutil.colors.magenta(tildify(destPath))
      );
      gutil.log(
        gutil.colors.red('[-Error:]  Generate task has been canceled')
      );
      // File is existed in the system, kill the process
      process.exit(0);
    }
  }
}

function errorHandler (fullName) {
  gutil.log(
    gutil.colors.red('[-Error:] Invalid argument, expected: `type:name` got: '),
    gutil.colors.bold(fullName)
  );

  gutil.log(
    '[-Syntax:]',
    gutil.colors.cyan('type:name'), ' ex: em generate route:post'
  );

  gutil.log(
    gutil.colors.red('[-Error:]'),
    'See \'em generate --help\''
  );
  process.exit(0);
}
<|endoftext|>


<commit_before>// stream.js
//
// A (potentially very long) stream of object IDs
//
// Copyright 2011,2012 StatusNet Inc.
//
// 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.

var databank = require('databank'),
    _ = require('underscore'),
    DatabankObject = databank.DatabankObject,
    Step = require('step'),
    IDMaker = require('../idmaker').IDMaker,
    NoSuchThingError = databank.NoSuchThingError,
    DatabankError = databank.DatabankError;

var Stream = DatabankObject.subClass('stream');

exports.Stream = Stream;

Stream.SOFT_LIMIT = 1000;
Stream.HARD_LIMIT = 2000;

Stream.beforeCreate = function(props, callback) {

    var bank = Stream.bank(),
        stream = null;

    if (!props.name) {
        callback(new Error('Gotta have a name'), null);
    }

    Step(
        function() {
            bank.create('streamcount', props.name, 0, this.parallel());
            bank.create('streamsegments', props.name, [], this.parallel());
        },
        function(err, count, segments) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, props);
            }
        }
    );
};

Stream.prototype.afterCreate = function(callback) {
    this.newSegment(callback);
};

// put something in the stream

var randBetween = function(min, max) {
    var diff = max - min + 1;
    return Math.floor((Math.random() * diff) + min);
};

Stream.prototype.deliver = function(id, callback) {

    var stream = this,
        bank = Stream.bank(),
        current = null;

    Step(
        function() {
            bank.item('streamsegments', stream.name, 0, this);
        },
        function(err, id) {
            if (err) throw err;
            current = id;
            bank.read('streamsegmentcount', current, this);
        },
        function(err, cnt) {

            if (err) throw err;

            // Once we hit the soft limit, we start thinking about
            // a new segment. To avoid conflicts, a bit, we do it at a
            // random point between soft and hard limit. If we actually
            // hit the hard limit, force it.

            if (cnt > Stream.SOFT_LIMIT &&
                (cnt > Stream.HARD_LIMIT ||
                 randBetween(0, Stream.HARD_LIMIT - Stream.SOFT_LIMIT) === 0)) {
                stream.newSegment(this);
            } else {
                this(null, current);
            }
        },
        function(err, segmentId) {
            if (err) throw err;
            bank.prepend('streamsegment', segmentId, id, this.parallel());
            bank.incr('streamsegmentcount', segmentId, this.parallel());
            bank.incr('streamcount', stream.name, this.parallel());
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

Stream.prototype.remove = function(id, callback) {

    var stream = this,
        bank = Stream.bank(),
        current = null,
        segments,
        segmentId;

    Step(
        function() {
            bank.read('streamsegments', stream.name, this);
        },
        function(err, segments) {
            var i, cb = this, findFrom = function(j) {
                if (j >= segments.length) {
                    cb(new Error("Not found"), null);
                    return;
                }
                bank.indexOf('streamsegment', segments[j], id, function(err, idx) {
                    if (err) {
                        cb(err, null);
                    } else if (idx === -1) {
                        findFrom(j+1);
                    } else {
                        cb(err, segments[j]);
                    }
                });
            };
            if (err) throw err;
            findFrom(0);
        },
        function(err, found) {
            if (err) throw err;
            segmentId = found;
            bank.remove('streamsegment', segmentId, id, this);
        },
        function(err) {
            if (err) throw err;
            bank.decr('streamsegmentcount', segmentId, this.parallel());
            bank.decr('streamcount', stream.name, this.parallel());
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

Stream.prototype.newSegment = function(callback) {

    var bank = Stream.bank(),
        stream = this,
        id = stream.name + ':stream:' + IDMaker.makeID();

    Step(
        function() {
            bank.create('streamsegmentcount', id, 0, this.parallel());
            bank.create('streamsegment', id, [], this.parallel());
        },
        function(err, cnt, segment) {
            if (err) throw err;
            bank.prepend('streamsegments', stream.name, id, this);
        },
        function(err, segments) {
            if (err) {
                callback(err, null);
            } else {
                callback(err, id);
            }
        }
    );
};

Stream.prototype.getIDs = function(start, end, callback) {

    var bank = Stream.bank(),
        stream = this,
        getMore = function getMore(segments, start, end, callback) {

            var tip;

            if (segments.length === 0) {
                callback(null, []);
                return;
            }

            tip = segments.shift();

            Step(
                function() {
                    bank.read('streamsegmentcount', tip, this);
                },
                function(err, tipcount) {
                    var group = this.group();
                    if (err) throw err;
                    if (start < tipcount) {
                        bank.slice('streamsegment',
                                   tip,
                                   start,
                                   Math.min(end, tipcount),
                                   group());
                    }
                    if (end > tipcount) {
                        if (segments.length > 0) {
                            getMore(segments,
                                    Math.max(start - tipcount, 0),
                                    end - tipcount,
                                    group());
                        } else { // Asking for more than we have
                            // Need to trigger the rest
                            group()(null, []);
                        }
                    }
                },
                function(err, parts) {
                    if (err) {
                        callback(err, null);
                    } else if (_(parts).isNull() || _(parts).isUndefined() || _(parts).isEmpty()) {
                        callback(new Error("Bad results for segment " + tip + ", start = " + start + ", end = " + end), null);
                    } else {
                        callback(null, (parts.length === 1) ? parts[0] : parts[0].concat(parts[1]));
                    }
                }
            );
        };

    if (start < 0 || end < 0 || start > end) {
        callback(new Error("Bad parameters"), null);
        return;
    }

    Step(
        function() {
            // XXX: maybe just take slice from [0, end/HARD_LIMIT)
            bank.read('streamsegments', stream.name, this);
        },
        function(err, segments) {
            if (err) throw err;
            getMore(segments, start, end, this);
        },
        function(err, ids) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, ids);
            }
        }
    );
};

// XXX: Not atomic; can get out of whack if an insertion
// happens between indexOf() and getIDs()

Stream.prototype.getIDsGreaterThan = function(id, count, callback) {
    var stream = this;

    if (count < 0) {
        callback(new Error("count must be >= 0)"), null);
        return;
    }

    Step(
        function() {
            stream.indexOf(id, this);
        },
        function(err, idx) {
            if (err) throw err;
            stream.getIDs(idx + 1, idx + count + 1, this);
        },
        function(err, ids) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, ids);
            }
        }
    );
};

// XXX: Not atomic; can get out of whack if an insertion
// happens between indexOf() and getIDs()

Stream.prototype.getIDsLessThan = function(id, count, callback) {
    var stream = this;
    Step(
        function() {
            stream.indexOf(id, this);
        },
        function(err, idx) {
            if (err) throw err;
            stream.getIDs(Math.max(0, idx - count), idx, this);
        },
        function(err, ids) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, ids);
            }
        }
    );
};

Stream.prototype.indexOf = function(id, callback) {
    var bank = Stream.bank(),
        stream = this,
        indexOfSeg = function indexOfSeg(id, segments, offset, callback) {

            var tip, cnt;

            if (segments.length === 0) {
                callback(null, -1);
                return;
            }

            tip = segments.shift();

            Step(
                function() {
                    bank.read('streamsegmentcount', tip, this);
                },
                function(err, result) {
                    if (err) throw err;
                    cnt = result;
                    bank.indexOf('streamsegment', tip, id, this);
                },
                function(err, idx) {
                    if (err) {
                        callback(err, null);
                    } else if (idx == -1) {
                        indexOfSeg(id, segments, offset + cnt, callback);
                    } else {
                        callback(null, idx + offset);
                    }
                }
            );
        };

    Step(
        function() {
            // XXX: maybe just take slice from [0, end/HARD_LIMIT)
            bank.read('streamsegments', stream.name, this);
        },
        function(err, segments) {
            if (err) throw err;
            indexOfSeg(id, segments, 0, this);
        },
        function(err, idx) {
            if (err) {
                callback(err, null);
            } else if (idx === -1) {
                callback(new DatabankError("id '" + id + "' not found in stream '" + stream.name + "'"), null);
            } else {
                callback(null, idx);
            }
        }
    );
};

Stream.prototype.count = function(callback) {
    Stream.count(this.name, callback);
};

Stream.count = function(name, callback) {
    var bank = Stream.bank();
    bank.read('streamcount', name, callback);
};

Stream.schema = {
    'stream': {'pkey': 'name'},
    'streamcount': {'pkey': 'name'},
    'streamsegments': {'pkey': 'name'},

    'streamsegment': {'pkey': 'id'},
    'streamsegmentcount': {'pkey': 'id'}
};
<commit_msg>Special error if ID not found in stream<commit_after>// stream.js
//
// A (potentially very long) stream of object IDs
//
// Copyright 2011,2012 StatusNet Inc.
//
// 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.

var databank = require('databank'),
    _ = require('underscore'),
    DatabankObject = databank.DatabankObject,
    Step = require('step'),
    IDMaker = require('../idmaker').IDMaker,
    NoSuchThingError = databank.NoSuchThingError,
    DatabankError = databank.DatabankError;

var Stream = DatabankObject.subClass('stream');

Stream.SOFT_LIMIT = 1000;
Stream.HARD_LIMIT = 2000;

var NotInStreamError = function(id, streamName) {
    Error.captureStackTrace(this, NotInStreamError);
    this.name       = 'NotInStreamError';
    this.id         = id;
    this.streamName = streamName;
    this.message    = "id '" + id + "' not found in stream '" + streamName + "'";
};

NotInStreamError.prototype = new DatabankError();
NotInStreamError.prototype.constructor = NotInStreamError;

Stream.beforeCreate = function(props, callback) {

    var bank = Stream.bank(),
        stream = null;

    if (!props.name) {
        callback(new Error('Gotta have a name'), null);
    }

    Step(
        function() {
            bank.create('streamcount', props.name, 0, this.parallel());
            bank.create('streamsegments', props.name, [], this.parallel());
        },
        function(err, count, segments) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, props);
            }
        }
    );
};

Stream.prototype.afterCreate = function(callback) {
    this.newSegment(callback);
};

// put something in the stream

var randBetween = function(min, max) {
    var diff = max - min + 1;
    return Math.floor((Math.random() * diff) + min);
};

Stream.prototype.deliver = function(id, callback) {

    var stream = this,
        bank = Stream.bank(),
        current = null;

    Step(
        function() {
            bank.item('streamsegments', stream.name, 0, this);
        },
        function(err, id) {
            if (err) throw err;
            current = id;
            bank.read('streamsegmentcount', current, this);
        },
        function(err, cnt) {

            if (err) throw err;

            // Once we hit the soft limit, we start thinking about
            // a new segment. To avoid conflicts, a bit, we do it at a
            // random point between soft and hard limit. If we actually
            // hit the hard limit, force it.

            if (cnt > Stream.SOFT_LIMIT &&
                (cnt > Stream.HARD_LIMIT ||
                 randBetween(0, Stream.HARD_LIMIT - Stream.SOFT_LIMIT) === 0)) {
                stream.newSegment(this);
            } else {
                this(null, current);
            }
        },
        function(err, segmentId) {
            if (err) throw err;
            bank.prepend('streamsegment', segmentId, id, this.parallel());
            bank.incr('streamsegmentcount', segmentId, this.parallel());
            bank.incr('streamcount', stream.name, this.parallel());
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

Stream.prototype.remove = function(id, callback) {

    var stream = this,
        bank = Stream.bank(),
        current = null,
        segments,
        segmentId;

    Step(
        function() {
            bank.read('streamsegments', stream.name, this);
        },
        function(err, segments) {
            var i, cb = this, findFrom = function(j) {
                if (j >= segments.length) {
                    cb(new NotInStreamError(id, stream.name), null);
                    return;
                }
                bank.indexOf('streamsegment', segments[j], id, function(err, idx) {
                    if (err) {
                        cb(err, null);
                    } else if (idx === -1) {
                        findFrom(j+1);
                    } else {
                        cb(err, segments[j]);
                    }
                });
            };
            if (err) throw err;
            findFrom(0);
        },
        function(err, found) {
            if (err) throw err;
            segmentId = found;
            bank.remove('streamsegment', segmentId, id, this);
        },
        function(err) {
            if (err) throw err;
            bank.decr('streamsegmentcount', segmentId, this.parallel());
            bank.decr('streamcount', stream.name, this.parallel());
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

Stream.prototype.newSegment = function(callback) {

    var bank = Stream.bank(),
        stream = this,
        id = stream.name + ':stream:' + IDMaker.makeID();

    Step(
        function() {
            bank.create('streamsegmentcount', id, 0, this.parallel());
            bank.create('streamsegment', id, [], this.parallel());
        },
        function(err, cnt, segment) {
            if (err) throw err;
            bank.prepend('streamsegments', stream.name, id, this);
        },
        function(err, segments) {
            if (err) {
                callback(err, null);
            } else {
                callback(err, id);
            }
        }
    );
};

Stream.prototype.getIDs = function(start, end, callback) {

    var bank = Stream.bank(),
        stream = this,
        getMore = function getMore(segments, start, end, callback) {

            var tip;

            if (segments.length === 0) {
                callback(null, []);
                return;
            }

            tip = segments.shift();

            Step(
                function() {
                    bank.read('streamsegmentcount', tip, this);
                },
                function(err, tipcount) {
                    var group = this.group();
                    if (err) throw err;
                    if (start < tipcount) {
                        bank.slice('streamsegment',
                                   tip,
                                   start,
                                   Math.min(end, tipcount),
                                   group());
                    }
                    if (end > tipcount) {
                        if (segments.length > 0) {
                            getMore(segments,
                                    Math.max(start - tipcount, 0),
                                    end - tipcount,
                                    group());
                        } else { // Asking for more than we have
                            // Need to trigger the rest
                            group()(null, []);
                        }
                    }
                },
                function(err, parts) {
                    if (err) {
                        callback(err, null);
                    } else if (_(parts).isNull() || _(parts).isUndefined() || _(parts).isEmpty()) {
                        callback(new Error("Bad results for segment " + tip + ", start = " + start + ", end = " + end), null);
                    } else {
                        callback(null, (parts.length === 1) ? parts[0] : parts[0].concat(parts[1]));
                    }
                }
            );
        };

    if (start < 0 || end < 0 || start > end) {
        callback(new Error("Bad parameters"), null);
        return;
    }

    Step(
        function() {
            // XXX: maybe just take slice from [0, end/HARD_LIMIT)
            bank.read('streamsegments', stream.name, this);
        },
        function(err, segments) {
            if (err) throw err;
            getMore(segments, start, end, this);
        },
        function(err, ids) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, ids);
            }
        }
    );
};

// XXX: Not atomic; can get out of whack if an insertion
// happens between indexOf() and getIDs()

Stream.prototype.getIDsGreaterThan = function(id, count, callback) {
    var stream = this;

    if (count < 0) {
        callback(new Error("count must be >= 0)"), null);
        return;
    }

    Step(
        function() {
            stream.indexOf(id, this);
        },
        function(err, idx) {
            if (err) throw err;
            stream.getIDs(idx + 1, idx + count + 1, this);
        },
        function(err, ids) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, ids);
            }
        }
    );
};

// XXX: Not atomic; can get out of whack if an insertion
// happens between indexOf() and getIDs()

Stream.prototype.getIDsLessThan = function(id, count, callback) {
    var stream = this;
    Step(
        function() {
            stream.indexOf(id, this);
        },
        function(err, idx) {
            if (err) throw err;
            stream.getIDs(Math.max(0, idx - count), idx, this);
        },
        function(err, ids) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, ids);
            }
        }
    );
};

Stream.prototype.indexOf = function(id, callback) {
    var bank = Stream.bank(),
        stream = this,
        indexOfSeg = function indexOfSeg(id, segments, offset, callback) {

            var tip, cnt;

            if (segments.length === 0) {
                callback(null, -1);
                return;
            }

            tip = segments.shift();

            Step(
                function() {
                    bank.read('streamsegmentcount', tip, this);
                },
                function(err, result) {
                    if (err) throw err;
                    cnt = result;
                    bank.indexOf('streamsegment', tip, id, this);
                },
                function(err, idx) {
                    if (err) {
                        callback(err, null);
                    } else if (idx == -1) {
                        indexOfSeg(id, segments, offset + cnt, callback);
                    } else {
                        callback(null, idx + offset);
                    }
                }
            );
        };

    Step(
        function() {
            // XXX: maybe just take slice from [0, end/HARD_LIMIT)
            bank.read('streamsegments', stream.name, this);
        },
        function(err, segments) {
            if (err) throw err;
            indexOfSeg(id, segments, 0, this);
        },
        function(err, idx) {
            if (err) {
                callback(err, null);
            } else if (idx === -1) {
                callback(new NotInStreamError(id, stream.name), null);
            } else {
                callback(null, idx);
            }
        }
    );
};

Stream.prototype.count = function(callback) {
    Stream.count(this.name, callback);
};

Stream.count = function(name, callback) {
    var bank = Stream.bank();
    bank.read('streamcount', name, callback);
};

Stream.schema = {
    'stream': {'pkey': 'name'},
    'streamcount': {'pkey': 'name'},
    'streamsegments': {'pkey': 'name'},

    'streamsegment': {'pkey': 'id'},
    'streamsegmentcount': {'pkey': 'id'}
};

exports.Stream = Stream;
exports.NotInStreamError = NotInStreamError;
<|endoftext|>