<commit_before>/**
 * Lite Publisher CMS
 *
 *  copyright 2010 - 2016 Vladimir Yushko http://litepublisher.com/ http://litepublisher.ru/
 *  license   https://github.com/litepubl/cms/blob/master/LICENSE.txt MIT
 *  link      https://github.com/litepubl\cms
 *  version   7.04
  */

(function($, document, window) {
  'use strict';

  window.litepubl = {
    guid: $.now(),
    tml: {}, //namespace for templates
    adminpanel: false,
    is_adminpanel: function() {
      if (litepubl.adminpanel !== false) return litepubl.adminpanel;
      return litepubl.adminpanel = litepubl.is_admin_url(location.href);
    },

    is_admin_url: function(url) {
      url = url.toLowerCase();
      var i = url.indexOf('://');
      if (i >= 0) url = url.substring(i + 4);
      var path = url.split('/');
      if ((path.length <= 2) || (path[1] != 'admin') || (path[2] == '')) return 0;
      return /^(login|logout|password|reguser)$/.test(path[2]) ? 0 : 1;
    },

    user: 0,
    getuser: function() {
      var self = litepubl;
      if (self.user) return self.user;
      return self.user = {
        id: parseInt($.cookie('litepubl_user_id')),
        pass: $.cookie('litepubl_user'),
        regservice: $.cookie('litepubl_regservice')
      };
    },

    //forward declaration for future plugins as yandex metrika or google analitik
    stat: function(name, param) {},

    // current image galery
    openimage: function(image) {
      //image = {url, title, description...}
      // nothing abstract, must be assigned later
    },

    linkimage: function(link) {
      var file = link.data("file");
      var midle = link.data("midle");

      if (midle && ($(window).width() <= 768)) {
        var image = {
          url: midle.link,
          width: parseInt(midle.width),
          height: parseInt(midle.height)
        };
      } else {
        var image = {
          url: link.attr("href"),
          width: parseInt(file.width),
          height: parseInt(file.height)
        };
      }

      image.title = link.attr("title");
      image.description = $("img", link).attr("alt");

      return this.openimage(image);
    }

  };

  window.dump = function(obj) {
    alert(JSON.stringify(obj));
  };

  //cookies
  window.get_cookie = function(name) {
    return $.cookie(name);
  };

  window.set_cookie = function(name, value, expires) {
    $.cookie(name, value, {
      path: '/',
      expires: expires ? expires : 3650,
      secure: "secure" in ltoptions ? ltoptions.secure : false
    });
  };

  window.$ready = function(fn) {
    $(document).ready(fn);
  };

  window.erralert = function(e) {
    alert('error ' + e.message);
  };

}(jQuery, document, window));<commit_msg>before js erralert<commit_after>/**
 * Lite Publisher CMS
 *
 *  copyright 2010 - 2016 Vladimir Yushko http://litepublisher.com/ http://litepublisher.ru/
 *  license   https://github.com/litepubl/cms/blob/master/LICENSE.txt MIT
 *  link      https://github.com/litepubl\cms
 *  version   7.04
  */

(function($, window) {
  'use strict';

  window.litepubl = {
    guid: $.now(),
    tml: {}, //namespace for templates
    adminpanel: false,
    is_adminpanel: function() {
      if (litepubl.adminpanel !== false) return litepubl.adminpanel;
      return litepubl.adminpanel = litepubl.is_admin_url(location.href);
    },

    is_admin_url: function(url) {
      url = url.toLowerCase();
      var i = url.indexOf('://');
      if (i >= 0) url = url.substring(i + 4);
      var path = url.split('/');
      if ((path.length <= 2) || (path[1] != 'admin') || (path[2] == '')) return 0;
      return /^(login|logout|password|reguser)$/.test(path[2]) ? 0 : 1;
    },

    user: 0,
    getuser: function() {
      var self = litepubl;
      if (self.user) return self.user;
      return self.user = {
        id: parseInt($.cookie('litepubl_user_id')),
        pass: $.cookie('litepubl_user'),
        regservice: $.cookie('litepubl_regservice')
      };
    },

    //forward declaration for future plugins as yandex metrika or google analitik
    stat: function(name, param) {},

    // current image galery
    openimage: function(image) {
      //image = {url, title, description...}
      // nothing abstract, must be assigned later
    },

    linkimage: function(link) {
      var file = link.data("file");
      var midle = link.data("midle");

      if (midle && ($(window).width() <= 768)) {
        var image = {
          url: midle.link,
          width: parseInt(midle.width),
          height: parseInt(midle.height)
        };
      } else {
        var image = {
          url: link.attr("href"),
          width: parseInt(file.width),
          height: parseInt(file.height)
        };
      }

      image.title = link.attr("title");
      image.description = $("img", link).attr("alt");

      return this.openimage(image);
    }

  };

  window.dump = function(obj) {
    alert(JSON.stringify(obj));
  };

  //cookies
  window.get_cookie = function(name) {
    return $.cookie(name);
  };

  window.set_cookie = function(name, value, expires) {
    $.cookie(name, value, {
      path: '/',
      expires: expires ? expires : 3650,
      secure: "secure" in ltoptions ? ltoptions.secure : false
    });
  };

  window.erralert = function(e) {
    alert('error ' + e.message);
  };

}(jQuery, window));<|endoftext|>


<commit_before>export { Base, Root };

/**
 * A pool for recycling (re-using) objects.
 *
 * It could be used to improve performance by reducing memory re-allocation.
 *
 *
 * @member {object[]} issuedObjectArray
 *   All objects returned by .get_or_create_by() will be recorded in this array.
 *
 * @member {Set} sessionKeptObjectSet
 *   Temporary object list for speeding up searching of whether kept (i.e. not recycled) an object.
 *
 * @member {object[]} movingObjectArray
 *   Temporary object list for moving kept (i.e. not recycled) objects to the parent session.
 *
 * @member {object[]} recycledObjectArray
 *   All objects passed into .recycle() will be recorded in this array.
 *
 * @member {Set} recycledObjectSet
 *   Same content as .recycledObjectArray for avoiding recycle object duplicately.
 *
 */
let Base = ( ParentClass = Object ) => class Base extends ParentClass {

  /**
   * @param {Class} objectClass
   *   The class for all newly created objects.
   *
   * @param {function} pfn_SetAsConstructor_ReturnObject
   *   A function set contents like its constructor and return an object. Before .get_or_create_by() returns a recycled object,
   * its .pfnSetAsConstructor() method will be called to re-initilaize it. Its return value will be the final returned object.
   */
  constructor( objectClass, pfn_SetAsConstructor_ReturnObject ) {
    this.objectClass = objectClass;
    this.pfn_SetAsConstructor_ReturnObject = pfn_SetAsConstructor_ReturnObject;

    this.issuedObjectArray = new Array();
    this.sessionKeptObjectSet = new Set(); // For reducing memory re-allocation.
    this.movingObjectArray = new Array(); // For reducing memory re-allocation.

//!!! ...unfinished... (2022/06/21)
// should be a MultiLayerMap whose leaf node is an Array object.
// All objects with the same specification will be recycled into the same Array.
// This is more useful for Float32Array because of reducing more memory re-allocation.
//

    this.recycledObjectArray = new Array(); // For fetching efficiently (without creating iterator).
    this.recycledObjectSet = new Set(); // For checking object recycled multiple times.
  }

  /**
   * @return {number} Return the quantity of issued objects.
   */
  get issuedCount() {
    return this.issuedObjectArray.length;
  }

  /**
   * @return {number} Return the quantity of recycled objects.
   */
  get recycledCount() {
    return this.recycledObjectSet.length;
  }

  /**
   *
   * @return {object}
   *   An obeject which is an instance of this.ObjectClass. If it is newly created, all RestArgs will be passed into its constructor.
   * If it is re-used (i.e. from recycled) object, all RestArgs will be passed into its .pfnSetAsConstructor() method.
   */
  get_or_create_by( ...restArgs ) {
    let returnedObject;
    if ( this.recycledObjectArray.length > 0 ) {
      let candicatedObject = this.recycledObjectArray.pop();
      this.recycledObjectSet.delete( candicatedObject ); // Removed from set.
      returnedObject =this.pfn_SetAsConstructor_ReturnObject.apply( candicatedObject, restArgs );
    } else {
      returnedObject = new ( this.objectClass )( ...restArgs );
    }
    return returnedObject;
  }

  /**
   * @param {Object} objectToBeRecycled
   *   The object (which should be an instance of this.ObjectClass) to be recycled.
   *
   * @return {boolean}
   *   Return true, if the object is recycled. Return false, if the object has already been recycled.
   */
  recycle( objectToBeRecycled ) {
    if ( !this.recycledObjectSet.has( objectToBeRecycled ) ) { // Avoiding duplicately.
      this.recycledObjectSet.add( objectToBeRecycled );
      this.recycledObjectArray.push( objectToBeRecycled );
      return true;
    }
    return false;
  }

  /**
   * Discard all recycled objects. (Note: The issued objects list are not influenced.)
   */
  recycledClear() {
    this.recycledObjectArray.length = 0;
    this.recycledObjectSet.clear();
  }

  /**
   * @return {iterator)
   *   Return an iterator object that contains all recycled objects.
   */
  * recycledValues() {
    yield* this.recycledObjectArray.values();
  }

  /**
   * Start a auto-recycle session. This method will append a SESSION_BORDER_MARK to .issuedObjectArray
   *
   * @param {Base} this
   *   The pool for handling the objects issuing/recycling.
   */
  static session_push() {
    const SESSION_BORDER_MARK = this; // Use an object which is not possible be listed in the array as an session-border-mark.
    this.issuedObjectArray.push( SESSION_BORDER_MARK );
  }

  /**
   * End a auto-recycle session. This method will pop all objects from .issuedObjectArrayappend until encountering SESSION_BORDER_MARK.
   *
   *   - If the popped objects are not listed in keptObjectArray, they will be recycled.
   *
   *   - If the popped objects are listed in keptObjectArray, they will not be recycled and will become belonging to the parent
   *       session.
   *
   * @param {Base} this
   *   The pool for handling the objects issuing/recycling.
   *
   * @param {object|object[]} keptObjectOrArray
   *   An object or an object array. If the object(s) is not null, they will be kept (i.e. not be recycled) and be moved to parent session.
   */
  static session_pop( keptObjectOrArray ) {
    const SESSION_BORDER_MARK = this; // Use an object which is not possible be listed in the array as an session-border-mark.

    // 1. Prepare object list to be kept (i.e. not be recycled).
    {
      this.sessionKeptObjectSet.clear();
      if ( keptObjectOrArray ) {
        if ( keptObjectOrArray instanceof Array ) { // 1.1 An array of objects.
          for ( let i = 0; i < keptObjectOrArray.length; ++i ) {
            let keptObject = keptObjectOrArray[ i ];
            if ( keptObject )
              this.sessionKeptObjectSet.add( keptObject );
          }
        } else if ( keptObjectOrArray instanceof Object ) { // 1.2 An single object.
          this.sessionKeptObjectSet.add( keptObjectOrArray );
        }
      }
    }

    // 2. Recycle the last session's issued all objects (except objects should be kept).
    this.movingObjectArray.length = 0;
    while ( this.issuedObjectArray.length > 0 ) {
      let issuedObject = this.issuedObjectArray.pop();
      if ( issuedObject == SESSION_BORDER_MARK )
        break; // All objects of the last session have been popped.

      if ( this.sessionKeptObjectSet.has( issuedObject ) ) { // Found an object which should not be recycled.
        this.movingObjectArray.push( issuedObject ); // Collect it temporarily for moving it to parent session later.
      } else {
        this.recycle( issuedObject ); // Otherwise, recycle it.
      }
    }

    // 3. Re-push the objects which should be kept to the parent session.
    while ( this.movingObjectArray.length > 0 ) {
      let movingObject = this.movingObjectArray.pop();
      this.issuedObjectArray.push( movingObject ); // Moved (i.e. belonged) to parent session.
    }

    // 4. Reduce memory footprint.
    this.sessionKeptObjectSet.clear();
  }

  /**
   * Create a session. Call th function. End the session and recycle all issued objects (except the returned objects of the function).
   *
   *
   * @param {function} pfn
   *   A function to be called. It is viewed as a session. All objects the function got by .get_or_create_by() will be recycled
   * except the objects of the function's returned value (an object or an object array).
   *
   * @return {any}
   *   Return anything which the pfn() returned. If the returned value is an object or an array of object, these objects (if they
   * inside .issuedObjectArray) will be kept (i.e. not be recycled) and become belonging to the parent session.
   */
  sessionCall( pfn ) {
    Base.session_push.call( this );
    let returnedValue;
    try {
      returnedValue = pfn();
    } finally {
      Base.session_pop.call( this, returnedValue );
    }
  }

}

