<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 App = require('app');

/**
 * @class ServiceConfigProperty
 */
App.ServiceConfigProperty = Em.Object.extend({

  name: '',
  displayName: '',

  /**
   * value that is shown on IU
   * and is changing by user
   * @type {String|null}
   */
  value: '',

  /**
   * value that is saved on cluster configs
   * and stored in /api/v1/clusters/{name}/configurations
   * @type {String|null}
   */
  savedValue: null,

  /**
   * value that is returned from server as recommended
   * or stored on stack
   * @type {String|null}
   */
  recommendedValue: null,

  /**
   * initial value of config. if value is saved it will be initial
   * otherwise first recommendedValue will be initial
   * @type {String|null}
   */
  initialValue: null,

  /**
   * value that is shown on IU
   * and is changing by user
   * @type {boolean}
   */
  isFinal: false,

  /**
   * value that is saved on cluster configs api
   * @type {boolean}
   */
  savedIsFinal: null,

  /**
   * value that is returned from server as recommended
   * or stored on stack
   * @type {boolean}
   */
  recommendedIsFinal: null,

  /**
   * @type {boolean}
   */
  supportsFinal: false,

  /**
   * Hint message to display in tooltip. Tooltip will be wrapped on question mark icon.
   * If value is false no tooltip and question mark icon.
   *
   * @type {boolean|string}
   */
  hintMessage: false,

  /**
   * Display label on the right side from input. In general used for checkbox only.
   *
   * @type {boolean}
   */
  rightSideLabel: false,

  /**
   * type of property
   * @type {String[]}
   * @default empty array
   */
  propertyType: [],

  /**
   * Text to be shown as placeholder
   * By default savedValue is shown as placeholder
   * @type {String}
   */
  placeholderText: '',

  /**
   * type of widget View
   * @type {string}
   * @default null
   */
  widgetType: null,

  /**
   * Placeholder used for configs with input type text
   */
  placeholder: function() {
    if (this.isEditable) {
      return this.get('placeholderText') || this.get('savedValue');
    }
    return null;
  }.property('isEditable', 'placeholderText', 'savedValue'),

  retypedPassword: '',
  description: '',
  displayType: 'string', // string, digits, number, directories, custom
  unit: '',
  category: 'General',
  isRequired: true, // by default a config property is required
  isReconfigurable: true, // by default a config property is reconfigurable
  isEditable: true, // by default a config property is editable
  disabledAsComponentAction: false, // is true for component action configs
  isNotEditable: Em.computed.not('isEditable'),
  hideFinalIcon: Em.computed.and('!isFinal', 'isNotEditable'),
  isVisible: true,
  isMock: false, // mock config created created only to displaying
  isRequiredByAgent: true, // Setting it to true implies property will be stored in configuration
  isSecureConfig: false,
  errorMessage: '',
  warnMessage: '',
  validationErrors: [], // stores messages from validation response marked as ERRROR
  validationWarnings: [], // stores message from validation response marked as WARN
  serviceConfig: null, // points to the parent App.ServiceConfig object
  filename: '',
  isOriginalSCP : true, // if true, then this is original SCP instance and its value is not overridden value.
  parentSCP: null, // This is the main SCP which is overridden by this. Set only when isOriginalSCP is false.
  overrides : null,
  overrideValues: [],
  group: null, // Contain group related to this property. Set only when isOriginalSCP is false.
  isUserProperty: null, // This property was added by user. Hence they get removal actions etc.
  isOverridable: true,
  compareConfigs: [],
  isComparison: false,
  hasCompareDiffs: false,
  showLabel: true,
  isConfigIdentity: false,
  copy: '',

  /**
   * Determines if config exists in the non-default config group but is loaded for default config group
   *
   * @type {boolean}
   */
  isCustomGroupConfig: false,

  /**
   * Determines if config is Undefined label, used for overrides, that do not have original property in default group
   * @type {boolean}
   */
  isUndefinedLabel: function () {
    return this.get('displayType') === 'label' && this.get('value') === 'Undefined';
  }.property('displayType', 'value'),

  error: Em.computed.bool('errorMessage.length'),
  warn: Em.computed.bool('warnMessage.length'),
  hasValidationErrors: Em.computed.bool('validationErrors.length'),
  hasValidationWarnings: Em.computed.bool('validationWarnings.length'),
  isValid: Em.computed.equal('errorMessage', ''),

  previousValue: null, // cached value before changing config value

  /**
   * List of isFinal-values for overrides
   * Set in the controller
   * Should be empty array by default!
   * @type {boolean[]}
   */
  overrideIsFinalValues: [],

  /**
   * true if property has warning or error
   * @type {boolean}
   */
  hasIssues: Em.computed.or('error', 'warn', 'overridesWithIssues.length'),

  overridesWithIssues: Em.computed.filterBy('overrides', 'hasIssues', true),

  index: null, //sequence number in category
  editDone: false, //Text field: on focusOut: true, on focusIn: false
  isNotSaved: false, // user property was added but not saved
  hasInitialValue: false, //if true then property value is defined and saved to server
  isHiddenByFilter: false, //if true then hide this property (filtered out)
  rowStyleClass: null, // CSS-Class to be applied on the row showing this config
  showAsTextBox: false,

  /**
   * config is invisible since wrapper section is hidden
   * @type {boolean}
   */
  hiddenBySection: false,

  /**
   * Determines config visibility on subsection level when wrapped.
   * @type {boolean}
   */
  hiddenBySubSection: false,

  /**
   * Determines visibility state including section/subsection state.
   * When true means that property is shown and may affect validation process.
   * When false means that property won't affect validation.
   */
  isActive: Em.computed.and('isVisible', '!hiddenBySubSection', '!hiddenBySection'),

  /**
   * @type {boolean}
   */
  recommendedValueExists: function () {
    return !Em.isNone(this.get('recommendedValue')) && (this.get('recommendedValue') != "")
      && this.get('isRequiredByAgent') && !this.get('cantBeUndone');
  }.property('recommendedValue'),

  /**
   * Usage example see on App.ServiceConfigRadioButtons.handleDBConnectionProperty()
   *
   * @property {Ember.View} additionalView - custom view related to property
   **/
  additionalView: null,

  /**
   * If config is saved we should compare config value with savedValue to
   * find out if it was changed, but if config in not saved there is no savedValue, so
   * we should use initialValue instead.
   */
  isNotInitialValue: function() {
    if (Em.isNone(this.get('savedValue')) && !Em.isNone(this.get('initialValue'))) {
      var value = this.get('value'), initialValue = this.get('initialValue');
      if (this.get('stackConfigProperty.valueAttributes.type') == 'float') {
        initialValue = !Em.isNone(initialValue) ? '' + parseFloat(initialValue) : null;
        value = '' + parseFloat(value);
      }
      return initialValue !== value;
    }
    return false;
  }.property('initialValue', 'savedValue', 'value', 'stackConfigProperty.valueAttributes.type'),

  /**
   * Is property has active override with error
   */
  isValidOverride: function () {
    return this.get('overrides.length') ? !this.get('overrides').find(function(o) {
     return Em.get(o, 'isEditable') && Em.get(o, 'errorMessage');
    }) : true;
  }.property("overrides.@each.errorMessage"),
  /**
   * No override capabilities for fields which are not edtiable
   * and fields which represent master hosts.
   */
  isPropertyOverridable: function () {
    var overrideable = this.get('isOverridable');
    var editable = this.get('isEditable');
    var overrides = this.get('overrides');
    var dt = this.get('displayType');
    return overrideable && (editable || !overrides || !overrides.length) && (!["componentHost", "password"].contains(dt));
  }.property('isEditable', 'displayType', 'isOverridable', 'overrides.length'),

  isOverridden: Em.computed.or('overrides.length', '!isOriginalSCP'),

  isOverrideChanged: function () {
    if (Em.isNone(this.get('overrides')) && this.get('overrideValues.length') === 0) return false;
    return JSON.stringify(this.get('overrides').mapProperty('isFinal')) !== JSON.stringify(this.get('overrideIsFinalValues'))
      || JSON.stringify(this.get('overrides').mapProperty('value')) !== JSON.stringify(this.get('overrideValues'));
  }.property('overrides.@each.isNotDefaultValue', 'overrides.@each.overrideIsFinalValues', 'overrideValues.length'),

  isRemovable: function() {
    return this.get('isEditable') && this.get('isRequiredByAgent') && !(this.get('overrides.length') > 0)
       && (this.get('isUserProperty') || !this.get('isOriginalSCP'));
  }.property('isUserProperty', 'isOriginalSCP', 'overrides.length', 'isRequiredByAgent'),

  init: function () {
    this.setInitialValues();
    this.set('viewClass', App.config.getViewClass(this.get("displayType"), this.get('dependentConfigPattern'), this.get('unit')));
    this.set('validateErrors', App.config.getErrorValidator(this.get("displayType")));
    this.set('validateWarnings', App.config.getWarningValidator(this.get("displayType")));
    this.validate();
  },

  setInitialValues: function () {
    if (Em.isNone(this.get('value'))) {
      if (!Em.isNone(this.get('savedValue'))) {
        this.set('value', this.get('savedValue'));
      } else if (!Em.isNone(this.get('recommendedValue'))) {
        this.set('value', this.get('recommendedValue'));
      }
    }
    this.set('previousValue', this.get('value'));
    if (this.get('value') === null) {
      this.set('isVisible', false);
    }
    if (this.get("displayType") === "password") {
      this.set('retypedPassword', this.get('value'));
      this.set('recommendedValue', '');
    }
    this.set('initialValue', this.get('value'));
  },

  /**
   * updates configs list that belongs to config group
   */
  updateGroupConfigs: function() {
    if (this.get('group')) {
      var o = this.get('group.properties').find(function(c) {
        return Em.get(c, 'name') === this.get('name') && Em.get(c, 'filename') === this.get('filename');
      }, this);

      if (o) {
        Em.set(o, 'value', this.get('value'));
      }
    }
  }.observes('value'),

  /**
   * Indicates when value is not the default value.
   * Returns false when there is no default value.
   *
   * @type {boolean}
   */
  isNotDefaultValue: function () {
    var value = this.get('value'),
      savedValue = this.get('savedValue'),
      supportsFinal = this.get('supportsFinal'),
      isFinal = this.get('isFinal'),
      savedIsFinal = this.get('savedIsFinal');

    if (this.get('name') === 'kdc_type') {
      return App.router.get('mainAdminKerberosController.kdcTypesValues')[savedValue] !== value;
    }
    // ignore precision difference for configs with type of `float` which value may ends with 0
    // e.g. between 0.4 and 0.40
    if (this.get('stackConfigProperty') && this.get('stackConfigProperty.valueAttributes.type') == 'float') {
      savedValue = !Em.isNone(savedValue) ? '' + parseFloat(savedValue) : null;
      value = '' + parseFloat(value);
    }
    return (savedValue != null && value !== savedValue) || (supportsFinal && !Em.isNone(savedIsFinal) && isFinal !== savedIsFinal);
  }.property('value', 'savedValue', 'isEditable', 'isFinal', 'savedIsFinal'),

  /**
   * Don't show "Undo" for hosts on Installer Step7
   */
  cantBeUndone: Em.computed.existsIn('displayType', ["componentHost", "componentHosts", "radio button"]),

  validate: function () {
    if ((typeof this.get('value') != 'object') && ((this.get('value') + '').length === 0)) {
      var widgetType = this.get('widgetType');
      this.set('errorMessage', (this.get('isRequired') && (!['test-db-connection','label'].contains(widgetType))) ? Em.I18n.t('errorMessage.config.required') : '');
    } else {
      this.set('errorMessage', this.validateErrors(this.get('value'), this.get('name'), this.get('retypedPassword')));
    }
    if (!this.get('widgetType') || ('text-field' === this.get('widgetType'))) {
      //temp conditions, since other warnings are calculated directly in widget view
      this.set('warnMessage', this.validateWarnings(this.get('value'), this.get('name'), this.get('filename'),
        this.get('stackConfigProperty'), this.get('unit')));
    }
  }.observes('value', 'retypedPassword', 'isEditable'),

  viewClass: App.ServiceConfigTextField,

  validateErrors: function() { return '' },

  validateWarnings: function() { return '' },

  /**
   * Get override for selected group
   *
   * @param {String} groupName
   * @returns {App.ServiceConfigProperty|null}
   */
  getOverride: function(groupName) {
    Em.assert('Group name should be defined string', (typeof groupName === 'string') && groupName);
    if (this.get('overrides.length')) {
      return this.get('overrides').findProperty('group.name', groupName);
    }
    return null;
  }

});
<commit_msg>AMBARI-25381 Save button is enabled without any config changes for Kerberos service (asnaik) (#3083)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 App = require('app');

/**
 * @class ServiceConfigProperty
 */
App.ServiceConfigProperty = Em.Object.extend({

  name: '',
  displayName: '',

  /**
   * value that is shown on IU
   * and is changing by user
   * @type {String|null}
   */
  value: '',

  /**
   * value that is saved on cluster configs
   * and stored in /api/v1/clusters/{name}/configurations
   * @type {String|null}
   */
  savedValue: null,

  /**
   * value that is returned from server as recommended
   * or stored on stack
   * @type {String|null}
   */
  recommendedValue: null,

  /**
   * initial value of config. if value is saved it will be initial
   * otherwise first recommendedValue will be initial
   * @type {String|null}
   */
  initialValue: null,

  /**
   * value that is shown on IU
   * and is changing by user
   * @type {boolean}
   */
  isFinal: false,

  /**
   * value that is saved on cluster configs api
   * @type {boolean}
   */
  savedIsFinal: null,

  /**
   * value that is returned from server as recommended
   * or stored on stack
   * @type {boolean}
   */
  recommendedIsFinal: null,

  /**
   * @type {boolean}
   */
  supportsFinal: false,

  /**
   * Hint message to display in tooltip. Tooltip will be wrapped on question mark icon.
   * If value is false no tooltip and question mark icon.
   *
   * @type {boolean|string}
   */
  hintMessage: false,

  /**
   * Display label on the right side from input. In general used for checkbox only.
   *
   * @type {boolean}
   */
  rightSideLabel: false,

  /**
   * type of property
   * @type {String[]}
   * @default empty array
   */
  propertyType: [],

  /**
   * Text to be shown as placeholder
   * By default savedValue is shown as placeholder
   * @type {String}
   */
  placeholderText: '',

  /**
   * type of widget View
   * @type {string}
   * @default null
   */
  widgetType: null,

  /**
   * Placeholder used for configs with input type text
   */
  placeholder: function() {
    if (this.isEditable) {
      return this.get('placeholderText') || this.get('savedValue');
    }
    return null;
  }.property('isEditable', 'placeholderText', 'savedValue'),

  retypedPassword: '',
  description: '',
  displayType: 'string', // string, digits, number, directories, custom
  unit: '',
  category: 'General',
  isRequired: true, // by default a config property is required
  isReconfigurable: true, // by default a config property is reconfigurable
  isEditable: true, // by default a config property is editable
  disabledAsComponentAction: false, // is true for component action configs
  isNotEditable: Em.computed.not('isEditable'),
  hideFinalIcon: Em.computed.and('!isFinal', 'isNotEditable'),
  isVisible: true,
  isMock: false, // mock config created created only to displaying
  isRequiredByAgent: true, // Setting it to true implies property will be stored in configuration
  isSecureConfig: false,
  errorMessage: '',
  warnMessage: '',
  validationErrors: [], // stores messages from validation response marked as ERRROR
  validationWarnings: [], // stores message from validation response marked as WARN
  serviceConfig: null, // points to the parent App.ServiceConfig object
  filename: '',
  isOriginalSCP : true, // if true, then this is original SCP instance and its value is not overridden value.
  parentSCP: null, // This is the main SCP which is overridden by this. Set only when isOriginalSCP is false.
  overrides : null,
  overrideValues: [],
  group: null, // Contain group related to this property. Set only when isOriginalSCP is false.
  isUserProperty: null, // This property was added by user. Hence they get removal actions etc.
  isOverridable: true,
  compareConfigs: [],
  isComparison: false,
  hasCompareDiffs: false,
  showLabel: true,
  isConfigIdentity: false,
  copy: '',

  /**
   * Determines if config exists in the non-default config group but is loaded for default config group
   *
   * @type {boolean}
   */
  isCustomGroupConfig: false,

  /**
   * Determines if config is Undefined label, used for overrides, that do not have original property in default group
   * @type {boolean}
   */
  isUndefinedLabel: function () {
    return this.get('displayType') === 'label' && this.get('value') === 'Undefined';
  }.property('displayType', 'value'),

  error: Em.computed.bool('errorMessage.length'),
  warn: Em.computed.bool('warnMessage.length'),
  hasValidationErrors: Em.computed.bool('validationErrors.length'),
  hasValidationWarnings: Em.computed.bool('validationWarnings.length'),
  isValid: Em.computed.equal('errorMessage', ''),

  previousValue: null, // cached value before changing config value

  /**
   * List of isFinal-values for overrides
   * Set in the controller
   * Should be empty array by default!
   * @type {boolean[]}
   */
  overrideIsFinalValues: [],

  /**
   * true if property has warning or error
   * @type {boolean}
   */
  hasIssues: Em.computed.or('error', 'warn', 'overridesWithIssues.length'),

  overridesWithIssues: Em.computed.filterBy('overrides', 'hasIssues', true),

  index: null, //sequence number in category
  editDone: false, //Text field: on focusOut: true, on focusIn: false
  isNotSaved: false, // user property was added but not saved
  hasInitialValue: false, //if true then property value is defined and saved to server
  isHiddenByFilter: false, //if true then hide this property (filtered out)
  rowStyleClass: null, // CSS-Class to be applied on the row showing this config
  showAsTextBox: false,

  /**
   * config is invisible since wrapper section is hidden
   * @type {boolean}
   */
  hiddenBySection: false,

  /**
   * Determines config visibility on subsection level when wrapped.
   * @type {boolean}
   */
  hiddenBySubSection: false,

  /**
   * Determines visibility state including section/subsection state.
   * When true means that property is shown and may affect validation process.
   * When false means that property won't affect validation.
   */
  isActive: Em.computed.and('isVisible', '!hiddenBySubSection', '!hiddenBySection'),

  /**
   * @type {boolean}
   */
  recommendedValueExists: function () {
    return !Em.isNone(this.get('recommendedValue')) && (this.get('recommendedValue') != "")
      && this.get('isRequiredByAgent') && !this.get('cantBeUndone');
  }.property('recommendedValue'),

  /**
   * Usage example see on App.ServiceConfigRadioButtons.handleDBConnectionProperty()
   *
   * @property {Ember.View} additionalView - custom view related to property
   **/
  additionalView: null,

  /**
   * If config is saved we should compare config value with savedValue to
   * find out if it was changed, but if config in not saved there is no savedValue, so
   * we should use initialValue instead.
   */
  isNotInitialValue: function() {
    if (Em.isNone(this.get('savedValue')) && !Em.isNone(this.get('initialValue'))) {
      var value = this.get('value'), initialValue = this.get('initialValue');
      if (this.get('stackConfigProperty.valueAttributes.type') == 'float') {
        initialValue = !Em.isNone(initialValue) ? '' + parseFloat(initialValue) : null;
        value = '' + parseFloat(value);
      }
      return initialValue !== value;
    }
    return false;
  }.property('initialValue', 'savedValue', 'value', 'stackConfigProperty.valueAttributes.type'),

  /**
   * Is property has active override with error
   */
  isValidOverride: function () {
    return this.get('overrides.length') ? !this.get('overrides').find(function(o) {
     return Em.get(o, 'isEditable') && Em.get(o, 'errorMessage');
    }) : true;
  }.property("overrides.@each.errorMessage"),
  /**
   * No override capabilities for fields which are not edtiable
   * and fields which represent master hosts.
   */
  isPropertyOverridable: function () {
    var overrideable = this.get('isOverridable');
    var editable = this.get('isEditable');
    var overrides = this.get('overrides');
    var dt = this.get('displayType');
    return overrideable && (editable || !overrides || !overrides.length) && (!["componentHost", "password"].contains(dt));
  }.property('isEditable', 'displayType', 'isOverridable', 'overrides.length'),

  isOverridden: Em.computed.or('overrides.length', '!isOriginalSCP'),

  isOverrideChanged: function () {
    if (Em.isNone(this.get('overrides')) && this.get('overrideValues.length') === 0) return false;
    return JSON.stringify(this.get('overrides').mapProperty('isFinal')) !== JSON.stringify(this.get('overrideIsFinalValues'))
      || JSON.stringify(this.get('overrides').mapProperty('value')) !== JSON.stringify(this.get('overrideValues'));
  }.property('overrides.@each.isNotDefaultValue', 'overrides.@each.overrideIsFinalValues', 'overrideValues.length'),

  isRemovable: function() {
    return this.get('isEditable') && this.get('isRequiredByAgent') && !(this.get('overrides.length') > 0)
       && (this.get('isUserProperty') || !this.get('isOriginalSCP'));
  }.property('isUserProperty', 'isOriginalSCP', 'overrides.length', 'isRequiredByAgent'),

  init: function () {
    this.setInitialValues();
    this.set('viewClass', App.config.getViewClass(this.get("displayType"), this.get('dependentConfigPattern'), this.get('unit')));
    this.set('validateErrors', App.config.getErrorValidator(this.get("displayType")));
    this.set('validateWarnings', App.config.getWarningValidator(this.get("displayType")));
    this.validate();
  },

  setInitialValues: function () {
    if (Em.isNone(this.get('value'))) {
      if (!Em.isNone(this.get('savedValue'))) {
        this.set('value', this.get('savedValue'));
      } else if (!Em.isNone(this.get('recommendedValue'))) {
        this.set('value', this.get('recommendedValue'));
      }
    }
    this.set('previousValue', this.get('value'));
    if (this.get('value') === null) {
      this.set('isVisible', false);
    }
    if (this.get("displayType") === "password") {
      this.set('retypedPassword', this.get('value'));
      this.set('recommendedValue', '');
    }
    this.set('initialValue', this.get('value'));
  },

  /**
   * updates configs list that belongs to config group
   */
  updateGroupConfigs: function() {
    if (this.get('group')) {
      var o = this.get('group.properties').find(function(c) {
        return Em.get(c, 'name') === this.get('name') && Em.get(c, 'filename') === this.get('filename');
      }, this);

      if (o) {
        Em.set(o, 'value', this.get('value'));
      }
    }
  }.observes('value'),

  /**
   * Indicates when value is not the default value.
   * Returns false when there is no default value.
   *
   * @type {boolean}
   */
  isNotDefaultValue: function () {
    var value = this.get('value'),
      savedValue = this.get('savedValue'),
      supportsFinal = this.get('supportsFinal'),
      isFinal = this.get('isFinal'),
      savedIsFinal = this.get('savedIsFinal');

    // ignore precision difference for configs with type of `float` which value may ends with 0
    // e.g. between 0.4 and 0.40
    if (this.get('stackConfigProperty') && this.get('stackConfigProperty.valueAttributes.type') == 'float') {
      savedValue = !Em.isNone(savedValue) ? '' + parseFloat(savedValue) : null;
      value = '' + parseFloat(value);
    }
    return (savedValue != null && value !== savedValue) || (supportsFinal && !Em.isNone(savedIsFinal) && isFinal !== savedIsFinal);
  }.property('value', 'savedValue', 'isEditable', 'isFinal', 'savedIsFinal'),

  /**
   * Don't show "Undo" for hosts on Installer Step7
   */
  cantBeUndone: Em.computed.existsIn('displayType', ["componentHost", "componentHosts", "radio button"]),

  validate: function () {
    if ((typeof this.get('value') != 'object') && ((this.get('value') + '').length === 0)) {
      var widgetType = this.get('widgetType');
      this.set('errorMessage', (this.get('isRequired') && (!['test-db-connection','label'].contains(widgetType))) ? Em.I18n.t('errorMessage.config.required') : '');
    } else {
      this.set('errorMessage', this.validateErrors(this.get('value'), this.get('name'), this.get('retypedPassword')));
    }
    if (!this.get('widgetType') || ('text-field' === this.get('widgetType'))) {
      //temp conditions, since other warnings are calculated directly in widget view
      this.set('warnMessage', this.validateWarnings(this.get('value'), this.get('name'), this.get('filename'),
        this.get('stackConfigProperty'), this.get('unit')));
    }
  }.observes('value', 'retypedPassword', 'isEditable'),

  viewClass: App.ServiceConfigTextField,

  validateErrors: function() { return '' },

  validateWarnings: function() { return '' },

  /**
   * Get override for selected group
   *
   * @param {String} groupName
   * @returns {App.ServiceConfigProperty|null}
   */
  getOverride: function(groupName) {
    Em.assert('Group name should be defined string', (typeof groupName === 'string') && groupName);
    if (this.get('overrides.length')) {
      return this.get('overrides').findProperty('group.name', groupName);
    }
    return null;
  }

});
<|endoftext|>


<commit_before>/**
 * jQuery highlightTextarea 2.0
 *
 * Copyright 2012, Damien "Mistic" Sorel
 *    http://www.strangeplanet.fr
 *
 * thanks to Julien L for the main algorythm
 *    http://stackoverflow.com/a/7599199
 *
 * thanks to Pascal Wacker for jQuery wrapper and API methods
 *    pascal.wacker@tilllate.com
 *
 * Dual licensed under the MIT or GPL Version 3 licenses.
 *    http://www.opensource.org/licenses/mit-license.php
 *    http://www.gnu.org/licenses/gpl.html
 *
 * Depends:
 *	  jquery.js
 *    jquery-ui.js | resizable (optional)
 */

(function($) {
    /**
     * Plugin declaration
     */
    $.fn.highlightTextarea = function(options) {
        // callable public methods
        var callable = ['highlight','enable','disable','setOptions','setWords'];

        var plugin = $(this).data('highlightTextarea');

        // already instantiated and trying to execute a method
        if (plugin && typeof options === 'string') {
            if ($.inArray(options,callable)!==false) {
                return plugin[options].apply(plugin, Array.prototype.slice.call(arguments, 1));
            }
            else {
                throw 'Method "' + options + '" does not exist on jQuery.highlightTextarea';
            }
        }
        // not instantiated and trying to pass options object (or nothing)
        else if (!plugin && (typeof options === 'object' || !options)) {
            if (!options) {
                options = {};
            }

            // extend defaults
            options = $.extend({}, $.fn.highlightTextarea.defaults, options);
            options.regParam = options.caseSensitive ? 'g' : 'gi';

            // for each element instantiate the plugin
            return this.each(function() {
                var plugin = $(this).data('highlightTextarea');

                // create new instance of the plugin if the plugin isn't initialised
                if (!plugin) {
                    plugin = new $.highlightTextarea($(this), options);
                    plugin.init();
                    $(this).data('highlightTextarea', plugin);
                }
            });
        }
    }

    /**
     * Defaults
     */
    $.fn.highlightTextarea.defaults = {
        words:         [],
        color:         '#ffff00',
        caseSensitive: true,
        resizable:     false,
        id:            null,
        debug:         false
    };

    /**
     * Main plugin function
     */
    $.highlightTextarea = function(element, options) {
        this.options = options;

        if (element instanceof jQuery) {
            this.$textarea = element;
        }
        else {
            this.$textarea = $(element);
        }

        this.$main = null;
        this.$highlighterContainer = null;
        this.$highlighter = null;

        /*
         * init the plugin
         * scope: private
         */
        this.init = function() {
            // build the HTML wrapper
            if (this.$textarea.closest('.highlightTextarea').length <= 0) {
                this.$textarea.wrap('
');
            }
            this.$main = this.$textarea.parent('.highlightTextarea');

            if (this.$main.find('.highlighterContainer').length <= 0) {
                this.$main.prepend('

');
            }
            this.$highlighterContainer = this.$main.children('.highlighterContainer');

            if (this.$highlighterContainer.find('.highlighter').length <= 0) {
                this.$highlighterContainer.html('

');
            }
            this.$highlighter = this.$highlighterContainer.children('.highlighter');

            // set id
            if (this.options.id != null) {
                this.$main.attr('id', this.options.id);
            }

            // set css
            this.updateCss();

            // bind the events
            this.bindEvents();

            // apply the resizeable
            this.applyResizable();

            // highlight content
            this.highlight();
        }

        /*
         * compute highlight
         * @param delay: boolean - use a delayed update
         * scope: public
         */
        this.highlight = function(delay) {
            if (delay==null || delay==false) {
                this.applyText(this.$textarea.val());
            }
            else {
                this.condensator($.proxy(function(){
                  this.applyText(this.$textarea.val());
                }, this), 100, 300);
            }

            return this.$textarea.data('highlightTextareaEvents')===true;
        }

        /*
         * update plugin options
         * scope: public
         */
        this.setOptions = function(options) {
            if (typeof options != 'object') {
                options = {};
            }

            this.options = $.extend({}, this.options, options);
            this.options.regParam = this.options.caseSensitive ? 'g' : 'gi';

            if (this.options.debug) {
                this.$highlighter.addClass('debug');
            }
            else {
                this.$highlighter.removeClass('debug');
            }

            if (this.$textarea.data('highlightTextareaEvents')===true) {
                this.highlight();
                return true;
            }
            else {
                return false;
            }
        }

        /*
         * update words list
         * scope: public
         */
        this.setWords = function(words) {
            if (typeof words !== 'string' && !(words instanceof Array)) {
                words = [];
            }
            else if (typeof words === 'string') {
                words = [words];
            }
            this.options.words = words;

            if (this.$textarea.data('highlightTextareaEvents')===true) {
                this.highlight();
                return true;
            }
            else {
                return false;
            }
        }

        /*
         * add events handlers
         * scope: private
         */
        this.bindEvents = function() {
            var events = this.$textarea.data('highlightTextareaEvents');

            if (typeof events != 'boolean' || events !== true) {
                // prevend positionning errors by allways focusing the textarea
                this.$highlighter.on({
                  'click.highlightTextarea' : $.proxy(function(){ this.$textarea.focus(); }, this)
                });

                // add triggers to textarea
                this.$textarea.on({
                    'input.highlightTextarea' :  $.proxy(function(){ this.highlight(true); }, this),
                    'resize.highlightTextarea' : $.proxy(function(){ this.updateSizePosition(true); }, this),
                    'scroll.highlightTextarea' : $.proxy(function(){ this.updateSizePosition(); }, this)
                });

                this.$textarea.data('highlightTextareaEvents', true);
            }
        }

        /*
         * remove event handlers
         * scope: private
         */
        this.unbindEvents = function() {
            this.$highlighter.off('click.highlightTextarea');
            this.$textarea.off('input.highlightTextarea scroll.highlightTextarea resize.highlightTextarea');
            this.$textarea.data('highlightTextareaEvents', false);
        }

        /*
         * enable the highlighting
         * scope: public
         */
        this.enable = function() {
            this.bindEvents();
            this.highlight();
        }

        /*
         * disable the highlighting
         * scope: public
         */
        this.disable = function() {
            this.unbindEvents();
            this.$highlighter.html(this.html_entities(this.$textarea.val()));
        }

        /*
         * set style of containers
         * scope: private
         */
        this.updateCss = function() {
            // the main container has the same size and position than the original textarea
            this.cloneCss(this.$textarea, this.$main, [
                'float','vertical-align'
            ]);
            this.$main.css({
                'width':  this.$textarea.outerWidth(true),
                'height': this.$textarea.outerHeight(true)
            });

            // the highlighter container is positionned at "real" top-left corner of the textarea and takes its background
            this.cloneCss(this.$textarea, this.$highlighterContainer, [
                'background','background-image','background-color','background-position','background-repeat','background-origin','background-clip','background-size',
                'padding-top','padding-right','padding-bottom','padding-left'
            ]);
            this.$highlighterContainer.css({
                'top':    this.toPx(this.$textarea.css('margin-top')) + this.toPx(this.$textarea.css('border-top-width')),
                'left':   this.toPx(this.$textarea.css('margin-left')) + this.toPx(this.$textarea.css('border-left-width')),
                'width':  this.$textarea.width(),
                'height': this.$textarea.height()
            });

            // the highlighter has the same size than the "inner" textarea and must have the same font properties
            this.cloneCss(this.$textarea, this.$highlighter, [
                'font-size','font-family','font-style','font-weight','line-height',
                'vertical-align','word-spacing','text-align'
            ]);
            this.$highlighter.css({
                'width':  this.$textarea.width(),
                'height': this.$textarea.height()
            });

            // now make the textarea transparent to see the highlighter throught
            this.$textarea.css({
                'background': 'none',
            });

            // display highlighter text for debuging
            if (this.options.debug) {
                this.$highlighter.addClass('debug');
            }
        }

        /*
         * set textarea as resizable
         * scope: private
         */
        this.applyResizable = function() {
            if (this.options.resizable && jQuery.ui) {
                this.$textarea.resizable({
                    'handles': 'se',
                    'resize':  $.proxy(function() { this.updateSizePosition(true); }, this)
                });
            }
        }

        /*
         * replace $highlighter html with formated $textarea content
         * scope: private
         */
        this.applyText = function(text) {
            text = this.html_entities(text);

            if (this.options.words.length > 0) {
                replace = new Array();

                for (var i=0; i<this.options.words.length; i++) {
                  replace.push(this.html_entities(this.options.words[i]));
                }

                text = text.replace(
                  new RegExp('('+replace.join('|')+')', this.options.regParam),
                  "$1"
                );
            }

            this.$highlighter.html(text);
            this.updateSizePosition();
        }

        /*
         * adapt $highlighter size and position according to $textarea size and scroll bar
         * @param forced: boolean - update containers size
         * scope: private
         */
        this.updateSizePosition = function(forced) {
            // resize containers
            if (forced) {
                this.$main.css({
                    'width':  this.$textarea.outerWidth(true),
                    'height': this.$textarea.outerHeight(true)
                });
                this.$highlighterContainer.css({
                    'width':  this.$textarea.width(),
                    'height': this.$textarea.height()
                });
            }

            if (
              (this.$textarea[0].clientHeight < this.$textarea[0].scrollHeight && this.$textarea.css('overflow') != 'hidden' && this.$textarea.css('overflow-y') != 'hidden')
              || this.$textarea.css('overflow') == 'scroll' || this.$textarea.css('overflow-y') == 'scroll'
            ) {
                var padding = 18;
            }
            else {
                var padding = 5;
            }

            this.$highlighter.css({
                'width':         this.$textarea.width()-padding,
                'height':        this.$textarea.height()+this.$textarea.scrollTop(),
                'padding-right': padding,
                'top':           -this.$textarea.scrollTop()
            });
        }

        /*
         * set 'to' css attributes listed in 'what' as defined for 'from'
         * scope: private
         */
        this.cloneCss = function(from, to, what) {
            for (var i=0; i<what.length; i++) {
                to.css(what[i], from.css(what[i]));
            }
        }

        /*
         * clean/convert px and em size to px size (without 'px' suffix)
         * scope: private
         */
        this.toPx = function(value) {
            if (value != value.replace('em', '')) {
                // https://github.com/filamentgroup/jQuery-Pixel-Em-Converter
                var that = parseFloat(value.replace('em', '')),
                    scopeTest = $('

').appendTo('body'),
                    scopeVal = scopeTest.height();
                scopeTest.remove();
                return Math.round(that * scopeVal);
            }
            else if (value != value.replace('px', '')) {
                return parseInt(value.replace('px', ''));
            }
            else {
                return parseInt(value);
            }
        }

        /*
         * apply html entities
         * scope: private
         */
        this.html_entities = function(value) {
            if (value) {
                return $('
').text(value).html();
            }
            else {
                return '';
            }
        }

        /*
         * add a delay with age limit to a method
         * scope: private
         */
        var timer = null;
        var startTime = null;
        this.condensator = function(callback, ms, limit) {
            if (limit==null) {
              limit=ms;
            }

            var date = new Date();
            clearTimeout(timer);

            if (startTime==null) {
                startTime = date.getTime();
            }

            if (date.getTime() - startTime > limit) {
                callback.call();
                startTime = date.getTime();
            }
            else {
                timer = setTimeout(callback, ms);
            }
        }
    };
})(jQuery);<commit_msg>Using @rinogo's fork of the text highlighter JS<commit_after>/**
 * jQuery highlightTextarea 2.0
 *
 * Copyright 2012, Damien "Mistic" Sorel
 *    http://www.strangeplanet.fr
 *
 * thanks to Julien L for the main algorythm
 *    http://stackoverflow.com/a/7599199
 *
 * thanks to Pascal Wacker for jQuery wrapper and API methods
 *    pascal.wacker@tilllate.com
 *
 * Dual licensed under the MIT or GPL Version 3 licenses.
 *    http://www.opensource.org/licenses/mit-license.php
 *    http://www.gnu.org/licenses/gpl.html
 *
 * Depends:
 *    jquery.js
 *    jquery-ui.js | resizable (optional)
 */

(function($) {
    /**
     * Plugin declaration
     */
    $.fn.highlightTextarea = function(options) {
        // callable public methods
        var callable = ['highlight','enable','disable','setOptions','setWords'];

        var plugin = $(this).data('highlightTextarea');

        // already instantiated and trying to execute a method
        if (plugin && typeof options === 'string') {
            if ($.inArray(options,callable)!==false) {
                return plugin[options].apply(plugin, Array.prototype.slice.call(arguments, 1));
            }
            else {
                throw 'Method "' + options + '" does not exist on jQuery.highlightTextarea';
            }
        }
        // not instantiated and trying to pass options object (or nothing)
        else if (!plugin && (typeof options === 'object' || !options)) {
            if (!options) {
                options = {};
            }

            // extend defaults
            options = $.extend({}, $.fn.highlightTextarea.defaults, options);
            options.regParam = options.caseSensitive ? 'g' : 'gi';

            // for each element instantiate the plugin
            return this.each(function() {
                var plugin = $(this).data('highlightTextarea');

                // create new instance of the plugin if the plugin isn't initialised
                if (!plugin) {
                    plugin = new $.highlightTextarea($(this), options);
                    plugin.init();
                    $(this).data('highlightTextarea', plugin);
                }
            });
        }
    }

    /**
     * Defaults
     */
    $.fn.highlightTextarea.defaults = {
        words:         [],
        color:         '#ffff00',
        caseSensitive: true,
        resizable:     false,
        id:            null,
        debug:         false
    };

    /**
     * Main plugin function
     */
    $.highlightTextarea = function(element, options) {
        this.options = options;

        if (element instanceof jQuery) {
            this.$textarea = element;
        }
        else {
            this.$textarea = $(element);
        }

        this.$main = null;
        this.$highlighterContainer = null;
        this.$highlighter = null;

        /*
         * init the plugin
         * scope: private
         */
        this.init = function() {
                        //Determine scrollbar width
                        this.scrollbarWidth = this.getScrollbarWidth();

            // build the HTML wrapper
            if (this.$textarea.closest('.highlightTextarea').length <= 0) {
                this.$textarea.wrap('
');
            }
            this.$main = this.$textarea.parent('.highlightTextarea');

            if (this.$main.find('.highlighterContainer').length <= 0) {
                this.$main.prepend('

');
            }
            this.$highlighterContainer = this.$main.children('.highlighterContainer');

            if (this.$highlighterContainer.find('.highlighter').length <= 0) {
                this.$highlighterContainer.html('

');
            }
            this.$highlighter = this.$highlighterContainer.children('.highlighter');

            // set id
            if (this.options.id != null) {
                this.$main.attr('id', this.options.id);
            }

            // set css
            this.updateCss();

            // bind the events
            this.bindEvents();

            // apply the resizeable
            this.applyResizable();

            // highlight content
            this.highlight();
        }

        /*
         * compute highlight
         * @param delay: boolean - use a delayed update
         * scope: public
         */
        this.highlight = function(delay) {
            if (delay==null || delay==false) {
                this.applyText(this.$textarea.val());
            }
            else {
                this.condensator($.proxy(function(){
                  this.applyText(this.$textarea.val());
                }, this), 100, 300);
            }

            return this.$textarea.data('highlightTextareaEvents')===true;
        }

        /*
         * update plugin options
         * scope: public
         */
        this.setOptions = function(options) {
            if (typeof options != 'object') {
                options = {};
            }

            this.options = $.extend({}, this.options, options);
            this.options.regParam = this.options.caseSensitive ? 'g' : 'gi';

            if (this.options.debug) {
                this.$highlighter.addClass('debug');
            }
            else {
                this.$highlighter.removeClass('debug');
            }

            if (this.$textarea.data('highlightTextareaEvents')===true) {
                this.highlight();
                return true;
            }
            else {
                return false;
            }
        }

        /*
         * update words list
         * scope: public
         */
        this.setWords = function(words) {
            if (typeof words !== 'string' && !(words instanceof Array)) {
                words = [];
            }
            else if (typeof words === 'string') {
                words = [words];
            }
            this.options.words = words;

            if (this.$textarea.data('highlightTextareaEvents')===true) {
                this.highlight();
                return true;
            }
            else {
                return false;
            }
        }

        /*
         * add events handlers
         * scope: private
         */
        this.bindEvents = function() {
            var events = this.$textarea.data('highlightTextareaEvents');

            if (typeof events != 'boolean' || events !== true) {
                // prevend positionning errors by allways focusing the textarea
                this.$highlighter.on({
                  'click.highlightTextarea' : $.proxy(function(){ this.$textarea.focus(); }, this)
                });

                // add triggers to textarea
                this.$textarea.on({
                    'input.highlightTextarea' :  $.proxy(function(){ this.highlight(true); }, this),
                    'resize.highlightTextarea' : $.proxy(function(){ this.updateSizePosition(true); }, this),
                    'scroll.highlightTextarea' : $.proxy(function(){ this.updateSizePosition(); }, this)
                });

                                //For <input> elements, also call updateSizePosition() on a more comprehensive set of events.  (<input> elements don't seem to trigger the scroll event, so we have to detect changes through more creative means...)
                                if(this.$textarea[0].tagName.toLowerCase()=='input') {
                    this.$textarea.on({
                                        //The slight delay below helps prevent Cmd-Left Arrow and Cmd-Right Arrow on Mac from behaving strangely.  (With this delay removed, the highlight is only updated after Cmd is released)
                    'keydown.highlightTextarea keypress.highlightTextarea keyup.highlightTextarea input.highlightTextarea2 select.highlightTextarea' : $.proxy(function(){ setTimeout($.proxy(function() { this.updateSizePosition(); }, this), 1); }, this),
                    'mouseover.highlightTextarea' : $.proxy(function(){ this.inputRefreshInterval = setInterval($.proxy(function() { this.updateSizePosition(); }, this), 100); }, this), //Respond to horizontal mouse scrolling (again, we can't use the scroll event, so this is the second best option.  Feel free to comment out the mouseover and mouseout handlers if you can't spare the extra CPU required for polling)
                    'mouseout.highlightTextarea' : $.proxy(function(){ clearInterval(this.inputRefreshInterval); this.updateSizePosition(); }, this)
                    });
                                }

                this.$textarea.data('highlightTextareaEvents', true);
            }
        }

        /*
         * remove event handlers
         * scope: private
         */
        this.unbindEvents = function() {
            this.$highlighter.off('click.highlightTextarea');
            this.$textarea.off('input.highlightTextarea scroll.highlightTextarea resize.highlightTextarea keydown.highlightTextarea keypress.highlightTextarea keyup.highlightTextarea input.highlightTextarea2 select.highlightTextarea mouseover.highlightTextarea mouseout.highlightTextarea');
            this.$textarea.data('highlightTextareaEvents', false);
        }

        /*
         * enable the highlighting
         * scope: public
         */
        this.enable = function() {
            this.bindEvents();
            this.highlight();
        }

        /*
         * disable the highlighting
         * scope: public
         */
        this.disable = function() {
            this.unbindEvents();
            this.$highlighter.html(this.html_entities(this.$textarea.val()));
        }

        /*
         * set style of containers
         * scope: private
         */
        this.updateCss = function() {
            // the main container has the same size and position than the original textarea
            this.cloneCss(this.$textarea, this.$main, [
                'float','vertical-align'
            ]);
            this.$main.css({
                'width':  this.$textarea.outerWidth(true),
                'height': this.$textarea.outerHeight(true)
            });

            // the highlighter container is positionned at "real" top-left corner of the textarea and takes its background
            this.cloneCss(this.$textarea, this.$highlighterContainer, [
                'background','background-image','background-color','background-position','background-repeat','background-origin','background-clip','background-size',
                'padding-top','padding-right','padding-bottom','padding-left'
            ]);
            this.$highlighterContainer.css({
                'top':    this.toPx(this.$textarea.css('margin-top')) + this.toPx(this.$textarea.css('border-top-width')),
                'left':   this.toPx(this.$textarea.css('margin-left')) + this.toPx(this.$textarea.css('border-left-width')),
                'width':  this.$textarea.width(),
                'height': this.$textarea.height()
            });

            // the highlighter has the same size than the "inner" textarea and must have the same font properties
            this.cloneCss(this.$textarea, this.$highlighter, [
                'font-size','font-family','font-style','font-weight','line-height',
                'vertical-align','word-spacing','text-align'
            ]);
            this.$highlighter.css({
                'width':  this.$textarea.width(),
                'height': this.$textarea.height()
            });

            // now make the textarea transparent to see the highlighter throught
            this.$textarea.css({
                'background': 'none',
            });

            // display highlighter text for debuging
            if (this.options.debug) {
                this.$highlighter.addClass('debug');
            }
        }

        /*
         * set textarea as resizable
         * scope: private
         */
        this.applyResizable = function() {
            if (this.options.resizable && jQuery.ui) {
                this.$textarea.resizable({
                    'handles': 'se',
                    'resize':  $.proxy(function() { this.updateSizePosition(true); }, this)
                });
            }
        }

        /*
         * replace $highlighter html with formated $textarea content
         * scope: private
         */
        this.applyText = function(text) {
            text = this.html_entities(text);

            if (this.options.words.length > 0) {
                replace = new Array();

                for (var i=0; i<this.options.words.length; i++) {
                  replace.push(this.html_entities(this.options.words[i]));
                }

                text = text.replace(
                  new RegExp('('+replace.join('|')+')', this.options.regParam),
                  "$1"
                );
            }

            this.$highlighter.html(text);
            this.updateSizePosition();
        }

        /*
         * adapt $highlighter size and position according to $textarea size and scroll bar
         * @param forced: boolean - update containers size
         * scope: private
         */
        this.updateSizePosition = function(forced) {
            // resize containers
            if (forced) {
                this.$main.css({
                    'width':  this.$textarea.outerWidth(true),
                    'height': this.$textarea.outerHeight(true)
                });
                this.$highlighterContainer.css({
                    'width':  this.$textarea.width(),
                    'height': this.$textarea.height()
                });
            }

                        //If there is a vertical scrollbar, account for its width
            if (
              (this.$textarea[0].clientHeight < this.$textarea[0].scrollHeight && this.$textarea.css('overflow') != 'hidden' && this.$textarea.css('overflow-y') != 'hidden')
              || this.$textarea.css('overflow') == 'scroll' || this.$textarea.css('overflow-y') == 'scroll'
            ) {
                var padding = this.scrollbarWidth;
            }
                        //No vertical scrollbar detected
            else {
                var padding = 0;
            }

                        var width = this.$textarea[0].tagName.toLowerCase()=='input' ? 99999 : this.$textarea.width()-padding; //TODO: There's got to be a better way of going about this than just using 99999px...
            this.$highlighter.css({
                'width':         width,
                'height':        this.$textarea.height()+this.$textarea.scrollTop(),
                'padding-right': padding,
                'top':           -this.$textarea.scrollTop(),
                'left':          -this.$textarea.scrollLeft()
            });
        }

                //Adapted from http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth
                this.getScrollbarWidth = function() {
                var parent,
                  child;

                if ( typeof width === 'undefined' ) {
                  parent = $('

').appendTo('body');
                  child = parent.children();
                  width = child.innerWidth() - child.height( 99 ).innerWidth();
                  parent.remove();
                }

                return width;
                }

        /*
         * set 'to' css attributes listed in 'what' as defined for 'from'
         * scope: private
         */
        this.cloneCss = function(from, to, what) {
            for (var i=0; i<what.length; i++) {
                to.css(what[i], from.css(what[i]));
            }
        }

        /*
         * clean/convert px and em size to px size (without 'px' suffix)
         * scope: private
         */
        this.toPx = function(value) {
            if (value != value.replace('em', '')) {
                // https://github.com/filamentgroup/jQuery-Pixel-Em-Converter
                var that = parseFloat(value.replace('em', '')),
                    scopeTest = $('

').appendTo('body'),
                    scopeVal = scopeTest.height();
                scopeTest.remove();
                return Math.round(that * scopeVal);
            }
            else if (value != value.replace('px', '')) {
                return parseInt(value.replace('px', ''));
            }
            else {
                return parseInt(value);
            }
        }

        /*
         * apply html entities
         * scope: private
         */
        this.html_entities = function(value) {
            if (value) {
                return $('
').text(value).html();
            }
            else {
                return '';
            }
        }

        /*
         * add a delay with age limit to a method
         * scope: private
         */
        var timer = null;
        var startTime = null;
        this.condensator = function(callback, ms, limit) {
            if (limit==null) {
              limit=ms;
            }

            var date = new Date();
            clearTimeout(timer);

            if (startTime==null) {
                startTime = date.getTime();
            }

            if (date.getTime() - startTime > limit) {
                callback.call();
                startTime = date.getTime();
            }
            else {
                timer = setTimeout(callback, ms);
            }
        }
    };
})(jQuery);
<|endoftext|>


<commit_before>var fs      = require('fs');
var path    = require('path');
var chai    = require('chai');
var expect  = chai.expect;
var sinon   = require('sinon');
var es      = require('event-stream');
var stream  = require('stream');
var through = require('through2');
var moment  = require('moment');
var Prowl   = require('node-prowl');
var ToDue   = require('..');

chai.Assertion.addMethod('afterDate', function(date) {
  var obj = moment(this._obj);
  date = moment(date);
  this.assert(
    obj.isAfter(date, 'day'),
    'expected #{act} to be after #{exp} (in days)',
    'expected #{act} to not be after #{exp} (in days)',
    date.format('YYYY-MM-DD'),
    obj.format('YYYY-MM-DD')
  );
});

var check = function(callback, expectations, args) {
  return function() {
    try {
      expectations(args && args());
    } catch (err) {
      callback(err);
    }
    callback();
  };
};

var checkStream = function(callback, expectations) {
  var results = [];
  return through.obj(function(chunk, enc, callback) {
    results.push(chunk);
    this.push(chunk);
    callback();
  }, check(callback, expectations, function() { return results; }))
  .once('error', callback);
};

var getFixturePath = function(todoName) {
  return path.join(__dirname, 'fixtures', todoName);
};

var getFixtureStream = function(todoName) {
  return fs.createReadStream(getFixturePath(todoName + '.txt'));
};

var getFixtureData = function(todoName) {
  return new stream.Readable({objectMode: true})
  .wrap(es.readArray(require(getFixturePath(todoName + '.json'))));
};

describe('todoParse', function() {
  beforeEach(function() {
    this.testStream = getFixtureStream('todoparse').pipe(es.split());
  });

  it('converts a text stream to stream of todo objects', function(done) {
    var expected = [
      {complete: false, description: 'test todo 1', contexts: ["Testing"], projects: ["tester"]},
      {complete: false, description: 'test todo 2', contexts: [], projects: ["tester"], due: "2015-06-07"},
      {complete: true,  description: 'test todo 3', contexts: ["Testing"], projects: []}
    ];

    this.testStream.pipe(ToDue.todoParse())
    .pipe(checkStream(done, function(results) {
      expect(results).to.deep.equal(expected);
    }));
  });
});

describe('filterByDueDate', function() {
  beforeEach(function() {
    this.testStream = getFixtureData('todo');
  });

  it('ignores todos that are in the future', function(done) {
    var date = moment("2015-06-07", "YYYY-MM-DD");
    this.testStream.pipe(ToDue.filterByDueDate(date))
    .pipe(checkStream(done, function(results) {
      expect(results).to.have.length.above(0);
      results.forEach(function(todo) {
        expect(todo).to.have.property('due');
        expect(todo.due).to.not.be.afterDate(date);
      });
    }));
  });

  it('flags todos that are past due', function(done) {
    var date = moment("2016-07-10", "YYYY-MM-DD");
    this.testStream.pipe(ToDue.filterByDueDate(date))
    .pipe(checkStream(done, function(results) {
      expect(results).to.have.length.above(0);
      results.forEach(function(todo) {
        expect(todo).to.have.property('pastDue', true);
      });
    }));
  });
});

describe('filterComplete', function() {
  beforeEach(function() {
    this.testStream = getFixtureData('todo');
  });

  it('ignores completed todos', function(done) {
    this.testStream.pipe(ToDue.filterComplete())
    .pipe(checkStream(done, function(results) {
      expect(results).to.have.length.above(0);
      results.forEach(function(todo) {
        expect(todo.complete).to.be.false;
      });
    }));
  });
});

describe('sendToProwl', function() {
  beforeEach(function() {
    this.testStream = getFixtureData('sendtoprowltodos');
    this.prowl = new Prowl('12345');
  });

  it('sends a todo message to the prowl API', function(done) {
    var prowlMessageMatch = sinon.match
      .has('description', sinon.match.string)
      .and(sinon.match.has('priority', sinon.match.number));

    var pushSpy = sinon.mock(this.prowl)
      .expects('push')
      .callsArg(3)
      .withArgs(sinon.match.string, sinon.match.string, prowlMessageMatch)
      .twice();

    this.testStream.pipe(ToDue.sendToProwl(this.prowl))
    .once('finish', check(done, function() { pushSpy.verify(); }))
    .once('error', done);
  });
});
<commit_msg>Spec the helper functions<commit_after>var fs      = require('fs');
var path    = require('path');
var chai    = require('chai');
var expect  = chai.expect;
var sinon   = require('sinon');
var es      = require('event-stream');
var stream  = require('stream');
var through = require('through2');
var moment  = require('moment');
var Prowl   = require('node-prowl');
var ToDue   = require('..');

chai.Assertion.addMethod('afterDate', function(date) {
  var obj = moment(this._obj);
  date = moment(date);
  this.assert(
    obj.isAfter(date, 'day'),
    'expected #{act} to be after #{exp} (in days)',
    'expected #{act} to not be after #{exp} (in days)',
    date.format('YYYY-MM-DD'),
    obj.format('YYYY-MM-DD')
  );
});

var check = function(callback, expectations, args) {
  return function() {
    try {
      expectations(args && args());
    } catch (err) {
      callback(err);
    }
    callback();
  };
};

var checkStream = function(callback, expectations) {
  var results = [];
  return through.obj(function(chunk, enc, callback) {
    results.push(chunk);
    this.push(chunk);
    callback();
  }, check(callback, expectations, function() { return results; }))
  .once('error', callback);
};

var getFixturePath = function(todoName) {
  return path.join(__dirname, 'fixtures', todoName);
};

var getFixtureStream = function(todoName) {
  return fs.createReadStream(getFixturePath(todoName + '.txt'));
};

var getFixtureData = function(todoName) {
  return new stream.Readable({objectMode: true})
  .wrap(es.readArray(require(getFixturePath(todoName + '.json'))));
};

describe('todoParse', function() {
  beforeEach(function() {
    this.testStream = getFixtureStream('todoparse').pipe(es.split());
  });

  it('converts a text stream to stream of todo objects', function(done) {
    var expected = [
      {complete: false, description: 'test todo 1', contexts: ["Testing"], projects: ["tester"]},
      {complete: false, description: 'test todo 2', contexts: [], projects: ["tester"], due: "2015-06-07"},
      {complete: true,  description: 'test todo 3', contexts: ["Testing"], projects: []}
    ];

    this.testStream.pipe(ToDue.todoParse())
    .pipe(checkStream(done, function(results) {
      expect(results).to.deep.equal(expected);
    }));
  });
});

describe('filterByDueDate', function() {
  beforeEach(function() {
    this.testStream = getFixtureData('todo');
  });

  it('ignores todos that are in the future', function(done) {
    var date = moment("2015-06-07", "YYYY-MM-DD");
    this.testStream.pipe(ToDue.filterByDueDate(date))
    .pipe(checkStream(done, function(results) {
      expect(results).to.have.length.above(0);
      results.forEach(function(todo) {
        expect(todo).to.have.property('due');
        expect(todo.due).to.not.be.afterDate(date);
      });
    }));
  });

  it('flags todos that are past due', function(done) {
    var date = moment("2016-07-10", "YYYY-MM-DD");
    this.testStream.pipe(ToDue.filterByDueDate(date))
    .pipe(checkStream(done, function(results) {
      expect(results).to.have.length.above(0);
      results.forEach(function(todo) {
        expect(todo).to.have.property('pastDue', true);
      });
    }));
  });
});

describe('filterComplete', function() {
  beforeEach(function() {
    this.testStream = getFixtureData('todo');
  });

  it('ignores completed todos', function(done) {
    this.testStream.pipe(ToDue.filterComplete())
    .pipe(checkStream(done, function(results) {
      expect(results).to.have.length.above(0);
      results.forEach(function(todo) {
        expect(todo.complete).to.be.false;
      });
    }));
  });
});

describe('todoProwlDesc', function() {
  beforeEach(function() {
    this.mockTodo = {
      'description': 'foobar',
      'contexts': [],
      'projects': []
    };
  });

  it('returns a string', function() {
    expect(ToDue.todoProwlDesc(this.mockTodo)).to.be.a.string;
    this.mockTodo.priority = 'A';
    expect(ToDue.todoProwlDesc(this.mockTodo)).to.be.a.string;
  });
});

describe('prowlPriority', function() {
  beforeEach(function() {
    this.testCases = ['A', 'B', 'C', 'D', 'E', 'F', null, ''];
  });

  it('returns a valid Prowl priority number', function() {
    this.testCases.forEach(function (pri) {
      expect(ToDue.prowlPriority(pri)).to.be.at.least(-2).and.at.most(2);
    });
  });
});

describe('sendToProwl', function() {
  beforeEach(function() {
    this.testStream = getFixtureData('sendtoprowltodos');
    this.prowl = new Prowl('12345');
  });

  it('sends a todo message to the prowl API', function(done) {
    var prowlMessageMatch = sinon.match
      .has('description', sinon.match.string)
      .and(sinon.match.has('priority', sinon.match.number));

    var pushSpy = sinon.mock(this.prowl)
      .expects('push')
      .callsArg(3)
      .withArgs(sinon.match.string, sinon.match.string, prowlMessageMatch)
      .twice();

    this.testStream.pipe(ToDue.sendToProwl(this.prowl))
    .once('finish', check(done, function() { pushSpy.verify(); }))
    .once('error', done);
  });
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var amok = require('..');
var program = require('commander');

program.version(require('../package.json').version);

program.usage('[OPTION ...] <URL | FILE>');

program.option('--debug-host <HOST>', 'specify debug host', 'localhost');
program.option('--debug-port <PORT>', 'specify debug port', 9222);

program.option('-b, --browser <BROWSER>', 'specify browser', function(value) {
  var args = process.env[value.toUpperCase() + '_FLAGS'];
  if (args) {
    args = args.split(' ');
  }

  var command = process.env[value.toUpperCase() + '_BIN'];
  if (!command) {
    command = value;
  }

  return {
    command: command,
    args: args
  };
});

program.option('--http-host <HOST>', 'specify http host', 'localhost');
program.option('--http-port <PORT>', 'specify http port', 9966);

program.option('-c, --compiler <COMPILER>', 'specify compiler');
program.option('-w, --watch <GLOB>', 'specify watch pattern');
program.option('-t, --hot [GLOB]', 'enable script hot patching');
program.option('-i, --interactive', 'enable interactive mode');
program.option('-d, --cwd <DIR>', 'change working directory', process.cwd());

program.parse(process.argv);

amok.set('cwd', program.cwd);
amok.set('port', program.debugPort);
amok.set('host', program.debugHost);

program.url = program.args.filter(function(arg) {
  return arg.match(/^(http|https|file|about:blank)/);
})[0];

program.scripts = program.args.filter(function(arg) {
  return arg.match(/(.js|.ts|.coffee)$/);
});

if (program.url) {
  amok.set('url', program.url);
} else if (program.scripts.length > 0) {
  amok.set('scripts', program.scripts.reduce(function(object, value, key) {
    object[value] = value;
    return object;
  }, {}));

  amok.use(amok.server(program.httpPort, program.httpHost));
} else {
  program.help();
}

if (program.compiler) {
  amok.use(amok.compiler(program.compiler, program.args, process.stderr));
}

if (program.watch) {
  amok.use(amok.watch(program.watch));
}

if (program.hot) {
  amok.use(amok.hotpatch(program.hot));
}

if (program.browser) {
  amok.use(amok.browser(program.browser.command, program.browser.args, process.stderr));
}

if (program.interactive) {
  amok.use(amok.repl(process.stdin, process.stdout));
} else {
  amok.use(amok.print(process.stdout));
}

amok.on('connect', function() {
  amok.client.on('error', function(error) {
    process.stderr.write(error.description + '\n');
  });
});

amok.connect(amok.get('port'), amok.get('host'));

process.on('SIGTERM', function() {
  process.exit(0);
});

process.on('SIGINT', function() {
  process.exit(0);
});

process.on('exit', function() {
  amok.close();
});
<commit_msg>Print client error message instead of description<commit_after>#!/usr/bin/env node

var amok = require('..');
var program = require('commander');

program.version(require('../package.json').version);

program.usage('[OPTION ...] <URL | FILE>');

program.option('--debug-host <HOST>', 'specify debug host', 'localhost');
program.option('--debug-port <PORT>', 'specify debug port', 9222);

program.option('-b, --browser <BROWSER>', 'specify browser', function(value) {
  var args = process.env[value.toUpperCase() + '_FLAGS'];
  if (args) {
    args = args.split(' ');
  }

  var command = process.env[value.toUpperCase() + '_BIN'];
  if (!command) {
    command = value;
  }

  return {
    command: command,
    args: args
  };
});

program.option('--http-host <HOST>', 'specify http host', 'localhost');
program.option('--http-port <PORT>', 'specify http port', 9966);

program.option('-c, --compiler <COMPILER>', 'specify compiler');
program.option('-w, --watch <GLOB>', 'specify watch pattern');
program.option('-t, --hot [GLOB]', 'enable script hot patching');
program.option('-i, --interactive', 'enable interactive mode');
program.option('-d, --cwd <DIR>', 'change working directory', process.cwd());

program.parse(process.argv);

amok.set('cwd', program.cwd);
amok.set('port', program.debugPort);
amok.set('host', program.debugHost);

program.url = program.args.filter(function(arg) {
  return arg.match(/^(http|https|file|about:blank)/);
})[0];

program.scripts = program.args.filter(function(arg) {
  return arg.match(/(.js|.ts|.coffee)$/);
});

if (program.url) {
  amok.set('url', program.url);
} else if (program.scripts.length > 0) {
  amok.set('scripts', program.scripts.reduce(function(object, value, key) {
    object[value] = value;
    return object;
  }, {}));

  amok.use(amok.server(program.httpPort, program.httpHost));
} else {
  program.help();
}

if (program.compiler) {
  amok.use(amok.compiler(program.compiler, program.args, process.stderr));
}

if (program.watch) {
  amok.use(amok.watch(program.watch));
}

if (program.hot) {
  amok.use(amok.hotpatch(program.hot));
}

if (program.browser) {
  amok.use(amok.browser(program.browser.command, program.browser.args, process.stderr));
}

if (program.interactive) {
  amok.use(amok.repl(process.stdin, process.stdout));
} else {
  amok.use(amok.print(process.stdout));
}

amok.on('connect', function() {
  amok.client.on('error', function(error) {
    process.stderr.write(error.message + '\n');
  });
});

amok.connect(amok.get('port'), amok.get('host'));

process.on('SIGTERM', function() {
  process.exit(0);
});

process.on('SIGINT', function() {
  process.exit(0);
});

process.on('exit', function() {
  amok.close();
});
<|endoftext|>


<commit_before>//#!/bin/env node

var crawl = require('..');

var args = process.argv.slice(2);
var url = args[0];

crawl(url, function (err, scrapeResult) {
	if (err) {
		throw err;
	} else {
		console.log(JSON.stringify(scrapeResult, null, 4));
	}
});
<commit_msg>Replace the #! in the bin script<commit_after>#!/usr/bin/env node

var crawl = require('..');

var args = process.argv.slice(2);
var url = args[0];

crawl(url, function (err, scrapeResult) {
	if (err) {
		throw err;
	} else {
		console.log(JSON.stringify(scrapeResult, null, 4));
	}
});
<|endoftext|>


<commit_before>var PATH = require('path'),

    /** @type Function */
    join = PATH.join,
    /** @type Function */
    relative = PATH.relative,
    /** @type Function */
    resolve = PATH.resolve.bind(null, __dirname),

    /**
     * Путь до корня проекта
     * @type String
     * @exports PRJ_ROOT
     */
    PRJ_ROOT = exports.PRJ_ROOT = resolve('../'),

    /**
     * Имя директории куда складываем библиотеки
     * @type String
     * @exports LIB_DIR
     */
    LIB_DIR = exports.LIB_DIR = 'vendor',

    /**
     * Путь до корня хранилища библиотек
     * @type String
     * @exports LIB_ROOT
     */
    LIB_ROOT = exports.LIB_ROOT = join(PRJ_ROOT, LIB_DIR),

    /**
     * Имя директории с .bem-конфигами
     * @type String
     * @exports CONF_DIR
     */
    CONF_DIR = exports.CONF_DIR = 'configs',

    /**
     * Путь до директории с .bem-конфигами
     * @type String
     * @exports CONF_ROOT
     */
    CONF_ROOT = exports.CONF_ROOT = resolve(CONF_DIR),

    /**
     * «Текущая» конфигурация
     * @exports getConf
     * @returns {Object}
     */
    getConf = exports.getConf = function() {
        return require(join(CONF_ROOT, 'current'));
    },

    /**
     * Абсолютный путь до библиотеки `lib`
     * @exports getLibPath
     * @param {String} id библиотеки
     * @returns {String}
     */
    getLibPath = exports.getLibPath = function(lib) {
        return join(LIB_ROOT, lib);
    },

    /**
     * Путь до библиотеки `lib` относительно корня проекта
     * @exports getLibRelPath
     * @param {String} id библиотеки
     * @returns {String}
     */
    getLibRelPath = exports.getLibRelPath = function(lib) {
        return relative(PRJ_ROOT, getLibPath(lib));
    };
<commit_msg>Ability to use a set of paths within Environ#getLib*Path<commit_after>var PATH = require('path'),

    /** @type Function */
    join = PATH.join,
    /** @type Function */
    relative = PATH.relative,
    /** @type Function */
    resolve = PATH.resolve.bind(null, __dirname),

    /**
     * Путь до корня проекта
     * @type String
     * @exports PRJ_ROOT
     */
    PRJ_ROOT = exports.PRJ_ROOT = resolve('../'),

    /**
     * Имя директории куда складываем библиотеки
     * @type String
     * @exports LIB_DIR
     */
    LIB_DIR = exports.LIB_DIR = 'vendor',

    /**
     * Путь до корня хранилища библиотек
     * @type String
     * @exports LIB_ROOT
     */
    LIB_ROOT = exports.LIB_ROOT = join(PRJ_ROOT, LIB_DIR),

    /**
     * Имя директории с .bem-конфигами
     * @type String
     * @exports CONF_DIR
     */
    CONF_DIR = exports.CONF_DIR = 'configs',

    /**
     * Путь до директории с .bem-конфигами
     * @type String
     * @exports CONF_ROOT
     */
    CONF_ROOT = exports.CONF_ROOT = resolve(CONF_DIR),

    /**
     * «Текущая» конфигурация
     * @exports getConf
     * @returns {Object}
     */
    getConf = exports.getConf = function() {
        return require(join(CONF_ROOT, 'current'));
    },

    /**
     * Абсолютный путь до библиотеки `lib`
     * @exports getLibPath
     * @param {String} lib id библиотеки
     * @param {String} [...path]
     * @returns {String}
     */
    getLibPath = exports.getLibPath = function() {
        var args = [].splice.call(arguments, 0);
        return join.apply(null, [LIB_ROOT].concat(args));
    },

    /**
     * Путь до библиотеки `lib` относительно корня проекта
     * @exports getLibRelPath
     * @param {String} lib id библиотеки
     * @param {String} [...path]
     * @returns {String}
     */
    getLibRelPath = exports.getLibRelPath = function() {
        return relative(PRJ_ROOT, getLibPath.apply(null, arguments));
    };
<|endoftext|>


<commit_before>#!/usr/bin/env node
/**
 * d20 Roller
 * by Mike Green
 */

/* jshint node: true */

'use strict';

var _       = require('lodash');
var path    = require('path');
var pkg     = require(path.join(__dirname, '..', 'package.json'));
var chalk   = require('chalk');
var align   = require('space-align');
var Roller  = require(path.join(__dirname, '..', 'index'));
var argv    = require('yargs')
  .usage('Usage: $0 <command> [options]')
  .command('roll', 'Roll NdN dice')
  .command('ability', 'Generate a single ability score')
  .command('abilities', 'Generate 6 random ability scores')
  .command('bonus', 'Calculate the bonus granted by an ability score')
  .option('s', {
    description: 'Show results of each die roll',
    alias: 'show-results'
  })
  .help('help')
  .version(pkg.version, 'version', 'display version information')
  .example('$0 roll 4d6+4')
  .argv;

var roller, command, args;

var resultsTemplate = _.template('[ <%= results.join(", ") %> ]');

var commands = {
  roll: function() {
    var dice, result, data;

    if (!args.length) {
      console.log(chalk.magenta('Please specify some dice to roll'));
      process.exit();
    }

    dice   = args.join('');
    result = roller.roll(dice);

    if (argv.showResults) {
      data = align([
        [ resultsTemplate({ results: result.results }), '=', result.total ]
      ])[0];
    } else {
      data = result.total;
    }

    console.log(data);
  },
  ability: function() {
    var result, data;

    result = roller.abilityScore();

    if (argv.showResults) {
      data = align([
        [ resultsTemplate({ results: result.results }), '=', result.total ]
      ])[0];
    } else {
      data = result.total;
    }

    console.log(data);
  },
  abilities: function() {

  },
  bonus: function() {

  }
};

if (!argv._.length) {
  console.log(chalk.bold(chalk.red('Please specify a command: ')) + 'See --help for usage.');
  process.exit();
}

command = argv._[0];
args    = _.rest(argv._);

if (!commands.hasOwnProperty(command)) {
  console.log(chalk.bold(chalk.red('Invalid command: ')) + 'See --help for available commands.');
  process.exit();
}

roller = new Roller();

commands[command].call(this);
<commit_msg>added bonus calculation command<commit_after>#!/usr/bin/env node
/**
 * d20 Roller
 * by Mike Green
 */

/* jshint node: true */

'use strict';

var _       = require('lodash');
var path    = require('path');
var pkg     = require(path.join(__dirname, '..', 'package.json'));
var chalk   = require('chalk');
var align   = require('space-align');
var Roller  = require(path.join(__dirname, '..', 'index'));
var argv    = require('yargs')
  .usage('Usage: $0 <command> [options]')
  .command('roll', 'Roll NdN dice')
  .command('ability', 'Generate a single ability score')
  .command('abilities', 'Generate 6 random ability scores')
  .command('bonus', 'Calculate the bonus granted by an ability score')
  .option('s', {
    description: 'Show results of each die roll',
    alias: 'show-results'
  })
  .help('help')
  .version(pkg.version, 'version', 'display version information')
  .example('$0 roll 4d6+4')
  .argv;

var roller, command, args;

var resultsTemplate = _.template('[ <%= results.join(", ") %> ]');

var commands = {
  roll: function() {
    var dice, result, data;

    if (!args.length) {
      console.log(chalk.magenta('Please specify some dice to roll'));
      process.exit(1);
    }

    dice   = args.join('');
    result = roller.roll(dice);

    if (argv.showResults) {
      data = align([
        [ resultsTemplate({ results: result.results }), '=', result.total ]
      ])[0];
    } else {
      data = result.total;
    }

    console.log(data);
  },
  ability: function() {
    var result, data;

    result = roller.abilityScore();

    if (argv.showResults) {
      data = align([
        [ resultsTemplate({ results: result.results }), '=', result.total ]
      ])[0];
    } else {
      data = result.total;
    }

    console.log(data);
  },
  abilities: function() {
    var result, data;

    result = roller.abilityScores();

    if (argv.showResults) {
      data = align(_.map(result, function(res) {
        return [ resultsTemplate({ results: res.results }), '=', res.total ];
      }));
    } else {
      data = _.map(result, 'total');
    }

    _.forEach(data, function(score) {
      console.log(score);
    });
  },
  bonus: function() {
    var result;

    if (!args.length) {
      console.log(chalk.magenta('Please specify a score'));
      process.exit(1);
    }

    result = roller.bonus(args[0])

    if (result > -1) {
      console.log('+' + result);
    } else {
      console.log(result);
    }
  }
};

if (!argv._.length) {
  console.log(chalk.bold.red('Please specify a command: ') + 'See --help for usage.');
  process.exit();
}

command = argv._[0];
args    = _.rest(argv._);

if (!commands.hasOwnProperty(command)) {
  console.log(chalk.bold(chalk.red('Invalid command: ')) + 'See --help for available commands.');
  process.exit();
}

roller = new Roller();

commands[command].call(this);
<|endoftext|>


<commit_before>#!/usr/bin/env node

'use strict';

const fs = require('fs');
const path = require('path');
const shelljs = require('shelljs');
const program = require('commander');
const packager = require('/usr/local/lib/node_modules/electron-packager');

program
    .option('run', 'Run app')
    .option('build', 'Build app')
    .option('debug', 'Debug app')
    .parse(process.argv);

let folder = '/Users/lixinliang/GitHub/nina/';

if (program.run) {
    shelljs.cd(folder);
    shelljs.exec('electron . --env=dev');
}

if (program.build) {
    let dest = '/Users/lixinliang/Desktop/';
    packager({
        'arch' : 'x64',
        'platform' : 'darwin',
        'dir' : `${ folder }`,
        'out' : `${ dest }`,
        'icon' : `${ path.join(folder, './icon/logo.icns') }`,
        'ignore' : ['.DS_Store'],
        'overwrite' : true,
        'electron-version' : '1.4.12',
        'app-copyright' : 'lixinliang',
        'version-string':{
            'ProductName' : 'Nina',
            'CompanyName' : 'lixinliang',
            'InternalName' : 'NinaDesktop',
            'FileDescription' : 'NinaDesktop',
            'OriginalFilename' : 'NinaDesktop',
        },
    }, ( err ) => {
        shelljs.exec(`open ${ dest }`);
    });
    // shelljs.cd(folder);
    // shelljs.exec(`electron-packager . Nina --platform=darwin --arch=x64 --overwrite --out=${ dest } --icon=./icon/logo.png --ignore='(.DS_Store)' --electron-version 1.4.12 && open ${ dest }`);
}

if (program.debug) {
    shelljs.cd(folder);
    shelljs.exec(`osascript -e 'tell application "Terminal" to do script "node-inspector"'`);
    shelljs.exec(`open -a "Google Chrome" "http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858"`);
    shelljs.exec('electron --debug-brk=5858 . --env=debug');
}
<commit_msg>update<commit_after>#!/usr/bin/env node

'use strict';

const fs = require('fs');
const path = require('path');
const shelljs = require('shelljs');
const program = require('commander');
const packager = require('/usr/local/lib/node_modules/electron-packager');

program
    .option('run', 'Run app')
    .option('build', 'Build app')
    .option('debug', 'Debug app')
    .parse(process.argv);

function main () {

    let folder = '/Users/lixinliang/GitHub/nina/';

    if (program.run) {
        shelljs.cd(folder);
        shelljs.exec('electron . --env=dev');
        return;
    }

    if (program.build) {
        let dest = '/Users/lixinliang/Desktop/';
        packager({
            'arch' : 'x64',
            'platform' : 'darwin',
            'dir' : `${ folder }`,
            'out' : `${ dest }`,
            'icon' : `${ path.join(folder, './icon/logo.icns') }`,
            'ignore' : ['.DS_Store'],
            'overwrite' : true,
            'electronVersion' : '1.4.12',
            'appCopyright' : 'Copyright (c) 2017 lixinliang',
            'version-string':{
                'ProductName' : 'Nina',
                'InternalName' : 'Nina',
                'OriginalFilename' : 'Nina',
                'CompanyName' : 'lixinliang',
                'FileDescription' : 'Nina Tool Desktop',
            },
        }, ( err ) => {
            if (err) {
                console.log(err);
                return;
            }
            shelljs.exec(`open ${ dest }`);
        });
        return;
        // shelljs.cd(folder);
        // shelljs.exec(`electron-packager . Nina --platform=darwin --arch=x64 --overwrite --out=${ dest } --icon=./icon/logo.png --ignore='(.DS_Store)' --electron-version 1.4.12 && open ${ dest }`);
    }

    if (program.debug) {
        shelljs.cd(folder);
        shelljs.exec(`osascript -e 'tell application "Terminal" to do script "node-inspector"'`);
        shelljs.exec(`open -a "Google Chrome" "http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858"`);
        shelljs.exec('electron --debug-brk=5858 . --env=debug');
        return;
    }

    shelljs.exec('nina -h');
}

main();
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import {
    MainLogo,
    UpgradeButton,
    useUser,
    Hamburger,
    TopNavbar,
    TopNavbarLink,
    FloatingButton
} from 'react-components';
import { c } from 'ttag';

const PrivateHeader = ({ title, url, inSettings, onCreateEvent, expanded, onToggleExpand, isNarrow }) => {
    const [{ isFree }] = useUser();
    return (

            <MainLogo url={url} className="nomobile" />
            <Hamburger expanded={expanded} onToggle={onToggleExpand} />
            {title && isNarrow ? {title} : null}
            <TopNavbar>
                {isFree || isNarrow ? null : <UpgradeButton external={true} />}
                {isNarrow && !inSettings ? null : (
                    <TopNavbarLink
                        className="nomobile"
                        to={url}
                        icon="calendar"
                        text={c('Title').t`Calendar`}
                        aria-current={!inSettings}
                    />
                )}
                {isNarrow && inSettings ? null : (
                    <TopNavbarLink
                        to={`${url}/settings/general`}
                        icon="settings-master"
                        text={c('Title').t`Settings`}
                        aria-current={inSettings}
                    />
                )}
            </TopNavbar>
            {isNarrow && !inSettings && onCreateEvent ? (
                <FloatingButton onClick={() => onCreateEvent()} icon="plus" />
            ) : null}

    );
};

PrivateHeader.propTypes = {
    isNarrow: PropTypes.bool,
    expanded: PropTypes.bool,
    onToggleExpand: PropTypes.func,
    onCreateEvent: PropTypes.func,
    inSettings: PropTypes.bool,
    url: PropTypes.string,
    title: PropTypes.string
};

export default PrivateHeader;
<commit_msg>Hotfix header - display upgrade for free mail user<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import {
    MainLogo,
    UpgradeButton,
    useUser,
    Hamburger,
    TopNavbar,
    TopNavbarLink,
    FloatingButton
} from 'react-components';
import { c } from 'ttag';

const PrivateHeader = ({ title, url, inSettings, onCreateEvent, expanded, onToggleExpand, isNarrow }) => {
    const [{ hasPaidMail }] = useUser();
    return (

            <MainLogo url={url} className="nomobile" />
            <Hamburger expanded={expanded} onToggle={onToggleExpand} />
            {title && isNarrow ? {title} : null}
            <TopNavbar>
                {hasPaidMail || isNarrow ? null : <UpgradeButton external={true} />}
                {isNarrow && !inSettings ? null : (
                    <TopNavbarLink
                        className="nomobile"
                        to={url}
                        icon="calendar"
                        text={c('Title').t`Calendar`}
                        aria-current={!inSettings}
                    />
                )}
                {isNarrow && inSettings ? null : (
                    <TopNavbarLink
                        to={`${url}/settings/general`}
                        icon="settings-master"
                        text={c('Title').t`Settings`}
                        aria-current={inSettings}
                    />
                )}
            </TopNavbar>
            {isNarrow && !inSettings && onCreateEvent ? (
                <FloatingButton onClick={() => onCreateEvent()} icon="plus" />
            ) : null}

    );
};

PrivateHeader.propTypes = {
    isNarrow: PropTypes.bool,
    expanded: PropTypes.bool,
    onToggleExpand: PropTypes.func,
    onCreateEvent: PropTypes.func,
    inSettings: PropTypes.bool,
    url: PropTypes.string,
    title: PropTypes.string
};

export default PrivateHeader;
<|endoftext|>


<commit_before>/* global process, require, __dirname, console */
/* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */
/**
 *
 * Simple NodeJS/Express Server
 *
 * ## Installation
 * ---------------------------
 * > touch .env
 * npm i -D dotenv express express-livereload compression body-parser
 *
 */
const path = require('path');
const cors = require('cors');
const helmet = require('helmet');
const express = require('express');
const bodyParser = require('body-parser');
const compression = require('compression');

require('dotenv').load({
  path: path.join(__dirname, '.env')
});

const config = {
  port: process.env.PORT || 9080,
  debug: process.env.DEBUG || false,
  public: path.join(__dirname, 'public')
};

const server = express();
server.disable('x-powered-by');
server.use(cors());
server.use(helmet());
server.use(compression());
server.use(bodyParser.json({ limit: '50mb' }));

// Express server is used to serve static ressouces
server.use('/', express.static(config.public));

server.listen(config.port, () => {
  if (!config.debug) return;
  const msg = `Server is running under http://localhost:${port}`;
  console.log(`\x1b[32m${msg}\x1b[39m`);
});
<commit_msg>Update server.js<commit_after>/* global process, require, __dirname, console */
/* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */
/**
 *
 * Simple NodeJS/Express Server
 *
 * ## Installation
 * ---------------------------
 *
 * `echo "PORT=9080" > .env`
 * npm i -D dotenv express cors helmet body-parser compression
 *
 */
const path = require('path');
const cors = require('cors');
const helmet = require('helmet');
const express = require('express');
const bodyParser = require('body-parser');
const compression = require('compression');

require('dotenv').load({
  path: path.join(__dirname, '.env')
});

const config = {
  port: process.env.PORT || 9080,
  debug: process.env.DEBUG || false,
  public: path.join(__dirname, 'public')
};

const server = express();
server.disable('x-powered-by');
server.use(cors());
server.use(helmet());
server.use(compression());
server.use(bodyParser.json({ limit: '50mb' }));

// Express server is used to serve static ressouces
server.use('/', express.static(config.public));

server.listen(config.port, () => {
  if (!config.debug) return;
  const msg = `Server is running under http://localhost:${port}`;
  console.log(`\x1b[32m${msg}\x1b[39m`);
});
<|endoftext|>


<commit_before>/**
 * @date 2015-01-06
 * @author jialin.hou@raidtalk.com
 */
;(function (window, document, undefined) {
  var gg = {};
  gg.$ = function (selector, element) {
    if (!element) {
      element = document;
    }

    try {
      return element.querySelector(selector);
    } catch (e) {

    }
  };

  gg.$$ = function (selector, element) {
    if (!element) {
      element = document;
    }

    try {
      return element.querySelectorAll(selector);
    } catch (e) {

    }
  };

  gg.id = function (selector, element) {
    if (!element) {
      element = document;
    }

    return element.getElementById(selector);
  };

  gg.getChildren = function (selector, element) {
    var parent = this.$(selector, element);
    return parent.childNodes;
  };

  /**
   * DOM
   */
  gg.dom = gg.dom || {};
  gg.dom.isElement = function (element) {
    return element && element.nodeType === Node.ELEMENT_NODE;
  }

  gg.dom.firstChild = function (element) {
    return isElement(element) ? element.firstChild : null;
  }

  /**
   * tools (include extend grammer)
   * @type {[type]}
   */
  gg.util = gg.util || {};
  gg.util.isArray = function (obj) {
    return Object.protype.toString.call(obj) === '[Object Array]';
  };

  gg.util.makeArray = function (obj) {
    return Array.prototype.slice.call(obj);
  }

  gg.util.trim = function (string) {
    return string.replace(/^\s|\s$/g, '');
  };

  gg.util.extend = function () {

  };

  window.gg = gg;
})(window, document);<commit_msg>update<commit_after>/**
 * @date 2015-01-06
 * @author jialin.hou@raidtalk.com
 */
;(function (window, document, undefined) {
  var gg = {};
  gg.$ = function (selector, element) {
    if (!element) {
      element = document;
    }

    try {
      return element.querySelector(selector);
    } catch (e) {

    }
  };

  gg.$$ = function (selector, element) {
    if (!element) {
      element = document;
    }

    try {
      return element.querySelectorAll(selector);
    } catch (e) {

    }
  };

  gg.id = function (selector, element) {
    if (!element) {
      element = document;
    }

    return element.getElementById(selector);
  };

  gg.getChildren = function (selector, element) {
    var parent = this.$(selector, element);
    return parent.childNodes;
  };

  /**
   * DOM
   */
  gg.dom = gg.dom || {};
  gg.dom.mode = function () {
    return document.compatMode;
  };

  gg.dom.activeElement = function () {
    return document.activeElement;
  };

  gg.dom.global = function () {
    return document.defaultView;
  };

  gg.dom.doc = function () {
    return document.body.ownerElement;
  };

  gg.dom.isElement = function (element) {
    return element && element.nodeType === Node.ELEMENT_NODE;
  };

  gg.dom.attributes = function (element) {
    var attributes = [],
      attrs = element.attributes;

    for (var i = 0, len = attrs.length; i < len; i++) {
      attributes.push(attrs[i]);
    }

    reutrn attributes;
  };

  gg.dom.prop = function (element, attr, value) {

    // get prop
    if (undefined === value) {
      return element.getAttribute(attr);
    } else {
      if (value == '') {
        element.removeAttribute(attr);
      } else {
        element.setAttribute(attr, value);
      }
    }
  };

  gg.dom.addClass = function (element, value) {
    if (value === undefined) {
      return;
    }

    if (element.classList) {
      element.classList.add(value);
    } else {
      var className = this.prop('class');
      if (!className.indexOf(value)) {
        className += value;
        this.prop(element, 'class', className);
      }
    }
  };

  gg.dom.firstChild = function (element) {
    return isElement(element) ? element.firstChild : null;
  };

  gg.dom.isEqual = function (elemA, elemB) {
    return elemA.isEqualNode(elemB);
  };

  /**
   * tools (include extend grammer)
   * @type {[type]}
   */
  gg.util = gg.util || {};
  gg.util.isArray = function (obj) {
    return Object.protype.toString.call(obj) === '[Object Array]';
  };

  gg.util.makeArray = function (obj) {
    return Array.prototype.slice.call(obj);
  }

  gg.util.trim = function (string) {
    return string.replace(/^\s|\s$/g, '');
  };

  gg.util.extend = function () {

  };

  window.gg = gg;
})(window, document);<|endoftext|>