/**
 * Almost the same as Pool.Base class except its parent class is fixed to Object. In other words, caller can not
 * specify the parent class of Pool.Root (so it is named "Root" which can not have parent class).
 */
class Root extends Base() {
}

<commit_msg>Update Pool_Base.js<commit_after>export { Base, Root };

/**
 * A pool for recycling (re-using) objects.
 *
 * It could be used to improve performance by reducing memory re-allocation.
 *
 *
 * @member {object[]} issuedObjectArray
 *   All objects returned by .get_or_create_by() will be recorded in this array.
 *
 * @member {Set} sessionKeptObjectSet
 *   Temporary object list for speeding up searching of whether kept (i.e. not recycled) an object.
 *
 * @member {object[]} movingObjectArray
 *   Temporary object list for moving kept (i.e. not recycled) objects to the parent session.
 *
 * @member {object[]} recycledObjectArray
 *   All objects passed into .recycle() will be recorded in this array.
 *
 * @member {Set} recycledObjectSet
 *   Same content as .recycledObjectArray for avoiding recycle object duplicately.
 *
 */
let Base = ( ParentClass = Object ) => class Base extends ParentClass {

  /**
   * @param {Class} objectClass
   *   The class for all newly created objects.
   *
   * @param {function} pfn_SetAsConstructor_ReturnObject
   *   A function set contents like its constructor and return an object. Before .get_or_create_by() returns a recycled object,
   * its .pfnSetAsConstructor() method will be called to re-initilaize it. Its return value will be the final returned object.
   */
  constructor( objectClass, pfn_SetAsConstructor_ReturnObject ) {
    this.objectClass = objectClass;
    this.pfn_SetAsConstructor_ReturnObject = pfn_SetAsConstructor_ReturnObject;

    this.issuedObjectArray = new Array();
    this.sessionKeptObjectSet = new Set(); // For reducing memory re-allocation.
    this.movingObjectArray = new Array(); // For reducing memory re-allocation.

//!!! ...unfinished... (2022/06/21)
// should be a MultiLayerMap whose leaf node is an Array object.
// All objects with the same specification will be recycled into the same Array.
// This is more useful for Float32Array because of reducing more memory re-allocation.
//

    this.recycledObjectArray = new Array(); // For fetching efficiently (without creating iterator).
    this.recycledObjectSet = new Set(); // For checking object recycled multiple times.
  }

  /**
   * @return {number} Return the quantity of issued objects.
   */
  get issuedCount() {
    return this.issuedObjectArray.length;
  }

  /**
   * @return {number} Return the quantity of recycled objects.
   */
  get recycledCount() {
    return this.recycledObjectSet.length;
  }

  /**
   *
   * @return {object}
   *   An obeject which is an instance of this.ObjectClass. If it is newly created, all RestArgs will be passed into its constructor.
   * If it is re-used (i.e. from recycled) object, all RestArgs will be passed into its .pfnSetAsConstructor() method.
   */
  get_or_create_by( ...restArgs ) {
    let returnedObject;
    if ( this.recycledObjectArray.length > 0 ) {
      let candicatedObject = this.recycledObjectArray.pop();
      this.recycledObjectSet.delete( candicatedObject ); // Removed from set.
      returnedObject =this.pfn_SetAsConstructor_ReturnObject.apply( candicatedObject, restArgs );
    } else {
      returnedObject = new ( this.objectClass )( ...restArgs );
    }
    return returnedObject;
  }

  /**
   * @param {Object} objectToBeRecycled
   *   The object (which should be an instance of this.ObjectClass) to be recycled.
   *
   * @return {boolean}
   *   Return true, if the object is recycled. Return false, if the object has already been recycled.
   */
  recycle( objectToBeRecycled ) {

//!!! ...unfinished... (2022/06/22)
// What if the object to be recycled is inside .issuedObjectArray ?

    if ( !this.recycledObjectSet.has( objectToBeRecycled ) ) { // Avoiding duplicately.
      this.recycledObjectSet.add( objectToBeRecycled );
      this.recycledObjectArray.push( objectToBeRecycled );
      return true;
    }
    return false;
  }

  /**
   * Discard all recycled objects. (Note: The issued objects list are not influenced.)
   */
  recycledClear() {
    this.recycledObjectArray.length = 0;
    this.recycledObjectSet.clear();
  }

  /**
   * @return {iterator)
   *   Return an iterator object that contains all recycled objects.
   */
  * recycledValues() {
    yield* this.recycledObjectArray.values();
  }

  /**
   * Start a auto-recycle session. This method will append a SESSION_BORDER_MARK to .issuedObjectArray
   *
   * @param {Base} this
   *   The pool for handling the objects issuing/recycling.
   */
  static session_push() {
    const SESSION_BORDER_MARK = this; // Use an object which is not possible be listed in the array as an session-border-mark.
    this.issuedObjectArray.push( SESSION_BORDER_MARK );
  }

  /**
   * End a auto-recycle session. This method will pop all objects from .issuedObjectArrayappend until encountering SESSION_BORDER_MARK.
   *
   *   - If the popped objects are not listed in keptObjectArray, they will be recycled.
   *
   *   - If the popped objects are listed in keptObjectArray, they will not be recycled and will become belonging to the parent
   *       session.
   *
   * @param {Base} this
   *   The pool for handling the objects issuing/recycling.
   *
   * @param {object|object[]} keptObjectOrArray
   *   An object or an object array. If the object(s) is not null, they will be kept (i.e. not be recycled) and be moved to parent session.
   */
  static session_pop( keptObjectOrArray ) {
    const SESSION_BORDER_MARK = this; // Use an object which is not possible be listed in the array as an session-border-mark.

    // 1. Prepare object list to be kept (i.e. not be recycled).
    {
      this.sessionKeptObjectSet.clear();
      if ( keptObjectOrArray ) {
        if ( keptObjectOrArray instanceof Array ) { // 1.1 An array of objects.
          for ( let i = 0; i < keptObjectOrArray.length; ++i ) {
            let keptObject = keptObjectOrArray[ i ];
            if ( keptObject )
              this.sessionKeptObjectSet.add( keptObject );
          }
        } else if ( keptObjectOrArray instanceof Object ) { // 1.2 An single object.
          this.sessionKeptObjectSet.add( keptObjectOrArray );
        }
      }
    }

    // 2. Recycle the last session's issued all objects (except objects should be kept).
    this.movingObjectArray.length = 0;
    while ( this.issuedObjectArray.length > 0 ) {
      let issuedObject = this.issuedObjectArray.pop();
      if ( issuedObject == SESSION_BORDER_MARK )
        break; // All objects of the last session have been popped.

      if ( this.sessionKeptObjectSet.has( issuedObject ) ) { // Found an object which should not be recycled.
        this.movingObjectArray.push( issuedObject ); // Collect it temporarily for moving it to parent session later.
      } else {
        this.recycle( issuedObject ); // Otherwise, recycle it.
      }
    }

    // 3. Re-push the objects which should be kept to the parent session.
    while ( this.movingObjectArray.length > 0 ) {
      let movingObject = this.movingObjectArray.pop();
      this.issuedObjectArray.push( movingObject ); // Moved (i.e. belonged) to parent session.
    }

    // 4. Reduce memory footprint.
    this.sessionKeptObjectSet.clear();
  }

  /**
   * Create a session. Call th function. End the session and recycle all issued objects (except the returned objects of the function).
   *
   *
   * @param {function} pfn
   *   A function to be called. It is viewed as a session. All objects the function got by .get_or_create_by() will be recycled
   * except the objects of the function's returned value (an object or an object array).
   *
   * @return {any}
   *   Return anything which the pfn() returned. If the returned value is an object or an array of object, these objects (if they
   * inside .issuedObjectArray) will be kept (i.e. not be recycled) and become belonging to the parent session.
   */
  sessionCall( pfn ) {
    Base.session_push.call( this );
    let returnedValue;
    try {
      returnedValue = pfn();
    } finally {
      Base.session_pop.call( this, returnedValue );
    }
  }

}

/**
 * Almost the same as Pool.Base class except its parent class is fixed to Object. In other words, caller can not
 * specify the parent class of Pool.Root (so it is named "Root" which can not have parent class).
 */
class Root extends Base() {
}

<|endoftext|>


<commit_before>import React from 'react';

import app from '../Firebase';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import Form from 'react-bootstrap/Form';

import { COLLECTION_TRIPS } from '../../constants/database.js';
import { formatTripData } from '../Utils/filter-input.js';

const db = app.firestore();

/**
 * Returns a Form.Control element with input type 'text' and other props
 * specified by the function parameters.
 *
 * @param {string} defaultText Default text value in the form input.
 * @param {React.RefObject} ref Ref attached to the value inputted in the form.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Control element.
 */
function createTextFormControl(defaultText, ref, isAddTripForm) {
  if (isAddTripForm) {
    return (
      <Form.Control
        type="text"
        placeholder={defaultText}
        ref={ref}
      />
    );
  }
  return (
    <Form.Control
      type="text"
      defaultValue={defaultText}
      ref={ref}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'date' and other props
 * specified by the function parameters.
 *
 * @param {string} defaultDate Default ISO date string in the form input.
 * @param {React.RefObject} refArr The list of refs attached to the emails
 *     inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createDateFormControl(defaultDate, ref) {
  return (
    <Form.Control
      type="date"
      ref={ref}
      defaultValue={defaultDate}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'email' and other props
 * specified by the function parameters.
 *
 * @param {string} defaultEmail Default text value in the form input.
 * @param {React.RefObject} ref Ref attached to the value inputted in the form.
 * @param {number} idx Index of the email Form.Control used for key prop.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Control element.
 */
function createEmailFormControl(defaultEmail, ref, idx, isAddTripForm) {
  if (isAddTripForm) {
    return (
      <Form.Control
        type="email"
        placeholder={defaultEmail}
        ref={ref}
        key={idx}
      />
    );
  }
  return (
    <Form.Control
      type="email"
      defaultValue={defaultEmail}
      ref={ref}
      key={idx}
    />
  );
}

/**
 * Returns multiple Form.Control elements with input type 'email' and other
 * props specified by the function parameters.
 *
 * One is added to the index of the emails show in order to display all
 * collaborators except the current user.
 *
 * TODO(Issue #67): Email verification before submitting the form.
 *
 * TODO(Issue #72): More intuitive remove collaborator when !`isAddTripForm`.
 *
 * @param {!Array<string>} defaultEmailArr Array of the emails to be displayed
 *     in the default form fields.
 * @param {!Array<React.RefObject>} refArr Array of refs attached to the
 *     emails inputted in the form.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Control elements.
 */
function createMultiFormControl(defaultEmailArr, refArr, isAddTripForm) {
  return (
    <>
      {refArr.map((ref, idx) =>
        createEmailFormControl(defaultEmailArr[idx + 1],
                                 ref, idx, isAddTripForm)
      )}
    </>
  );
}

/**
 * Returns a Form.Group element with components specified by the input args.
 *
 * @param {string} controlId Prop that accessibly wires the nested label and
 *                           input prop.
 * @param {string} formLabel Label/title for the form input.
 * @param {string} inputType Input type of the form.
 * @param {string} defaultVal Default value in the form input.
 * @param {React.RefObject} ref Ref attached to the values inputted in the form.
 * @param {string} subFormText Subtext instructions under a form input.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Group element.
 */
function createFormGroup(controlId, formLabel, inputType,
                          defaultVal, ref, isAddTripForm) {
  let formControl;
  switch(inputType) {
    case 'text':
      formControl = createTextFormControl(defaultVal, ref, isAddTripForm);
      break;
    case 'date':
      formControl = createDateFormControl(defaultVal, ref);
      break;
    case 'emails':
      formControl = createMultiFormControl(defaultVal, ref, isAddTripForm);
      break;
    default:
      console.error('There should be no other input type')
  }

  return (
    <Form.Group controlId={controlId}>
      <Form.Label>{formLabel}</Form.Label>
      {formControl}
    </Form.Group>
  )
}

/**
 * Component corresponding to the save trips modal.
 *
 * This component acts as a "pseudo-parent" of the AddTripModal and
 * EditTripModal components. The only differences in the implementation between
 * the two fake components are dervied from the props  `tripid` and
 * `defaultFormObj` (see below). The primary difference between the add and
 * edit trip modals is the former displays placeholder values in the empty form
 * fields whereas the latter displays the current values of the trip in the
 * respective form fields.
 *
 * @param {Object} props These are the props for this component:
 * - show: Boolean that determines if the add trips modal should be displayed.
 * - handleClose: Event handler responsible for closing the add trips modal.
 * - refreshTripsContainer: Handler that refreshes the TripsContainer
 *        component upon trip creation (Remove when fix Issue #62).
 * - tripId: For adding a new trip, this will be null. For editting an existing
 *        trip, this will the document id associated with the trip.
 * - defaultFormObj: Object containing the placeholder/default values for the
 *        form input text boxes.
 * - key: Special React attribute that ensures a new AddTripModal instance is
 *        created whenever this key is updated
 *
 * @extends React.Component
 */
class SaveTripModal extends React.Component {
  /** @inheritdoc */
  constructor(props) {
    super(props);

    // Create Refs to reference form input elements
    this.nameRef = React.createRef();
    this.descriptionRef = React.createRef();
    this.destinationRef = React.createRef();
    this.startDateRef = React.createRef();
    this.endDateRef = React.createRef();

    this.isAddTripForm = this.props.tripId === null;

    // Create the number of collaborator input box refs as one less than the
    // number of collaborators specified in prop `defaultFormObj` (do not
    // include current user in list)
    //
    // TODO(Issue 71): Give user option to remove themself as collab. from trip.
    const collaboratorsRefArr = [];
    for (let i = 1; i < this.props.defaultFormObj.collaborators.length; i++) {
      collaboratorsRefArr.push(React.createRef())
    }
    this.state = { collaboratorsRefArr: collaboratorsRefArr }
  }

  /** Adds a new Ref element to the state variable `collaboratorsRefArr`. */
  addCollaboratorRef = () => {
    this.setState({ collaboratorsRefArr:
                    this.state.collaboratorsRefArr.concat([React.createRef()]) }
                 );
  }

  /**
   * Creates a new Trip document in firestore with data in `tripData`.
   *
   * @param {Object} tripData Data the new trip document will contain.
   */
  addNewTrip(tripData) {
    db.collection(COLLECTION_TRIPS)
        .add(tripData)
        .then(docRef => {
          console.log("Document written with ID: ", docRef.id);
        })
        .catch(error => {
          console.error("Error adding document: ", error);
        });
  }

  /**
   * Updates an existing Trip document in firestore with data in `tripData`.
   *
   * @param {string} tripId The document ID of the trip that is updated.
   * @param {Object} tripData Data the new trip document will contain.
   */
  updateExistingTrip(tripId, tripData) {
    db.collection(COLLECTION_TRIPS)
        .doc(tripId)
        .set(tripData)
        .then(() => {
          console.log("Document written with ID: ", tripId);
        })
        .catch(error => {
          console.error("Error adding document: ", error);
        });
  }

  /**
   * Formats/cleans the form data and saves the Trip document in firestore.
   */
  saveTrip() {
    const tripData = formatTripData(
        {
          name: this.nameRef.current.value,
          description: this.descriptionRef.current.value,
          destination: this.destinationRef.current.value,
          startDate: this.startDateRef.current.value,
          endDate: this.endDateRef.current.value,
          collaboratorEmails:
              this.state.collaboratorsRefArr.map(ref => ref.current.value)
        }
    );

    if (this.isAddTripForm) {
      this.addNewTrip(tripData);
    } else {
      this.updateExistingTrip(this.props.tripId, tripData);
    }

  }

  /**
   * Handles submission of the form which includes:
   *  - Creation of the trip.
   *  - Refreshing the trips container.
   *  - Closing the modal.
   */
  handleSubmitForm = () => {
    this.saveTrip();
    this.props.refreshTripsContainer();
    this.props.handleClose();
  }

  /** Gets the Modal title based the type of modal (edit or add trip). */
  getModalTitle = () => {
    if (this.isAddTripForm) {
      return 'Add New Trip';
    }
    return 'Edit Trip';
  }

  /** @inheritdoc */
  render() {
    return (
      <Modal show={this.props.show} onHide={this.props.handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>{this.getModalTitle()}</Modal.Title>
        </Modal.Header>

        <Form>
          <Modal.Body>
            {createFormGroup('tripNameGroup', 'Trip Name', 'text',
                this.props.defaultFormObj.name, this.nameRef,
                this.isAddTripForm)}
            {createFormGroup('tripDescGroup', 'Trip Description', 'text',
                this.props.defaultFormObj.description, this.descriptionRef,
                this.isAddTripForm)}
            {createFormGroup('tripDestGroup', 'Trip Destination', 'text',
                this.props.defaultFormObj.destination, this.destinationRef,
                this.isAddTripForm)}
            {createFormGroup('tripStartDateGroup', 'Start Date', 'date',
                this.props.defaultFormObj.startDate, this.startDateRef,
                this.isAddTripForm)}
            {createFormGroup('tripEndDateGroup', 'End Date', 'date',
                this.props.defaultFormObj.endDate, this.endDateRef,
                this.isAddTripForm)}
            {createFormGroup('tripCollabsGroup', 'Trip Collaborators', 'emails',
                this.props.defaultFormObj.collaborators,
                this.state.collaboratorsRefArr, this.isAddTripForm)}
            <Button onClick={this.addCollaboratorRef}>
              Add Another Collaborator
            </Button>
          </Modal.Body>

          <Modal.Footer>
            <Button variant='secondary' onClick={this.props.handleClose}>
              Cancel
            </Button>
            <Button variant='primary' onClick={this.handleSubmitForm}>
              Save Trip
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>
    );
  }
};

export default SaveTripModal;
<commit_msg>single quotes.<commit_after>import React from 'react';

import app from '../Firebase';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import Form from 'react-bootstrap/Form';

import { COLLECTION_TRIPS } from '../../constants/database.js';
import { formatTripData } from '../Utils/filter-input.js';

const db = app.firestore();

/**
 * Returns a Form.Control element with input type 'text' and other props
 * specified by the function parameters.
 *
 * @param {string} defaultText Default text value in the form input.
 * @param {React.RefObject} ref Ref attached to the value inputted in the form.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Control element.
 */
function createTextFormControl(defaultText, ref, isAddTripForm) {
  if (isAddTripForm) {
    return (
      <Form.Control
        type='text'
        placeholder={defaultText}
        ref={ref}
      />
    );
  }
  return (
    <Form.Control
      type='text'
      defaultValue={defaultText}
      ref={ref}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'date' and other props
 * specified by the function parameters.
 *
 * @param {string} defaultDate Default ISO date string in the form input.
 * @param {React.RefObject} refArr The list of refs attached to the emails
 *     inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createDateFormControl(defaultDate, ref) {
  return (
    <Form.Control
      type='date'
      ref={ref}
      defaultValue={defaultDate}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'email' and other props
 * specified by the function parameters.
 *
 * @param {string} defaultEmail Default text value in the form input.
 * @param {React.RefObject} ref Ref attached to the value inputted in the form.
 * @param {number} idx Index of the email Form.Control used for key prop.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Control element.
 */
function createEmailFormControl(defaultEmail, ref, idx, isAddTripForm) {
  if (isAddTripForm) {
    return (
      <Form.Control
        type='email'
        placeholder={defaultEmail}
        ref={ref}
        key={idx}
      />
    );
  }
  return (
    <Form.Control
      type='email'
      defaultValue={defaultEmail}
      ref={ref}
      key={idx}
    />
  );
}

/**
 * Returns multiple Form.Control elements with input type 'email' and other
 * props specified by the function parameters.
 *
 * One is added to the index of the emails show in order to display all
 * collaborators except the current user.
 *
 * TODO(Issue #67): Email verification before submitting the form.
 *
 * TODO(Issue #72): More intuitive remove collaborator when !`isAddTripForm`.
 *
 * @param {!Array<string>} defaultEmailArr Array of the emails to be displayed
 *     in the default form fields.
 * @param {!Array<React.RefObject>} refArr Array of refs attached to the
 *     emails inputted in the form.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Control elements.
 */
function createMultiFormControl(defaultEmailArr, refArr, isAddTripForm) {
  return (
    <>
      {refArr.map((ref, idx) =>
        createEmailFormControl(defaultEmailArr[idx + 1],
                                 ref, idx, isAddTripForm)
      )}
    </>
  );
}

/**
 * Returns a Form.Group element with components specified by the input args.
 *
 * @param {string} controlId Prop that accessibly wires the nested label and
 *                           input prop.
 * @param {string} formLabel Label/title for the form input.
 * @param {string} inputType Input type of the form.
 * @param {string} defaultVal Default value in the form input.
 * @param {React.RefObject} ref Ref attached to the values inputted in the form.
 * @param {string} subFormText Subtext instructions under a form input.
 * @param {boolean} isAddTripForm True if form is adding new trip, false if
 *     form is editting existing trip.
 * @return {JSX.Element} The Form.Group element.
 */
function createFormGroup(controlId, formLabel, inputType,
                          defaultVal, ref, isAddTripForm) {
  let formControl;
  switch(inputType) {
    case 'text':
      formControl = createTextFormControl(defaultVal, ref, isAddTripForm);
      break;
    case 'date':
      formControl = createDateFormControl(defaultVal, ref);
      break;
    case 'emails':
      formControl = createMultiFormControl(defaultVal, ref, isAddTripForm);
      break;
    default:
      console.error('There should be no other input type')
  }

  return (
    <Form.Group controlId={controlId}>
      <Form.Label>{formLabel}</Form.Label>
      {formControl}
    </Form.Group>
  )
}

/**
 * Component corresponding to the save trips modal.
 *
 * This component acts as a 'pseudo-parent' of the AddTripModal and
 * EditTripModal components. The only differences in the implementation between
 * the two fake components are dervied from the props  `tripid` and
 * `defaultFormObj` (see below). The primary difference between the add and
 * edit trip modals is the former displays placeholder values in the empty form
 * fields whereas the latter displays the current values of the trip in the
 * respective form fields.
 *
 * @param {Object} props These are the props for this component:
 * - show: Boolean that determines if the add trips modal should be displayed.
 * - handleClose: Event handler responsible for closing the add trips modal.
 * - refreshTripsContainer: Handler that refreshes the TripsContainer
 *        component upon trip creation (Remove when fix Issue #62).
 * - tripId: For adding a new trip, this will be null. For editting an existing
 *        trip, this will the document id associated with the trip.
 * - defaultFormObj: Object containing the placeholder/default values for the
 *        form input text boxes.
 * - key: Special React attribute that ensures a new AddTripModal instance is
 *        created whenever this key is updated
 *
 * @extends React.Component
 */
class SaveTripModal extends React.Component {
  /** @inheritdoc */
  constructor(props) {
    super(props);

    // Create Refs to reference form input elements
    this.nameRef = React.createRef();
    this.descriptionRef = React.createRef();
    this.destinationRef = React.createRef();
    this.startDateRef = React.createRef();
    this.endDateRef = React.createRef();

    this.isAddTripForm = this.props.tripId === null;

    // Create the number of collaborator input box refs as one less than the
    // number of collaborators specified in prop `defaultFormObj` (do not
    // include current user in list)
    //
    // TODO(Issue 71): Give user option to remove themself as collab. from trip.
    const collaboratorsRefArr = [];
    for (let i = 1; i < this.props.defaultFormObj.collaborators.length; i++) {
      collaboratorsRefArr.push(React.createRef())
    }
    this.state = { collaboratorsRefArr: collaboratorsRefArr }
  }

  /** Adds a new Ref element to the state variable `collaboratorsRefArr`. */
  addCollaboratorRef = () => {
    this.setState({ collaboratorsRefArr:
                    this.state.collaboratorsRefArr.concat([React.createRef()]) }
                 );
  }

  /**
   * Creates a new Trip document in firestore with data in `tripData`.
   *
   * @param {Object} tripData Data the new trip document will contain.
   */
  addNewTrip(tripData) {
    db.collection(COLLECTION_TRIPS)
        .add(tripData)
        .then(docRef => {
          console.log('Document written with ID: ', docRef.id);
        })
        .catch(error => {
          console.error('Error adding document: ', error);
        });
  }

  /**
   * Updates an existing Trip document in firestore with data in `tripData`.
   *
   * @param {string} tripId The document ID of the trip that is updated.
   * @param {Object} tripData Data the new trip document will contain.
   */
  updateExistingTrip(tripId, tripData) {
    db.collection(COLLECTION_TRIPS)
        .doc(tripId)
        .set(tripData)
        .then(() => {
          console.log('Document written with ID: ', tripId);
        })
        .catch(error => {
          console.error('Error adding document: ', error);
        });
  }

  /**
   * Formats/cleans the form data and saves the Trip document in firestore.
   */
  saveTrip() {
    const tripData = formatTripData(
        {
          name: this.nameRef.current.value,
          description: this.descriptionRef.current.value,
          destination: this.destinationRef.current.value,
          startDate: this.startDateRef.current.value,
          endDate: this.endDateRef.current.value,
          collaboratorEmails:
              this.state.collaboratorsRefArr.map(ref => ref.current.value)
        }
    );

    if (this.isAddTripForm) {
      this.addNewTrip(tripData);
    } else {
      this.updateExistingTrip(this.props.tripId, tripData);
    }

  }

  /**
   * Handles submission of the form which includes:
   *  - Creation of the trip.
   *  - Refreshing the trips container.
   *  - Closing the modal.
   */
  handleSubmitForm = () => {
    this.saveTrip();
    this.props.refreshTripsContainer();
    this.props.handleClose();
  }

  /** Gets the Modal title based the type of modal (edit or add trip). */
  getModalTitle = () => {
    if (this.isAddTripForm) {
      return 'Add New Trip';
    }
    return 'Edit Trip';
  }

  /** @inheritdoc */
  render() {
    return (
      <Modal show={this.props.show} onHide={this.props.handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>{this.getModalTitle()}</Modal.Title>
        </Modal.Header>

        <Form>
          <Modal.Body>
            {createFormGroup('tripNameGroup', 'Trip Name', 'text',
                this.props.defaultFormObj.name, this.nameRef,
                this.isAddTripForm)}
            {createFormGroup('tripDescGroup', 'Trip Description', 'text',
                this.props.defaultFormObj.description, this.descriptionRef,
                this.isAddTripForm)}
            {createFormGroup('tripDestGroup', 'Trip Destination', 'text',
                this.props.defaultFormObj.destination, this.destinationRef,
                this.isAddTripForm)}
            {createFormGroup('tripStartDateGroup', 'Start Date', 'date',
                this.props.defaultFormObj.startDate, this.startDateRef,
                this.isAddTripForm)}
            {createFormGroup('tripEndDateGroup', 'End Date', 'date',
                this.props.defaultFormObj.endDate, this.endDateRef,
                this.isAddTripForm)}
            {createFormGroup('tripCollabsGroup', 'Trip Collaborators', 'emails',
                this.props.defaultFormObj.collaborators,
                this.state.collaboratorsRefArr, this.isAddTripForm)}
            <Button onClick={this.addCollaboratorRef}>
              Add Another Collaborator
            </Button>
          </Modal.Body>

          <Modal.Footer>
            <Button variant='secondary' onClick={this.props.handleClose}>
              Cancel
            </Button>
            <Button variant='primary' onClick={this.handleSubmitForm}>
              Save Trip
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>
    );
  }
};

export default SaveTripModal;
<|endoftext|>


<commit_before>'use strict';

var isDate         = require('es5-ext/date/is-date')
  , i              = require('es5-ext/function/i')
  , isFunction     = require('es5-ext/function/is-function')
  , validFunction  = require('es5-ext/function/valid-function')
  , assign         = require('es5-ext/object/assign')
  , create         = require('es5-ext/object/create')
  , mixin          = require('es5-ext/object/mixin')
  , setPrototypeOf = require('es5-ext/object/set-prototype-of')
  , isRegExp       = require('es5-ext/reg-exp/is-reg-exp')
  , d              = require('d/d')
  , lazy           = require('d/lazy')
  , DbjsError      = require('../error')
  , Event          = require('../event')
  , ObjectsSet     = require('../objects-set')
  , turnPrototype  = require('../utils/propagate-prototype-turn').object
  , serialize      = require('../serialize/value')
  , isGetter       = require('../utils/is-getter')
  , getMessage     = require('../utils/get-sub-error-message')
  , updateEnum     = require('../utils/update-enumerability')
  , unserialize    = require('../unserialize/key')
  , notifyReverse  = require('../notify/reverse')
  , validDbValue   = require('../../valid-dbjs-value')
  , Extensions     = require('./extensions')

  , push = Array.prototype.push, slice = Array.prototype.slice
  , defineProperties = Object.defineProperties
  , keys = Object.keys, getPrototypeOf = Object.getPrototypeOf
  , isValidTypeName = RegExp.prototype.test.bind(/^[A-Z][0-9a-zA-Z]*$/)
  , typeMap = { boolean: 1, number: 2, string: 3, function: 4,  object: 4 }
  , getObjectType
  , destroy = function (sKey) { this[sKey]._destroy_(); }
  , initializeObject;

initializeObject = function (obj, postponed) {
	var sKey, descs = obj.__descriptors__, desc, set, iSKey, item, value
	  , resolve = function () { return desc._normalizeValue_(value); };
	for (sKey in descs) {
		desc = descs[sKey];
		if (desc._reverse_) continue;
		if (desc.nested || desc.multiple) updateEnum(obj, sKey, true);
		if (desc.nested) continue;
		if (isGetter(desc._value_)) continue;
		if (desc.multiple) {
			set = obj.__multiples__[sKey];
			if (!set) continue;
			for (iSKey in set) {
				item = set[iSKey];
				if (typeof item === 'number') {
					value = unserialize(iSKey);
				} else {
					value = item.key;
					if (!item._value_) continue;
					if (obj._normalize_(sKey, value) == null) continue;
				}
				postponed = notifyReverse(obj, sKey, value, null, null, null, iSKey,
					null, null, postponed);
			}
			continue;
		}
		value = desc._value_;
		if (value == null) continue;
		postponed = notifyReverse(obj, sKey, value, null, resolve, null, undefined,
			null, null, postponed);
	}
	return postponed;
};

getObjectType = function (value) {
	if (isDate(value)) return 4;
	if (isRegExp(value)) return 5;
	if (typeof value === 'function') return 6;
	return 7;
};

module.exports = function (db, createObj, object) {
	var Base, existingIds = db.objects.__setData__;

	Base = module.exports = function Self(value) {
		if (Self.is(value)) return value;
		return Self.create.apply(Self, arguments);
	};
	setPrototypeOf(Base, object);
	try { mixin(Base, Function.prototype); } catch (ignore) {}

	defineProperties(Base, assign({
		__id__: d('', 'Base'),
		object: d('', Base),
		extend: d(function (name) {
			return this._extendAndInitialize_.apply(this,
				this._validateExtend_.apply(this, arguments));
		}),
		_validateExtend_: d(function (name) {
			if (!isValidTypeName(name)) {
				throw new DbjsError(name + " is not valid type name",
					'INVALID_TYPE_NAME');
			}
			if (existingIds[name]) {
				throw new DbjsError(name + " type is already created", 'TYPE_EXISTS');
			}
			return [name].concat(this._validateExtendInitialize_.apply(this,
				slice.call(arguments, 1)));
		}),
		_validateExtendInitialize_: d(function (initialize, nsProps, objProps) {
			var errors;
			if (!isFunction(initialize)) {
				objProps = nsProps;
				nsProps = initialize;
				initialize = null;
			} else if (!nsProps) {
				nsProps = { _initialize_: { value: initialize } };
			} else {
				nsProps._initialize_ = { value: initialize };
			}

			if (nsProps) {
				try {
					nsProps = create(this)._validateDefineProperties_(nsProps);
				} catch (e) {
					if (e.name !== 'DbjsError') throw e;
					if (e.errors) errors = e.errors;
					else errors = [e];
				}
			}
			if (objProps) {
				try {
					objProps =
						create(this.prototype)._validateDefineProperties_(objProps);
				} catch (e) {
					if (e.name !== 'DbjsError') throw e;
					if (errors) {
						if (e.errors) push.apply(errors, e.errors);
						else errors.push(e);
					} else {
						if (e.errors) errors = e.errors;
						else errors = [e];
					}
				}
			}
			if (!errors) return [nsProps, objProps];
			throw new DbjsError("Invalid properties:\n\t" +
				errors.map(getMessage).join('\t\n'), 'TYPE_CONSTRUCTION_ERROR',
				{ errors: errors });
		}),
		_extendAndInitialize_: d(function (name) {
			var constructor = this._extend_(name);
			new Event(constructor, this); //jslint: skip
			constructor._initialize_.apply(constructor, slice.call(arguments, 1));
			return constructor;
		}),
		_initialize_: d(function (nsProps, objProps) {
			if (nsProps) this._defineProperties_(nsProps);
			if (objProps) this.prototype._defineProperties_(objProps);
		}),
		is: d(function (value) { return Boolean(serialize(value)); }),
		normalize: d(function (value) { return this.is(value) ? value : null; }),
		validate: d(function (value) {
			if (!serialize(value)) {
				throw new DbjsError(value + " cannot be handled by dbjs",
					'NOT_SUPPORTED_VALUE');
			}
			return value;
		}),
		compare: d(function (a, b) {
			var aType, bType;
			if (a == null) {
				if (b == null) return 0;
				return -Infinity;
			}
			if (b == null) return Infinity;
			aType = typeMap[typeof a];
			bType = typeMap[typeof b];
			if (aType !== bType) return aType - bType;
			if ((aType === 1) || (aType === 2)) return a - b;
			if (aType === 3) return String(a).localeCompare(b);
			aType = getObjectType(a);
			bType = getObjectType(b);
			if (aType !== bType) return aType - bType;
			if (aType === 4) return a - b;
			if ((aType === 5) || (aType === 6)) return String(a).localeCompare(b);
			return String(a.__id__).localeCompare(b.__id__);
		}),
		_extend_: d(function (name) {
			var postponed, constructor = function Self(value) {
				if (Self.is(value)) return value;
				return Self.create.apply(Self, arguments);
			};
			setPrototypeOf(constructor, this);
			defineProperties(constructor, {
				__id__: d('', name),
				__valueId__: d('', name),
				object: d('', constructor),
				master: d('', constructor)
			});
			constructor.prototype = create(this.prototype, {
				constructor: d(constructor),
				__id__: d('', name + '#'),
				__valueId__: d('', name + '#')
			});
			defineProperties(constructor.prototype, {
				master: d('', constructor.prototype),
				object: d('', constructor.prototype)
			});
			db.objects._add(constructor);
			db.objects._add(constructor.prototype);
			this._descendants_._add(constructor);
			this.prototype._descendants_._add(constructor.prototype);
			if (!(name in db)) db[name] = constructor;
			postponed = initializeObject(constructor);
			db._release_(initializeObject(constructor.prototype, postponed));
			return constructor;
		}),
		_setValue_: d(function (nu, dbEvent) {
			var old = getPrototypeOf(this), postponed;
			if (!nu) nu = Base;
			else validDbValue(validFunction(nu));
			if (old === nu) return;
			postponed = turnPrototype(this, nu, dbEvent);
			db._release_(turnPrototype(this.prototype, nu.prototype,
				dbEvent, postponed));
		}),
		create: d(function () {
			return this._createAndInitialize_.apply(this,
				this._validateCreate_.apply(this, arguments));
		}),
		_createNested_: d(function (object, sKey) {
			var nested;
			if (!this._keys_[sKey]) this._serialize_(unserialize(sKey, db.objects));
			nested = this._create_(object.__id__ + '/' + sKey, object.master);
			return defineProperties(nested, {
				owner: d('', object),
				key: d('', this._keys_[sKey]),
				__sKey__: d('', sKey)
			});
		}),
		_validateCreate_: d(function (value) { return [this.validate(value)]; }),
		_createAndInitialize_: d(i),
		_create_: d(function (id, master) {
			var object = createObj(this.prototype, id, id, null, master);
			db._release_(initializeObject(object));
			return object;
		}),
		find: d(function (key, value) {
			var sKey = this._serialize_(key), sValue;
			if (sKey == null) {
				throw new DbjsError(key + " is invalid key", 'INVALID_KEY');
			}
			if (value == null) {
				throw new DbjsError(value + " is not a value", 'INVALID_VALUE');
			}
			sValue = serialize(value);
			if (sValue == null) {
				throw new DbjsError(value + " is invalid database value",
					'NOT_SUPPORTED_VALUE');
			}
			return this.prototype._getReverseMap_(sKey)._getMultiple_(sValue, value);
		}),
		_destroy_: d(function () {
			if (this.hasOwnProperty('__typeAssignments__')) {
				this.__typeAssignments__._plainForEach_(function (obj) {
					new Event(obj.$type); //jslint: skip
				});
			}
			this.prototype._destroy_();
			this.prototype._destroy_.call(this);
		})
	}, lazy({
		_typeAssignments_: d(function () { return new ObjectsSet(); },
			{ cacheName: '__typeAssignments__', desc: '' }),
		extensions: d(function () {
			return new Extensions(this);
		}, { cacheName: '__extensions__', desc: '' })
	})));

	db.objects._add(Base);
	db.Base = Base;
	Base.prototype = object;

	defineProperties(object, {
		constructor: d(Base),
		_destroy_: d(function () {
			if (this.hasOwnProperty('__descendants__')) {
				this.__descendants__._plainForEach_(function (obj) {
					obj._destroy_();
				});
			}
			if (this.hasOwnProperty('__assignments__')) {
				this.__assignments__._plainForEach_(function (obj) {
					new Event(obj); //jslint: skip
				});
			}
			if (this.hasOwnProperty('__objects__')) {
				keys(this.__objects__).forEach(destroy, this.__objects__);
			}
			if (this.hasOwnProperty('__descriptors__')) {
				keys(this.__descriptors__).forEach(destroy, this.__descriptors__);
			}
			if (this.hasOwnProperty('__multiples__')) {
				keys(this.__multiples__).forEach(function (pSKey) {
					keys(this[pSKey]).forEach(destroy, this[pSKey]);
				}, this.__multiples__);
			}
			if (this.constructor.prototype === this) return;
			if (this.__sKey__) return;
			new Event(this); //jslint: skip
		}),
		_setValue_: d(function (nu, dbEvent) {
			var old;
			if (this.constructor.prototype === this) {
				// Sanity check
				throw new DbjsError("Turn of prototype of the prototypes is forbidden",
					'PROTOTYPE_TURN');
			}
			old = getPrototypeOf(this);
			if (!nu) {
				nu = object;
			} else {
				validDbValue(nu);
				if (nu._kind_ !== 'object') {
					throw new DbjsError("Prototype must be object kind",
						'NON_OBJECT_PROTOTYPE');
				}
			}
			if (old === nu) return;
			db._release_(turnPrototype(this, nu, dbEvent));
		})
	});
};
<commit_msg>Fix enumerability for newly created nested<commit_after>'use strict';

var isDate         = require('es5-ext/date/is-date')
  , i              = require('es5-ext/function/i')
  , isFunction     = require('es5-ext/function/is-function')
  , validFunction  = require('es5-ext/function/valid-function')
  , assign         = require('es5-ext/object/assign')
  , create         = require('es5-ext/object/create')
  , mixin          = require('es5-ext/object/mixin')
  , setPrototypeOf = require('es5-ext/object/set-prototype-of')
  , isRegExp       = require('es5-ext/reg-exp/is-reg-exp')
  , d              = require('d/d')
  , lazy           = require('d/lazy')
  , DbjsError      = require('../error')
  , Event          = require('../event')
  , ObjectsSet     = require('../objects-set')
  , turnPrototype  = require('../utils/propagate-prototype-turn').object
  , serialize      = require('../serialize/value')
  , isGetter       = require('../utils/is-getter')
  , getMessage     = require('../utils/get-sub-error-message')
  , updateEnum     = require('../utils/update-enumerability')
  , unserialize    = require('../unserialize/key')
  , notifyReverse  = require('../notify/reverse')
  , validDbValue   = require('../../valid-dbjs-value')
  , Extensions     = require('./extensions')

  , push = Array.prototype.push, slice = Array.prototype.slice
  , defineProperties = Object.defineProperties
  , keys = Object.keys, getPrototypeOf = Object.getPrototypeOf
  , isValidTypeName = RegExp.prototype.test.bind(/^[A-Z][0-9a-zA-Z]*$/)
  , typeMap = { boolean: 1, number: 2, string: 3, function: 4,  object: 4 }
  , getObjectType
  , destroy = function (sKey) { this[sKey]._destroy_(); }
  , initializeObject;

initializeObject = function (obj, postponed) {
	var sKey, descs = obj.__descriptors__, desc, set, iSKey, item, value
	  , resolve = function () { return desc._normalizeValue_(value); };
	for (sKey in descs) {
		desc = descs[sKey];
		if (desc._reverse_) continue;
		if (desc.nested || desc.multiple) updateEnum(obj, sKey, true);
		if (desc.nested) continue;
		if (isGetter(desc._value_)) continue;
		if (desc.multiple) {
			set = obj.__multiples__[sKey];
			if (!set) continue;
			for (iSKey in set) {
				item = set[iSKey];
				if (typeof item === 'number') {
					value = unserialize(iSKey);
				} else {
					value = item.key;
					if (!item._value_) continue;
					if (obj._normalize_(sKey, value) == null) continue;
				}
				postponed = notifyReverse(obj, sKey, value, null, null, null, iSKey,
					null, null, postponed);
			}
			continue;
		}
		value = desc._value_;
		if (value == null) continue;
		postponed = notifyReverse(obj, sKey, value, null, resolve, null, undefined,
			null, null, postponed);
	}
	return postponed;
};

getObjectType = function (value) {
	if (isDate(value)) return 4;
	if (isRegExp(value)) return 5;
	if (typeof value === 'function') return 6;
	return 7;
};

module.exports = function (db, createObj, object) {
	var Base, existingIds = db.objects.__setData__;

	Base = module.exports = function Self(value) {
		if (Self.is(value)) return value;
		return Self.create.apply(Self, arguments);
	};
	setPrototypeOf(Base, object);
	try { mixin(Base, Function.prototype); } catch (ignore) {}

	defineProperties(Base, assign({
		__id__: d('', 'Base'),
		object: d('', Base),
		extend: d(function (name) {
			return this._extendAndInitialize_.apply(this,
				this._validateExtend_.apply(this, arguments));
		}),
		_validateExtend_: d(function (name) {
			if (!isValidTypeName(name)) {
				throw new DbjsError(name + " is not valid type name",
					'INVALID_TYPE_NAME');
			}
			if (existingIds[name]) {
				throw new DbjsError(name + " type is already created", 'TYPE_EXISTS');
			}
			return [name].concat(this._validateExtendInitialize_.apply(this,
				slice.call(arguments, 1)));
		}),
		_validateExtendInitialize_: d(function (initialize, nsProps, objProps) {
			var errors;
			if (!isFunction(initialize)) {
				objProps = nsProps;
				nsProps = initialize;
				initialize = null;
			} else if (!nsProps) {
				nsProps = { _initialize_: { value: initialize } };
			} else {
				nsProps._initialize_ = { value: initialize };
			}

			if (nsProps) {
				try {
					nsProps = create(this)._validateDefineProperties_(nsProps);
				} catch (e) {
					if (e.name !== 'DbjsError') throw e;
					if (e.errors) errors = e.errors;
					else errors = [e];
				}
			}
			if (objProps) {
				try {
					objProps =
						create(this.prototype)._validateDefineProperties_(objProps);
				} catch (e) {
					if (e.name !== 'DbjsError') throw e;
					if (errors) {
						if (e.errors) push.apply(errors, e.errors);
						else errors.push(e);
					} else {
						if (e.errors) errors = e.errors;
						else errors = [e];
					}
				}
			}
			if (!errors) return [nsProps, objProps];
			throw new DbjsError("Invalid properties:\n\t" +
				errors.map(getMessage).join('\t\n'), 'TYPE_CONSTRUCTION_ERROR',
				{ errors: errors });
		}),
		_extendAndInitialize_: d(function (name) {
			var constructor = this._extend_(name);
			new Event(constructor, this); //jslint: skip
			constructor._initialize_.apply(constructor, slice.call(arguments, 1));
			return constructor;
		}),
		_initialize_: d(function (nsProps, objProps) {
			if (nsProps) this._defineProperties_(nsProps);
			if (objProps) this.prototype._defineProperties_(objProps);
		}),
		is: d(function (value) { return Boolean(serialize(value)); }),
		normalize: d(function (value) { return this.is(value) ? value : null; }),
		validate: d(function (value) {
			if (!serialize(value)) {
				throw new DbjsError(value + " cannot be handled by dbjs",
					'NOT_SUPPORTED_VALUE');
			}
			return value;
		}),
		compare: d(function (a, b) {
			var aType, bType;
			if (a == null) {
				if (b == null) return 0;
				return -Infinity;
			}
			if (b == null) return Infinity;
			aType = typeMap[typeof a];
			bType = typeMap[typeof b];
			if (aType !== bType) return aType - bType;
			if ((aType === 1) || (aType === 2)) return a - b;
			if (aType === 3) return String(a).localeCompare(b);
			aType = getObjectType(a);
			bType = getObjectType(b);
			if (aType !== bType) return aType - bType;
			if (aType === 4) return a - b;
			if ((aType === 5) || (aType === 6)) return String(a).localeCompare(b);
			return String(a.__id__).localeCompare(b.__id__);
		}),
		_extend_: d(function (name) {
			var postponed, constructor = function Self(value) {
				if (Self.is(value)) return value;
				return Self.create.apply(Self, arguments);
			};
			setPrototypeOf(constructor, this);
			defineProperties(constructor, {
				__id__: d('', name),
				__valueId__: d('', name),
				object: d('', constructor),
				master: d('', constructor)
			});
			constructor.prototype = create(this.prototype, {
				constructor: d(constructor),
				__id__: d('', name + '#'),
				__valueId__: d('', name + '#')
			});
			defineProperties(constructor.prototype, {
				master: d('', constructor.prototype),
				object: d('', constructor.prototype)
			});
			db.objects._add(constructor);
			db.objects._add(constructor.prototype);
			this._descendants_._add(constructor);
			this.prototype._descendants_._add(constructor.prototype);
			if (!(name in db)) db[name] = constructor;
			postponed = initializeObject(constructor);
			db._release_(initializeObject(constructor.prototype, postponed));
			return constructor;
		}),
		_setValue_: d(function (nu, dbEvent) {
			var old = getPrototypeOf(this), postponed;
			if (!nu) nu = Base;
			else validDbValue(validFunction(nu));
			if (old === nu) return;
			postponed = turnPrototype(this, nu, dbEvent);
			db._release_(turnPrototype(this.prototype, nu.prototype,
				dbEvent, postponed));
		}),
		create: d(function () {
			return this._createAndInitialize_.apply(this,
				this._validateCreate_.apply(this, arguments));
		}),
		_createNested_: d(function (object, sKey) {
			var nested, desc;
			if (!this._keys_[sKey]) this._serialize_(unserialize(sKey, db.objects));
			nested = this._create_(object.__id__ + '/' + sKey, object.master);
			desc = object._getDescriptor_(sKey);
			if (!desc._reverse_ && desc.nested) updateEnum(object, sKey, true);
			return defineProperties(nested, {
				owner: d('', object),
				key: d('', this._keys_[sKey]),
				__sKey__: d('', sKey)
			});
		}),
		_validateCreate_: d(function (value) { return [this.validate(value)]; }),
		_createAndInitialize_: d(i),
		_create_: d(function (id, master) {
			var object = createObj(this.prototype, id, id, null, master);
			db._release_(initializeObject(object));
			return object;
		}),
		find: d(function (key, value) {
			var sKey = this._serialize_(key), sValue;
			if (sKey == null) {
				throw new DbjsError(key + " is invalid key", 'INVALID_KEY');
			}
			if (value == null) {
				throw new DbjsError(value + " is not a value", 'INVALID_VALUE');
			}
			sValue = serialize(value);
			if (sValue == null) {
				throw new DbjsError(value + " is invalid database value",
					'NOT_SUPPORTED_VALUE');
			}
			return this.prototype._getReverseMap_(sKey)._getMultiple_(sValue, value);
		}),
		_destroy_: d(function () {
			if (this.hasOwnProperty('__typeAssignments__')) {
				this.__typeAssignments__._plainForEach_(function (obj) {
					new Event(obj.$type); //jslint: skip
				});
			}
			this.prototype._destroy_();
			this.prototype._destroy_.call(this);
		})
	}, lazy({
		_typeAssignments_: d(function () { return new ObjectsSet(); },
			{ cacheName: '__typeAssignments__', desc: '' }),
		extensions: d(function () {
			return new Extensions(this);
		}, { cacheName: '__extensions__', desc: '' })
	})));

	db.objects._add(Base);
	db.Base = Base;
	Base.prototype = object;

	defineProperties(object, {
		constructor: d(Base),
		_destroy_: d(function () {
			if (this.hasOwnProperty('__descendants__')) {
				this.__descendants__._plainForEach_(function (obj) {
					obj._destroy_();
				});
			}
			if (this.hasOwnProperty('__assignments__')) {
				this.__assignments__._plainForEach_(function (obj) {
					new Event(obj); //jslint: skip
				});
			}
			if (this.hasOwnProperty('__objects__')) {
				keys(this.__objects__).forEach(destroy, this.__objects__);
			}
			if (this.hasOwnProperty('__descriptors__')) {
				keys(this.__descriptors__).forEach(destroy, this.__descriptors__);
			}
			if (this.hasOwnProperty('__multiples__')) {
				keys(this.__multiples__).forEach(function (pSKey) {
					keys(this[pSKey]).forEach(destroy, this[pSKey]);
				}, this.__multiples__);
			}
			if (this.constructor.prototype === this) return;
			if (this.__sKey__) return;
			new Event(this); //jslint: skip
		}),
		_setValue_: d(function (nu, dbEvent) {
			var old;
			if (this.constructor.prototype === this) {
				// Sanity check
				throw new DbjsError("Turn of prototype of the prototypes is forbidden",
					'PROTOTYPE_TURN');
			}
			old = getPrototypeOf(this);
			if (!nu) {
				nu = object;
			} else {
				validDbValue(nu);
				if (nu._kind_ !== 'object') {
					throw new DbjsError("Prototype must be object kind",
						'NON_OBJECT_PROTOTYPE');
				}
			}
			if (old === nu) return;
			db._release_(turnPrototype(this, nu, dbEvent));
		})
	});
};
<|endoftext|>


<commit_before>var fs = require("fs");

var Context  = require('../lib/Context');
var Semaphore = require('../lib/Semaphore');
var Index = require('../lib/Index');
var Url = require('../lib/Url');
var assert = require('../lib/assert');
var request = require('../lib/request');

(function(){
// setup fallback error reporting
//
  Error.stackTraceLimit = Infinity;
  process.on('uncaughtException', function(err) {
    if (typeof err === 'object' && typeof err.name === 'string') {
      if (err.message) {
        console.log('\nMessage: ' + err.message);
      }
      console.log(err.stack);
    } else {
      console.log('dumpError :: argument is not an object');
    }
    process.exit(99);
  });
})();

function save_index$A(filename, index) {
  assert(typeof filename === 'string');
  assert(index instanceof Index);
  var con = this;
  var out;
  if (filename !== '-') {
    out = fs.createWriteStream(filename);
  } else {
    out = process.stdout;
  }
  out.write(index.toJSON(), function() {
    if (out !== process.stdout) {
      console.log("index writen to " + filename);
    }
    con.yield();
  });
}

/////
//    FuncName$A means this function is meant to be used as part of
//    an asynchronize calling chain and its input parameters should be
//    filled by its caller
////
function print_index$A(index) {
  assert(index instanceof Index);

  console.log("title:\t", index.title());
  console.log("author:\t", index.author());
  console.log("brief:\t", index.brief());
  if (index.coverUrl()) {
    console.log("cover:\t", index.coverUrl());
  }
  console.log("==== Table of Contents ====");
  index.debugPrint(console.log);
  this.yield();
}

function read_index$A(url, model) {
  assert(url instanceof Url);
  /////////////////////////////////
  var dom = require("../lib/dom")(url.getDomain(), 'index', model);

  function repeater$A(top) {
    assert(top && typeof top === 'object');
    if (typeof top.url !== 'string') {
      this.yield(Index.weave(top));
    } else {
      this.insert(
        function() {
          var context = this;
          var job = dom((new Url(top.url)),
                        function(r) { this.yield(); context.yield(r); });
          job.setArguments(top);
          job.run();
        },
        repeater$A
      ).yield();
    }
  }

  this.insert(repeater$A).yield({url: url.data(), href: url.data()});
}

function fetch_chapter$A(urlstr, model) {
  assert(typeof urlstr === 'string');
  var url = new Url(urlstr);
  ///////////////////////////////////////////////////

  var context = this;
  var dom = require('../lib/dom')(url.getDomain(), 'extract', model);
  dom(url, function(html) {
    context.yield(html);
    this.yield();
  }).run();
}

function download_index$A(config, index, model) {
  assert(typeof config  === 'object');
  assert(index instanceof Index);

  var length = index.getStatistics().leafCount;
  var sema = new Semaphore();
  var count = config.start;
  var i;
  var context = this;

  sema.hook(function() {
    console.log('\n  fetching complete, writen out to: ' + config.outdir);
    context.yield();
  });

  // write index.json
  sema.incr();
  fs.writeFile(config.outdir + "/index.json", index.toJSON(), function() {
    console.log("write index.json");
    sema.decr();
  });

  // fetch cover picture
  if (index.coverUrl()) {
    sema.incr();
    (function() {
      var con = new Context();
      con.set(
        // set encoding to null to get binary response(node Buffer)
        request({url: index.coverUrl(), encoding: null}),
        function (data) {
          var body = data.body;
          fs.writeFileSync(config.outdir + "/cover.jpg", body);
          console.log("write cover.jpg");
          sema.decr();
        }
      ).fire();
    })();
  }

  // skip n items to reeach start point
  var item_generator = index.getLeafIterator();
  for (i = 1; i < config.start; i++) { item_generator(); }

  var href = index.href();
  var url = new Url(href);
  var dom = require('../lib/dom')(url.getDomain(), 'extract', model);
  function generator() {
    var item = item_generator();
    if (!item) {
      return undefined;
    }

    var href = item.url;
    var url = new Url(decodeURI(href));
    var name = item.name;
    return [
      url,
      function(html) {
        var filename = url.getFileName();
        sema.incr();
        fs.writeFile(config.outdir + "/" + filename, html, function() {
          console.log("fetch ", count++, '/', length, " : ", name);
          sema.decr();
        });
        this.yield();
      }
    ];
  }

  for (i=0; i < config.concurrency; i++) {
    sema.incr();
    dom(generator).onEnd(function() {sema.decr(); }).run();
  }
}

function main(argv) {
  // for 'node debug'
  if (argv[1] === "debug") {
    argv = argv.slice(0,1).concat(argv.slice(2));
  }

  var program = require('commander'),
      package_json = require('../package.json'),
      commands = {};

  program.option("-l, --list", "list misc program information");

  function error(msg) {
    console.error("error: " + program._name +  ": " + msg);
    process.exit(1);
  }

  function check_existstence(prop) {
    if (!program[prop]) {
      error("--" + prop + " is required");
    }
  }

  function define_subcommand(cmd, obj) {
    obj.name = cmd;
    commands[cmd] = obj;
  }

  define_subcommand('index', {
    description: 'fetch and print book index and metadata',
    setup: function() {
      program.option("\n\b\b[index]:", "");
      program.option("-i, --input [file]", "the index file to be parsed (- as stdin)");
      program.option("-u, --url [url]", "the url to be indexed");
      program.option("-o, --out [file]", "write index to file");
      program.option("-b, --browser [model]", "browser backend to use", 'auto');
    },
    action: function() {
      var con = new Context();

      if (program.url !== undefined) {
        con.push(new Url(program.url), program.browser)
           .append(read_index$A);
      } else if (program.input !== undefined) {
        con.append((function() {
          var data = [];
          return function() {
            var input;
            var context = this;
            if (program.input === '-') {
              input = process.stdin;
            } else {
              input = fs.createReadStream(program.input);
            }
            input.setEncoding("utf-8");
            input.on("data", function(d) {
              data.push(d);
            });
            input.on("error", function() {
              error("IO error occurs when reading from " + program.input);
            });
            input.on('end', function() {
              context.yield(Index.loadJSON(data.join("")));
            });
          };
        })());
        //con.push(Index.loadJSON(fs.readFileSync(program.input)));
      } else {
        error("no url or index file speciified, abort");
      }

      if (program.out !== undefined) {
        con.unshift(program.out)
           .append(save_index$A);
      } else {
        con.append(print_index$A);
      }
      con.append(function() { process.exit(0); });
      con.fire();
    },
  });

  define_subcommand('download', {
    description: "download chapters from index",
    setup: function() {
      program
        .option("\n\b\b[download]:", "")
        .option("-u, --url [url]", "the url of index")
        .option("-i, --index [file]", "read index from saved file (- as stdin)")
        .option("-o, --out [dir]", "the target directory of fetched files")
        .option("-s, --start [pos]", "the start point (1-based) of downloading", "1")
        .option("-n, --concurrency [num]", "establish [num] connections concurrently", "5")
        .option("-b, --browser [model]", "browser backend to use", 'auto');
    },
    action: function() {
      var fs = require("fs"),
          stat;

      check_existstence("out");
      if (!fs.existsSync(program.out)) {
        error("folder " + program.out + " does not exist");
      }

      stat = fs.statSync(program.out);
      if (!stat.isDirectory()) {
        error(program.out + " is not a directory");
      }

      var con = new Context();
      con.push({
        start: parseInt(program.start, 10),
        concurrency: parseInt(program.concurrency, 10),
        outdir: program.out
      });
      if (typeof program.url === 'string') {
        (function() {
          con.push(new Url(program.url))
             .append(read_index$A);
        })();
      } else if (typeof program.index === 'string') {
        con.push(Index.loadJSON(fs.readFileSync(program.index)));
      } else {
        error("no url or index file specified, abort");
      }
      con.push(program.browser)
         .append(download_index$A)
         .append(function() { process.exit(0); })
         .fire();
    }
  });

  define_subcommand('package', {
    description: "package chapters into single epub file",
    setup: function() {
      program
        .option("\n\b\b[package]:", "")
        .option("-i, --index [dir]", "the directory contains index.json and chapters")
        .option("-o, --out [epub]", "the output epub filename");
    },
    action: function() {
      check_existstence("index");

      var fs = require("fs"),
          filename = program.index + "/index.json",
          index;

      if (!fs.existsSync(filename) || !fs.statSync(filename).isFile()) {
        error("cannot read file: " + program.index);
      }

      index = Index.loadJSON(fs.readFileSync(filename));
      index.package(program.index, program.out);
      process.exit(0);
    }
  });

  define_subcommand('fetch', {
    description: "fetch single chapter and present its content",
    setup: function() {
      program
        .option("\n\b\b[fetch]:", "")
        .option("-u, --url [url]", "the webpage to be fetched")
        .option("-b, --browser [model]", "browser backend to use", 'auto');
    },
    action: function() {
      check_existstence("url");
      var con = new Context();
      con.push(program.url, program.browser)
         .insert(fetch_chapter$A)
         .append(function(html) {
            console.log(html);
            process.exit(0);
          })
         .fire();
    }
  });

  function forEach(obj, callback)  {
    for (var k in obj) {
      if (obj.hasOwnProperty(k)) {
        callback(k, obj[k]);
      }
    }
  }

  program
    .version(package_json.version)
    .usage("Command [Options]");
  program._name = package_json.name;

  forEach(commands, function(k, v) {
    program.command(k).description(v.description);
  });

  if (argv.length === 2) {
    program.help();
  } else if (argv.length === 3 && (argv[2] === '-h' || argv[2] === '--help')) {
    for (var k in commands) {
      if (commands.hasOwnProperty(k)) {
        commands[k].setup();
      }
    }
    program.help();
  } else if (commands.hasOwnProperty(argv[2])) {
    (function(cmd) {
      argv = argv.slice(0, 2).concat(argv.slice(3));
      commands[cmd].setup();
      program.usage(cmd + " [Options]");

      if (argv.length === 2) {
        program.help();
      } else {
        program.parse(argv);
        commands[cmd].action(program);
      }
    })(argv[2]);
  } else if (argv.length === 3 && argv[2][0] === '-') {
    program.parse(argv);
    if (program.list) {
      var engines = require('../lib/dom').availableEngines;
      console.log([" * available dom engines are: "].concat(engines.join(', ')).join(''));
    }
    process.exit(0);
  } else {
    console.error("error: unknown subcommand: " + argv[2]);
  }
} /// end of main(argv)

require('../lib/miniserver')(function(port) {
  global.LOCAL_MINISERVER = function(path) {
    return 'http://localhost:' + port + '/' + path;
  };
  main(process.argv);
});

// vim: set errorformat=%f\:\ line\ %l\\,\ col\ %c\\,%m:
<commit_msg>fix phantom clean up issue<commit_after>var fs = require("fs");

var Context  = require('../lib/Context');
var Semaphore = require('../lib/Semaphore');
var Index = require('../lib/Index');
var Url = require('../lib/Url');
var assert = require('../lib/assert');
var request = require('../lib/request');

(function(){
// setup fallback error reporting
//
  Error.stackTraceLimit = Infinity;
  process.on('uncaughtException', function(err) {
    if (typeof err === 'object' && typeof err.name === 'string') {
      if (err.message) {
        console.log('\nMessage: ' + err.message);
      }
      console.log(err.stack);
    } else {
      console.log('dumpError :: argument is not an object');
    }
    process.exit(99);
  });
})();

function save_index$A(filename, index) {
  assert(typeof filename === 'string');
  assert(index instanceof Index);
  var con = this;
  var out;
  if (filename !== '-') {
    out = fs.createWriteStream(filename);
  } else {
    out = process.stdout;
  }
  out.write(index.toJSON(), function() {
    if (out !== process.stdout) {
      console.log("index writen to " + filename);
    }
    con.yield();
  });
}

/////
//    FuncName$A means this function is meant to be used as part of
//    an asynchronize calling chain and its input parameters should be
//    filled by its caller
////
function print_index$A(index) {
  assert(index instanceof Index);

  console.log("title:\t", index.title());
  console.log("author:\t", index.author());
  console.log("brief:\t", index.brief());
  if (index.coverUrl()) {
    console.log("cover:\t", index.coverUrl());
  }
  console.log("==== Table of Contents ====");
  index.debugPrint(console.log);
  this.yield();
}

function read_index$A(url, model) {
  assert(url instanceof Url);
  /////////////////////////////////
  var dom = require("../lib/dom")(url.getDomain(), 'index', model);

  function repeater$A(top) {
    assert(top && typeof top === 'object');
    if (typeof top.url !== 'string') {
      this.yield(Index.weave(top));
    } else {
      this.insert(
        function() {
          var context = this;
          var job = dom((new Url(top.url)),
                        function(r) { this.yield(); context.yield(r); });
          job.setArguments(top);
          job.run();
        },
        repeater$A
      ).yield();
    }
  }

  this.insert(repeater$A).yield({url: url.data(), href: url.data()});
}

function fetch_chapter$A(urlstr, model) {
  assert(typeof urlstr === 'string');
  var url = new Url(urlstr);
  ///////////////////////////////////////////////////

  var context = this;
  var dom = require('../lib/dom')(url.getDomain(), 'extract', model);
  dom(url, function(html) {
    this.yield();
    context.yield(html);
  }).run();
}

function download_index$A(config, index, model) {
  assert(typeof config  === 'object');
  assert(index instanceof Index);

  var length = index.getStatistics().leafCount;
  var sema = new Semaphore();
  var count = config.start;
  var i;
  var context = this;

  sema.hook(function() {
    console.log('\n  fetching complete, writen out to: ' + config.outdir);
    context.yield();
  });

  // write index.json
  sema.incr();
  fs.writeFile(config.outdir + "/index.json", index.toJSON(), function() {
    console.log("write index.json");
    sema.decr();
  });

  // fetch cover picture
  if (index.coverUrl()) {
    sema.incr();
    (function() {
      var con = new Context();
      con.set(
        // set encoding to null to get binary response(node Buffer)
        request({url: index.coverUrl(), encoding: null}),
        function (data) {
          var body = data.body;
          fs.writeFileSync(config.outdir + "/cover.jpg", body);
          console.log("write cover.jpg");
          sema.decr();
        }
      ).fire();
    })();
  }

  // skip n items to reeach start point
  var item_generator = index.getLeafIterator();
  for (i = 1; i < config.start; i++) { item_generator(); }

  var href = index.href();
  var url = new Url(href);
  var dom = require('../lib/dom')(url.getDomain(), 'extract', model);
  function generator() {
    var item = item_generator();
    if (!item) {
      return undefined;
    }

    var href = item.url;
    var url = new Url(decodeURI(href));
    var name = item.name;
    return [
      url,
      function(html) {
        var filename = url.getFileName();
        sema.incr();
        fs.writeFile(config.outdir + "/" + filename, html, function() {
          console.log("fetch ", count++, '/', length, " : ", name);
          sema.decr();
        });
        this.yield();
      }
    ];
  }

  for (i=0; i < config.concurrency; i++) {
    sema.incr();
    dom(generator).onEnd(function() {sema.decr(); }).run();
  }
}

function main(argv) {
  // for 'node debug'
  if (argv[1] === "debug") {
    argv = argv.slice(0,1).concat(argv.slice(2));
  }

  var program = require('commander'),
      package_json = require('../package.json'),
      commands = {};

  program.option("-l, --list", "list misc program information");

  function error(msg) {
    console.error("error: " + program._name +  ": " + msg);
    process.exit(1);
  }

  function check_existstence(prop) {
    if (!program[prop]) {
      error("--" + prop + " is required");
    }
  }

  function define_subcommand(cmd, obj) {
    obj.name = cmd;
    commands[cmd] = obj;
  }

  define_subcommand('index', {
    description: 'fetch and print book index and metadata',
    setup: function() {
      program.option("\n\b\b[index]:", "");
      program.option("-i, --input [file]", "the index file to be parsed (- as stdin)");
      program.option("-u, --url [url]", "the url to be indexed");
      program.option("-o, --out [file]", "write index to file");
      program.option("-b, --browser [model]", "browser backend to use", 'auto');
    },
    action: function() {
      var con = new Context();

      if (program.url !== undefined) {
        con.push(new Url(program.url), program.browser)
           .append(read_index$A);
      } else if (program.input !== undefined) {
        con.append((function() {
          var data = [];
          return function() {
            var input;
            var context = this;
            if (program.input === '-') {
              input = process.stdin;
            } else {
              input = fs.createReadStream(program.input);
            }
            input.setEncoding("utf-8");
            input.on("data", function(d) {
              data.push(d);
            });
            input.on("error", function() {
              error("IO error occurs when reading from " + program.input);
            });
            input.on('end', function() {
              context.yield(Index.loadJSON(data.join("")));
            });
          };
        })());
        //con.push(Index.loadJSON(fs.readFileSync(program.input)));
      } else {
        error("no url or index file speciified, abort");
      }

      if (program.out !== undefined) {
        con.unshift(program.out)
           .append(save_index$A);
      } else {
        con.append(print_index$A);
      }
      con.append(function() { process.exit(0); });
      con.fire();
    },
  });

  define_subcommand('download', {
    description: "download chapters from index",
    setup: function() {
      program
        .option("\n\b\b[download]:", "")
        .option("-u, --url [url]", "the url of index")
        .option("-i, --index [file]", "read index from saved file (- as stdin)")
        .option("-o, --out [dir]", "the target directory of fetched files")
        .option("-s, --start [pos]", "the start point (1-based) of downloading", "1")
        .option("-n, --concurrency [num]", "establish [num] connections concurrently", "5")
        .option("-b, --browser [model]", "browser backend to use", 'auto');
    },
    action: function() {
      var fs = require("fs"),
          stat;

      check_existstence("out");
      if (!fs.existsSync(program.out)) {
        error("folder " + program.out + " does not exist");
      }

      stat = fs.statSync(program.out);
      if (!stat.isDirectory()) {
        error(program.out + " is not a directory");
      }

      var con = new Context();
      con.push({
        start: parseInt(program.start, 10),
        concurrency: parseInt(program.concurrency, 10),
        outdir: program.out
      });
      if (typeof program.url === 'string') {
        (function() {
          con.push(new Url(program.url))
             .append(read_index$A);
        })();
      } else if (typeof program.index === 'string') {
        con.push(Index.loadJSON(fs.readFileSync(program.index)));
      } else {
        error("no url or index file specified, abort");
      }
      con.push(program.browser)
         .append(download_index$A)
         .append(function() { process.exit(0); })
         .fire();
    }
  });

  define_subcommand('package', {
    description: "package chapters into single epub file",
    setup: function() {
      program
        .option("\n\b\b[package]:", "")
        .option("-i, --index [dir]", "the directory contains index.json and chapters")
        .option("-o, --out [epub]", "the output epub filename");
    },
    action: function() {
      check_existstence("index");

      var fs = require("fs"),
          filename = program.index + "/index.json",
          index;

      if (!fs.existsSync(filename) || !fs.statSync(filename).isFile()) {
        error("cannot read file: " + program.index);
      }

      index = Index.loadJSON(fs.readFileSync(filename));
      index.package(program.index, program.out);
      process.exit(0);
    }
  });

  define_subcommand('fetch', {
    description: "fetch single chapter and present its content",
    setup: function() {
      program
        .option("\n\b\b[fetch]:", "")
        .option("-u, --url [url]", "the webpage to be fetched")
        .option("-b, --browser [model]", "browser backend to use", 'auto');
    },
    action: function() {
      check_existstence("url");
      var con = new Context();
      con.push(program.url, program.browser)
         .insert(fetch_chapter$A)
         .append(function(html) {
            console.log(html);
            process.exit(0);
          })
         .fire();
    }
  });

  function forEach(obj, callback)  {
    for (var k in obj) {
      if (obj.hasOwnProperty(k)) {
        callback(k, obj[k]);
      }
    }
  }

  program
    .version(package_json.version)
    .usage("Command [Options]");
  program._name = package_json.name;

  forEach(commands, function(k, v) {
    program.command(k).description(v.description);
  });

  if (argv.length === 2) {
    program.help();
  } else if (argv.length === 3 && (argv[2] === '-h' || argv[2] === '--help')) {
    for (var k in commands) {
      if (commands.hasOwnProperty(k)) {
        commands[k].setup();
      }
    }
    program.help();
  } else if (commands.hasOwnProperty(argv[2])) {
    (function(cmd) {
      argv = argv.slice(0, 2).concat(argv.slice(3));
      commands[cmd].setup();
      program.usage(cmd + " [Options]");

      if (argv.length === 2) {
        program.help();
      } else {
        program.parse(argv);
        commands[cmd].action(program);
      }
    })(argv[2]);
  } else if (argv.length === 3 && argv[2][0] === '-') {
    program.parse(argv);
    if (program.list) {
      var engines = require('../lib/dom').availableEngines;
      console.log([" * available dom engines are: "].concat(engines.join(', ')).join(''));
    }
    process.exit(0);
  } else {
    console.error("error: unknown subcommand: " + argv[2]);
  }
} /// end of main(argv)

require('../lib/miniserver')(function(port) {
  global.LOCAL_MINISERVER = function(path) {
    return 'http://localhost:' + port + '/' + path;
  };
  main(process.argv);
});

// vim: set errorformat=%f\:\ line\ %l\\,\ col\ %c\\,%m:
<|endoftext|>


<commit_before>const config = require('./webpack.dev.js');

config.module.rules.push({
  test: /\.js$/,
  enforce: 'post',
  include: `${config.context}/app`,
  loader: 'istanbul-instrumenter-loader',
  exclude: [/\.spec\.js$/, /node_modules/],
});

module.exports = config;
<commit_msg>[Finishes #149866130] Removed config files from being tested<commit_after>const config = require('./webpack.dev.js');

config.module.rules.push({
  test: /\.js$/,
  enforce: 'post',
  include: `${config.context}/app`,
  loader: 'istanbul-instrumenter-loader',
  exclude: [/\.config.js$/, /\.spec\.js$/, /node_modules/],
});

module.exports = config;
<|endoftext|>