<commit_before>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community 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://opensource.org/licenses/ECL-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.
 */

/**
 * @RESTModel BasicUser
 *
 * @Required  [displayName,id,lastModified,picture,resourceType,tenant,visibility]
 * @Property    {string}            displayName         Name to display for user
 * @Property    {string}            id                  Unique identifier for user
 * @Property    {string}            lastModified        Time resource last modified (epoch format)
 * @Property    {PictureInfo}       picture             Thumbnails and pictures for user
 * @Property    {string}            resourceType        Type of resource (user)
 * @Property    {BasicTenant}       tenant              Tenant information for user
 * @Property    {string}            visibility          Visibility of user              [loggedin,private,public]
 */

/**
 * @RESTModel BasicGroup
 *
 * @Required    [displayName,id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {string}        canJoin                 Can current user join group
 * @Property    {string}        description             Description of group
 * @Property    {string}        displayName             Name to display for group
 * @Property    {string}        id                      Unique identifier for group
 * @Property    {boolean}       isManager               Is current user manager of group
 * @Property    {boolean}       isMember                Is current user a member of group
 * @Property    {string}        joinable                Can users join group            [no,request,yes]
 * @Property    {number}        lastModified            Time information was last modified (epoch format)
 * @Property    {PictureInfo}   picture                 Thumbnail information
 * @Property    {string}        profilePath             Relative URL for group's main page
 * @Property    {string}        resourceType            Type of resource (group)
 * @Property    {Signature}     signature               Access control for group
 * @Property    {BasicTenant}   tenant                  Tenant for group
 * @Property    {string}        visibility              Visibility of group             [loggedin,private,public]
 */

/**
 * @RESTModel FollowingInfo
 *
 * @Required    [canFollow,isFollowing]
 * @Property    {boolean}       canFollow               Can the currently logged in user follow the referenced user
 * @Property    {boolean}       isFollowing             Is the currently logged in user following the referenced user
 */

/**
 * @RESTModel Me
 *
 * @Required    [displayName,id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {number}        acceptedTC              Time user accepted terms and conditions (epoch format)
 * @Property    {string}        authenticationStrategy  How is user authenticated       [cas,facebook,google,ldap,local,oauth,shibboleth,signed,twitter]
 * @Property    {string}        displayName             Name to display for user
 * @Property    {string}        email                   Email address for user
 * @Property    {string}        emailPreference         Email preferences for user      [daily,immediate,weekly]
 * @Property    {string}        id                      Unique identifier for user
 * @Property    {boolean}       isGlobalAdmin           Is user global administrator
 * @Property    {boolean}       isTenantAdmin           Is user tenant administrator
 * @Property    {number}        lastModified            Time user's information was last modified (epoch format)
 * @Property    {string}        locale                  Locale for user (e.g. en_US)
 * @Property    {boolean}       needsToAcceptTC         Does user need to accept terms and conditions
 * @Property    {number}        notificationsLastRead   Time user last read (epoch format)
 * @Property    {number}        notificationsUnread     Number of unread notifications for user
 * @Property    {PictureInfo}   picture                 User's thumbnail information
 * @Property    {string}        profilePath             URL for user's main page
 * @Property    {string}        publicAlias             Public alias for user
 * @Property    {string}        resourceType            Type of resource (user)
 * @Property    {Signature}     signature               Access control for user
 * @Property    {BasicTenant}   tenant                  User's tenant
 * @Property    {string}        visibility              Visibility of user              [loggedin,private,public]
 */

/**
 * @RESTModel Member
 *
 * @Required    [profile,role]
 * @Property    {BasicUser}     profile                 Profile of member
 * @Property    {string}        role                    Role of member          [manager,member,viewer]
 */

/**
 * @RESTModel MembershipInfo
 *
 * @Required    []
 * @Property    {boolean}       {principalId}           Should identified user or group be included in group, may be repeated for multiple users/groups
 */

/**
 * @RESTModel GroupsResponse
 *
 * @Required    [nextToken,results]
 * @Property    {BasicGroup[]}  results                 Array of groups
 * @Property    {string}        nextToken               Marker to allow continuation of search (empty if search has been exhausted)
 */

/**
 * @RESTModel MembersResponse
 *
 * @Required    [nextToken,results]
 * @Property    {Member[]}      results                 Array of members
 * @Property    {string}        nextToken               Marker to allow continuation of search (empty if search has been exhausted)
 */

/**
 * @RESTModel PictureInfo
 *
 * @Required    []
 * @Property    {string}        large                   URL for large image of resource
 * @Property    {string}        medium                  URL for medium image of resource
 * @Property    {string}        small                   URL for small image of resource
 */

/**
 * @RESTModel Profile
 *
 * @Required    [id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {number}        acceptedTC              Time user accepted terms and conditions (epoch format)
 * @Property    {string}        canJoin                 Can current user join group
 * @Property    {string}        description             Description of group
 * @Property    {string}        displayName             Name to display for user/group
 * @Property    {string}        email                   Email address for user
 * @Property    {string}        emailPreference         Email preferences for user      [daily,immediate,weekly]
 * @Property    {FollowingInfo} following               Following information (if requested user is not logged in user)
 * @Property    {string}        id                      Unique identifier for user/group
 * @Property    {boolean}       isManager               Is current user manager of group
 * @Property    {boolean}       isMember                Is current user a member of group
 * @Property    {string}        joinable                Can users join group            [no,request,yes]
 * @Property    {number}        lastModified            Time information was last modified (epoch format)
 * @Property    {string}        locale                  Locale for user (e.g. en_US)
 * @Property    {number}        notificationsLastRead   Time user last read (epoch format)
 * @Property    {number}        notificationsUnread     Number of unread notifications for user
 * @Property    {PictureInfo}   picture                 Thumbnail information
 * @Property    {string}        profilePath             URL for user/group's main page
 * @Property    {string}        publicAlias             Public alias for user
 * @Property    {string}        resourceType            Type of resource                [group,user]
 * @Property    {Signature}     signature               Access control for group
 * @Property    {BasicTenant}   tenant                  Tenant for user/group
 * @Property    {string}        visibility              Visibility of user/group        [loggedin,private,public]
 */

/**
 * @RESTModel TermsAndConditions
 *
 * @Required    [lastUpdate,text]
 * @Property    {number}        lastUpdate                Time terms and conditions were last updated (epoch format)
 * @Property    {string}        text                      Text of terms and conditions
 */

/**
 * @RESTModel User
 *
 * @Required    [displayName,id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {string}        displayName             Name to display for user
 * @Property    {string}        email                   Email address for user
 * @Property    {string}        emailPreference         Email preferences for user [daily,immediate,weekly]
 * @Property    {string}        id                      Unique identifier for user
 * @Property    {number}        lastModified            Time user's information was last modified (epoch format)
 * @Property    {string}        locale                  Locale for user (e.g. en_US)
 * @Property    {PictureInfo}   picture                 User's thumbnail information
 * @Property    {string}        profilePath             URL for user's main page
 * @Property    {string}        publicAlias             Public alias for user
 * @Property    {string}        resourceType            Type of resource (user)
 * @Property    {BasicTenant}   tenant                  User's tenant
 * @Property    {string}        visibility              Visibility of user      [loggedin,private,public]
 */
<commit_msg>move permissions model to principals module<commit_after>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community 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://opensource.org/licenses/ECL-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.
 */

/**
 * @RESTModel BasicUser
 *
 * @Required  [displayName,id,lastModified,picture,resourceType,tenant,visibility]
 * @Property    {string}            displayName         Name to display for user
 * @Property    {string}            id                  Unique identifier for user
 * @Property    {string}            lastModified        Time resource last modified (epoch format)
 * @Property    {PictureInfo}       picture             Thumbnails and pictures for user
 * @Property    {string}            resourceType        Type of resource (user)
 * @Property    {BasicTenant}       tenant              Tenant information for user
 * @Property    {string}            visibility          Visibility of user              [loggedin,private,public]
 */

/**
 * @RESTModel BasicGroup
 *
 * @Required    [displayName,id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {string}        canJoin                 Can current user join group
 * @Property    {string}        description             Description of group
 * @Property    {string}        displayName             Name to display for group
 * @Property    {string}        id                      Unique identifier for group
 * @Property    {boolean}       isManager               Is current user manager of group
 * @Property    {boolean}       isMember                Is current user a member of group
 * @Property    {string}        joinable                Can users join group            [no,request,yes]
 * @Property    {number}        lastModified            Time information was last modified (epoch format)
 * @Property    {PictureInfo}   picture                 Thumbnail information
 * @Property    {string}        profilePath             Relative URL for group's main page
 * @Property    {string}        resourceType            Type of resource (group)
 * @Property    {Signature}     signature               Access control for group
 * @Property    {BasicTenant}   tenant                  Tenant for group
 * @Property    {string}        visibility              Visibility of group             [loggedin,private,public]
 */

/**
 * @RESTModel FollowingInfo
 *
 * @Required    [canFollow,isFollowing]
 * @Property    {boolean}       canFollow               Can the currently logged in user follow the referenced user
 * @Property    {boolean}       isFollowing             Is the currently logged in user following the referenced user
 */

/**
 * @RESTModel Me
 *
 * @Required    [displayName,id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {number}        acceptedTC              Time user accepted terms and conditions (epoch format)
 * @Property    {string}        authenticationStrategy  How is user authenticated       [cas,facebook,google,ldap,local,oauth,shibboleth,signed,twitter]
 * @Property    {string}        displayName             Name to display for user
 * @Property    {string}        email                   Email address for user
 * @Property    {string}        emailPreference         Email preferences for user      [daily,immediate,weekly]
 * @Property    {string}        id                      Unique identifier for user
 * @Property    {boolean}       isGlobalAdmin           Is user global administrator
 * @Property    {boolean}       isTenantAdmin           Is user tenant administrator
 * @Property    {number}        lastModified            Time user's information was last modified (epoch format)
 * @Property    {string}        locale                  Locale for user (e.g. en_US)
 * @Property    {boolean}       needsToAcceptTC         Does user need to accept terms and conditions
 * @Property    {number}        notificationsLastRead   Time user last read (epoch format)
 * @Property    {number}        notificationsUnread     Number of unread notifications for user
 * @Property    {PictureInfo}   picture                 User's thumbnail information
 * @Property    {string}        profilePath             URL for user's main page
 * @Property    {string}        publicAlias             Public alias for user
 * @Property    {string}        resourceType            Type of resource (user)
 * @Property    {Signature}     signature               Access control for user
 * @Property    {BasicTenant}   tenant                  User's tenant
 * @Property    {string}        visibility              Visibility of user              [loggedin,private,public]
 */

/**
 * @RESTModel Member
 *
 * @Required    [profile,role]
 * @Property    {BasicUser}     profile                 Profile of member
 * @Property    {string}        role                    Role of member          [manager,member,viewer]
 */

/**
 * @RESTModel MembershipInfo
 *
 * @Required    []
 * @Property    {boolean}       {principalId}           Should identified user or group be included in group, may be repeated for multiple users/groups
 */

/**
 * @RESTModel GroupsResponse
 *
 * @Required    [nextToken,results]
 * @Property    {BasicGroup[]}  results                 Array of groups
 * @Property    {string}        nextToken               Marker to allow continuation of search (empty if search has been exhausted)
 */

/**
 * @RESTModel MembersResponse
 *
 * @Required    [nextToken,results]
 * @Property    {Member[]}      results                 Array of members
 * @Property    {string}        nextToken               Marker to allow continuation of search (empty if search has been exhausted)
 */

/**
 * @RESTModel Permissions
 *
 * @Required    []
 * @Property    {string}        {principalId}           Permission for specified user/group, may be repeated for multiple principals [false,manager,viewer]
 */

/**
 * @RESTModel PictureInfo
 *
 * @Required    []
 * @Property    {string}        large                   URL for large image of resource
 * @Property    {string}        medium                  URL for medium image of resource
 * @Property    {string}        small                   URL for small image of resource
 */

/**
 * @RESTModel Profile
 *
 * @Required    [id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {number}        acceptedTC              Time user accepted terms and conditions (epoch format)
 * @Property    {string}        canJoin                 Can current user join group
 * @Property    {string}        description             Description of group
 * @Property    {string}        displayName             Name to display for user/group
 * @Property    {string}        email                   Email address for user
 * @Property    {string}        emailPreference         Email preferences for user      [daily,immediate,weekly]
 * @Property    {FollowingInfo} following               Following information (if requested user is not logged in user)
 * @Property    {string}        id                      Unique identifier for user/group
 * @Property    {boolean}       isManager               Is current user manager of group
 * @Property    {boolean}       isMember                Is current user a member of group
 * @Property    {string}        joinable                Can users join group            [no,request,yes]
 * @Property    {number}        lastModified            Time information was last modified (epoch format)
 * @Property    {string}        locale                  Locale for user (e.g. en_US)
 * @Property    {number}        notificationsLastRead   Time user last read (epoch format)
 * @Property    {number}        notificationsUnread     Number of unread notifications for user
 * @Property    {PictureInfo}   picture                 Thumbnail information
 * @Property    {string}        profilePath             URL for user/group's main page
 * @Property    {string}        publicAlias             Public alias for user
 * @Property    {string}        resourceType            Type of resource                [group,user]
 * @Property    {Signature}     signature               Access control for group
 * @Property    {BasicTenant}   tenant                  Tenant for user/group
 * @Property    {string}        visibility              Visibility of user/group        [loggedin,private,public]
 */

/**
 * @RESTModel TermsAndConditions
 *
 * @Required    [lastUpdate,text]
 * @Property    {number}        lastUpdate                Time terms and conditions were last updated (epoch format)
 * @Property    {string}        text                      Text of terms and conditions
 */

/**
 * @RESTModel User
 *
 * @Required    [displayName,id,lastModified,profilePath,tenant,resourceType,visibility]
 * @Property    {string}        displayName             Name to display for user
 * @Property    {string}        email                   Email address for user
 * @Property    {string}        emailPreference         Email preferences for user [daily,immediate,weekly]
 * @Property    {string}        id                      Unique identifier for user
 * @Property    {number}        lastModified            Time user's information was last modified (epoch format)
 * @Property    {string}        locale                  Locale for user (e.g. en_US)
 * @Property    {PictureInfo}   picture                 User's thumbnail information
 * @Property    {string}        profilePath             URL for user's main page
 * @Property    {string}        publicAlias             Public alias for user
 * @Property    {string}        resourceType            Type of resource (user)
 * @Property    {BasicTenant}   tenant                  User's tenant
 * @Property    {string}        visibility              Visibility of user      [loggedin,private,public]
 */
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import _ from 'lodash';

const HostsTable = React.createClass({
  propTypes: {
    hosts: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string,
      cpu: PropTypes.number,
      load: PropTypes.number,
      apps: PropTypes.arrayOf(PropTypes.string.isRequired),
    })),
    source: PropTypes.shape({
      id: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
    }).isRequired,
  },

  getInitialState() {
    return {
      searchTerm: '',
      filteredHosts: this.props.hosts,
      sortDirection: null,
      sortKey: null,
    };
  },

  componentWillReceiveProps(newProps) {
    this.filterHosts(newProps.hosts, this.state.searchTerm);
  },

  filterHosts(allHosts, searchTerm) {
    const hosts = allHosts.filter((h) => {
      let apps = null;
      if (h.apps) {
        apps = h.apps.join(', ');
      } else {
        apps = '';
      }
      return (
        h.name.search(searchTerm) !== -1 ||
        apps.search(searchTerm) !== -1
      );
    });
    this.setState({searchTerm, filteredHosts: hosts});
  },

  changeSort(key) {
    // if we're using the key, reverse order; otherwise, set it with ascending
    if (this.state.sortKey === key) {
      const reverseDirection = (this.state.sortDirection === 'asc' ? 'desc' : 'asc');
      this.setState({sortDirection: reverseDirection});
    } else {
      this.setState({sortKey: key, sortDirection: 'asc'});
    }
  },

  sort(hosts, key, direction) {
    switch (direction) {
      case 'asc':
        return _.sortBy(hosts, (e) => e[key]);
      case 'desc':
        return _.sortBy(hosts, (e) => e[key]).reverse();
      default:
        return hosts;
    }
  },

  sortableClasses(key) {
    if (this.state.sortKey === key) {
      if (this.state.sortDirection === 'asc') {
        return "sortable-header sorting-up";
      }
      return "sortable-header sorting-down";
    }
    return "sortable-header";
  },

  render() {
    const hosts = this.sort(this.state.filteredHosts, this.state.sortKey, this.state.sortDirection);
    const hostCount = hosts.length;
    const {source} = this.props;

    return (

{hostCount ? `${hostCount} Hosts` : ''}

          <SearchBar onSearch={_.wrap(this.props.hosts, this.filterHosts)} />

                 this.changeSort('name')} className={this.sortableClasses('name')}>Hostname
                Status
                 this.changeSort('cpu')} className={this.sortableClasses('cpu')}>CPU
                 this.changeSort('load')} className={this.sortableClasses('load')}>Load
                Apps

              {
                hosts.map(({name, cpu, load, apps = []}) => {
                  return (

                      <Link to={`/sources/${source.id}/hosts/${name}`}>{name}</Link>

                      {isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
                      {isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}

                        {apps.map((app) => {
                          return <Link key={app} to={{pathname: `/sources/${source.id}/hosts/${name}`, query: {app}}}>{app}</Link>;
                        })}

                  );
                })
              }

    );
  },
});

const SearchBar = React.createClass({
  propTypes: {
    onSearch: PropTypes.func.isRequired,
  },

  handleChange() {
    this.props.onSearch(this.refs.searchInput.value);
  },

  render() {
    return (

        <input
          type="text"
          className="form-control"
          placeholder="Filter Hosts"
          ref="searchInput"
          onChange={this.handleChange}
        />

    );
  },
});

export default HostsTable;
<commit_msg>Add whitespace and commas between apps<commit_after>import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import _ from 'lodash';

const HostsTable = React.createClass({
  propTypes: {
    hosts: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string,
      cpu: PropTypes.number,
      load: PropTypes.number,
      apps: PropTypes.arrayOf(PropTypes.string.isRequired),
    })),
    source: PropTypes.shape({
      id: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
    }).isRequired,
  },

  getInitialState() {
    return {
      searchTerm: '',
      filteredHosts: this.props.hosts,
      sortDirection: null,
      sortKey: null,
    };
  },

  componentWillReceiveProps(newProps) {
    this.filterHosts(newProps.hosts, this.state.searchTerm);
  },

  filterHosts(allHosts, searchTerm) {
    const hosts = allHosts.filter((h) => {
      let apps = null;
      if (h.apps) {
        apps = h.apps.join(', ');
      } else {
        apps = '';
      }
      return (
        h.name.search(searchTerm) !== -1 ||
        apps.search(searchTerm) !== -1
      );
    });
    this.setState({searchTerm, filteredHosts: hosts});
  },

  changeSort(key) {
    // if we're using the key, reverse order; otherwise, set it with ascending
    if (this.state.sortKey === key) {
      const reverseDirection = (this.state.sortDirection === 'asc' ? 'desc' : 'asc');
      this.setState({sortDirection: reverseDirection});
    } else {
      this.setState({sortKey: key, sortDirection: 'asc'});
    }
  },

  sort(hosts, key, direction) {
    switch (direction) {
      case 'asc':
        return _.sortBy(hosts, (e) => e[key]);
      case 'desc':
        return _.sortBy(hosts, (e) => e[key]).reverse();
      default:
        return hosts;
    }
  },

  sortableClasses(key) {
    if (this.state.sortKey === key) {
      if (this.state.sortDirection === 'asc') {
        return "sortable-header sorting-up";
      }
      return "sortable-header sorting-down";
    }
    return "sortable-header";
  },

  render() {
    const hosts = this.sort(this.state.filteredHosts, this.state.sortKey, this.state.sortDirection);
    const hostCount = hosts.length;
    const {source} = this.props;

    return (

{hostCount ? `${hostCount} Hosts` : ''}

          <SearchBar onSearch={_.wrap(this.props.hosts, this.filterHosts)} />

                 this.changeSort('name')} className={this.sortableClasses('name')}>Hostname
                Status
                 this.changeSort('cpu')} className={this.sortableClasses('cpu')}>CPU
                 this.changeSort('load')} className={this.sortableClasses('load')}>Load
                Apps

              {
                hosts.map(({name, cpu, load, apps = []}) => {
                  return (

                      <Link to={`/sources/${source.id}/hosts/${name}`}>{name}</Link>

                      {isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
                      {isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}

                        {apps.map((app, index) => {
                          return (

                              <Link
                                style={{marginLeft: "2px"}}
                                to={{pathname: `/sources/${source.id}/hosts/${name}`, query: {app}}}>
                                {app}
                              </Link>
                              {index === apps.length - 1 ? null : ', '}

                          );
                        })}

                  );
                })
              }

    );
  },
});

const SearchBar = React.createClass({
  propTypes: {
    onSearch: PropTypes.func.isRequired,
  },

  handleChange() {
    this.props.onSearch(this.refs.searchInput.value);
  },

  render() {
    return (

        <input
          type="text"
          className="form-control"
          placeholder="Filter Hosts"
          ref="searchInput"
          onChange={this.handleChange}
        />

    );
  },
});

export default HostsTable;
<|endoftext|>


<commit_before>/* jslint node: true */
'use strict';

var View			= require('./view.js').View;
var miscUtil		= require('./misc_util.js');
var strUtil			= require('./string_util.js');
var ansi			= require('./ansi_term.js');

var assert			= require('assert');
var _				= require('lodash');
var GapBuffer		= require('gapbuffer').GapBuffer;

//
//	Notes
//	* options.tabSize can be used to resolve \t
//	* See https://github.com/dominictarr/hipster/issues/15 about insert/delete lines
//
//	Blessed
//		insertLine: CSR(top, bottom) + CUP(y, 0) + IL(1) + CSR(0, height)
//		deleteLine: CSR(top, bottom) + CUP(y, 0) + DL(1) + CSR(0, height)
//	Quick Ansi -- update only what was changed:
//	https://github.com/dominictarr/quickansi
//
//	This thread is awesome:
//	https://github.com/dominictarr/hipster/issues/15
//
//	See Atom's implementations
//	Newer TextDocument
//		https://github.com/atom/text-document
//
//	Older TextBuffer
//		http://www.oscon.com/oscon2014/public/schedule/detail/37593
//
//	Span Skip List could be used for mappings of rows/cols (display) to
//	character offsets in a buffer
//		https://github.com/atom/span-skip-list

//
//	Buffer: Actual text buffer
//	Transform: Display of soft wrap & tab expansion (e.g. tab -> ' ' * tabWidth)
//

//
//	General Design
//
//	*	Take any existing input & word wrap into lines[] preserving
//		formatting characters.
//	*	When drawing, formatting characters are processed but not shown
//		or processed directly in many cases. E.g., \n is processed but simply
//		causes us to go to our "next line" visibly.
//	*	Empty/blank lines = \n
//
exports.MultiLineEditTextView	= MultiLineEditTextView;

//
//	Some resources & comparisons
//
//	Enthral @ https://github.com/M-griffin/Enthral/blob/master/src/msg_fse.cpp
//		* Tabs are ignored
//		* Preview/reading mode processes colors, otherwise just text (e.g. editor)
//
//	x84 @ https://github.com/jquast/x84/blob/master/x84/bbs/editor.py
//
//	Syncronet
//
//
//	Projects of use/interest:
//
//	https://github.com/atom/text-buffer
//	http://danieltao.com/lazy.js/
//	http://www.jbox.dk/downloads/edit.c
//	https://github.com/slap-editor/slap
//	https://github.com/chjj/blessed
//

//	need self.skipTabs(dir): if pos='\t', skip ahead (in dir) until reg char. This can be used @ up, left, right, down

function MultiLineEditTextView(options) {

	if(!_.isBoolean(options.acceptsFocus)) {
		options.acceptsFocus = true;
	}

	if(!_.isBoolean(this.acceptsInput)) {
		options.acceptsInput = true;
	}

	View.call(this, options);

	//
	//	defualt tabWidth is 4
	//	See the following:
	//	* http://www.ansi-bbs.org/ansi-bbs2/control_chars/
	//	* http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
	//
	this.tabWidth	= _.isNumber(options.tabWidth) ? options.tabWidth : 8;

	var self = this;

	this.renderBuffer	= [];
	this.textBuffer		= new GapBuffer(1024);

	this.lines			= [];				//	a given line is text...until EOL
	this.topLineIndex	= 0;
	this.cursorPos		= { row : 0, col : 0 };	//	relative to view window
	this.renderStartIndex	= 0;

	this.getTabString = function() {
		return new Array(self.tabWidth).join(' ');
	};

	this.redrawViewableText = function() {
		var row		= self.position.row;
		var bottom	= row + self.dimens.height;
		var i		= self.topLineIndex;

		self.client.term.write(self.getSGR());

		while(i < self.renderBuffer.length && row < bottom) {
			self.client.term.write(ansi.goto(row, this.position.col));
			self.client.term.write(self.getRenderLine(self.renderBuffer[i]));
			++row;
			++i;
		}
	};

	this.wordWrap = function(line) {
		//
		//	Other implementations:
		//	* http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
		//	* http://james.padolsey.com/snippets/wordwrap-for-javascript/
		//	* http://phpjs.org/functions/wordwrap/
		//	* https://github.com/jonschlinkert/word-wrap
		//
		var re = new RegExp(
			'.{1,' + self.dimens.width + '}(\\s+|$)|\\S+?(\\s+|$)', 'g');
		return line.match(re) || [];
	};

	this.wordWrap3 = function(line, width) {
		var re = new RegExp('.{1,' + width + '}(\\s+|$)|\\S+?(\\s+|$)', 'g');
	    return line.replace(/\t/g, new Array(self.tabWidth).join('\t')).match(re) || [];
	};

	this.getRenderLine = function(line) {
		var replaceTabsRe	= new RegExp('\\t{' + (self.tabWidth - 1) + '}', 'g');
		var tabSpaces		= new Array(self.tabWidth).join(' ');
		return line.replace(replaceTabsRe, tabSpaces).replace(/\n/g, '');
	};

	this.updateRenderBuffer = function() {
		self.renderBuffer = [];
		//	:TODO: optimize this with asArray() taking the slice information
		var lines = self.textBuffer.asArray().slice(self.renderStartIndex, self.renderStartIndex + self.dimens.width * self.dimens.height)
			.join('')
			.split(/\r\n|\n|\r/g);

		var maxLines = self.dimens.height - self.position.row;

		for(var i = 0; i < lines.length && self.renderBuffer.length < maxLines; ++i) {
			if(0 === lines[i].length) {
				self.renderBuffer.push('');
			} else {
				Array.prototype.push.apply(self.renderBuffer, self.wordWrap3(lines[i] + '\n', self.dimens.width));

			}
		}
	};

	this.getReplaceTabsRegExp = function() {
		return new RegExp('\\t{' + (self.tabWidth - 1) + '}', 'g');
	};

	this.getTextBufferPosition = function(row, col) {
		var replaceTabsRe	= self.getReplaceTabsRegExp();
		var pos = 0;
		for(var r = 0; r < row; ++r) {
			if(self.renderBuffer[r].length > 0) {
				pos += self.renderBuffer[r].replace(replaceTabsRe, '\t').length;
			} else {
				pos += 1;
			}
		}

		pos += self.renderBuffer[row]
			.slice(0, Math.min(col, self.dimens.width))
			.replace(replaceTabsRe, '\t')
			.length;

		return pos;
	};

	this.getLineTextLength = function(row) {
		return self.renderBuffer[row].replace(self.getReplaceTabsRegExp(), '\t').replace(/\n/g, '').length;
		//return self.renderBuffer[row].replace(/\n/g, '').length;
	};

	this.getLineTextLengthToColumn = function(row, col) {
		return self.renderBuffer[row].replace(self.getReplaceTabsRegExp(), '\t').replace(/\n/g, '').slice(0, col).length;
	};

	this.getEndOfLinePosition = function(row) {
		row = row || self.cursorPos.row;
		return self.position.col + self.getLineTextLength(row);
	};

	this.getAbsolutePosition = function(row, col) {
		row = row || self.cursorPos.row;
		col = col || self.cursorPos.col;
		return { row : self.position.row + row, col : self.position.col + col };
	};

	this.getCharAtCursorPosition = function() {
		var pos = self.getTextBufferPosition(self.cursorPos.row, self.cursorPos.col);
		return self.textBuffer.get(pos);
	};

	this.moveCursorTo = function(row, col) {
		var absPos = self.getAbsolutePosition(row, col);
		self.client.term.write(ansi.goto(absPos.row, absPos.col));
	};

	this.scrollUp = function(count) {

	};

	this.scrollDown = function(count) {

	};

	this.cursorUp = function() {
		if(self.cursorPos.row > 0) {
			self.cursorPos.col = self.getLineTextLengthToColumn(self.cursorPos.row, self.cursorPos.col);
			self.cursorPos.row--;
			self.client.term.write(ansi.up());
		} else if(self.topLineIndex > 0) {
			//	:TODO: scroll up if possible to do so
		}

		var endOfLinePos = self.getEndOfLinePosition();
		//console.log('col=' + self.cursorPos.col + ' / eolPos=' + endOfLinePos)
		if(self.cursorPos.col > endOfLinePos) {
			self.client.term.write(ansi.right(self.cursorPos.col - endOfLinePos));
			self.cursorPos.col = endOfLinePos;
		}

		//	:TODO: if there is text @ cursor y position we're ok, otherwise,
		//	jump to the end of the line

	};

	this.cursorRight = function() {
		var rowVisibleLen = self.renderBuffer[self.cursorPos.row].replace(/\n/g, '').length;
		var max = Math.min(self.dimens.width, rowVisibleLen - 1);//selfself.getLineTextLength(self.cursorPos.row) - 1);
		console.log('self.dimens.width: ' + self.dimens.width + ' / lineLength: ' + (self.getLineTextLength(self.cursorPos.row) - 1))
		if(self.cursorPos.col < max) {
			self.cursorPos.col++;
			self.client.term.write(ansi.right());

			//	make tab adjustment if necessary
			if('\t' === self.getCharAtCursorPosition()) {
				//self.cursorPos.col++;
				self.cursorPos.col += (self.tabWidth - 1);
				self.client.term.write(ansi.right(self.tabWidth - 1));
			}
		} else {
			//	:TODO: completely backwards -- should go down
			if(self.cursorPos.row > 0) {
				self.cursorPos.row--;
				self.cursorPos.col = 0;
				self.moveCursorTo(self.cursorPos.row, self.cursorPos.col);
			}
		}

	};

	this.cursorLeft = function() {
		if(self.cursorPos.col > 0) {
			self.cursorPos.col--;
		} else {
			if(self.cursorPos.row > 0) {
				self.cursorPos.row--;
				self.cursorPos.col = self.renderBuffer[self.cursorPos.row].length;
			}
			//self.cursorUp();
		}
	};

	this.getLineIndex = function() {
		return self.topLineIndex + self.cursorPos.row;
	};

}

require('util').inherits(MultiLineEditTextView, View);

MultiLineEditTextView.prototype.setPosition = function(pos) {
	MultiLineEditTextView.super_.prototype.setPosition.call(this, pos);

};

MultiLineEditTextView.prototype.redraw = function() {
	MultiLineEditTextView.super_.prototype.redraw.call(this);

	this.redrawViewableText();
	//this.client.term.write(this.text);
};

MultiLineEditTextView.prototype.setFocus = function(focused) {

	MultiLineEditTextView.super_.prototype.setFocus.call(this, focused);

	this.moveCursorTo(this.cursorPos.row, this.cursorPos.col);
};

MultiLineEditTextView.prototype.setText = function(text) {
	//this.cursorPos.row = this.position.row + this.dimens.height;
	//this.lines = this.wordWrap(text);

	if(this.textBuffer.length > 0) {	//	:TODO: work around GapBuffer bug: if it's already empty this will cause gapEnd to be undefined
		this.textBuffer.clear();
	}

	//this.textBuffer.insertAll(0, text);
	text = text.replace(/\b/g, '');

	this.textBuffer.insertAll(0, text);

	this.updateRenderBuffer();

	console.log(this.renderBuffer)
	/*
	var idx = this.getTextBufferPosition(4, 0);
	for(var i = idx; i < idx + 4; ++i) {
		console.log(i + ' = "' + this.textBuffer.asArray()[i] + '"');
	}
	this.cursorPos.row = 15;
	this.cursorPos.col = 0;
	*/

	this.cursorPos.row = 14;
	this.cursorPos.col = 0;
};

MultiLineEditTextView.prototype.onSpecialKeyPress = function(keyName) {
	if(this.isSpecialKeyMapped('up', keyName)) {
		this.cursorUp();
	} else if(this.isSpecialKeyMapped('down', keyName)) {

	} else if(this.isSpecialKeyMapped('left', keyName)) {
		this.cursorLeft();
	} else if(this.isSpecialKeyMapped('right', keyName)) {
		this.cursorRight();
	}

	console.log(
		'row=' + this.cursorPos.row + ' / col=' + this.cursorPos.col +
		' / abs=' + JSON.stringify(this.getAbsolutePosition()) +
		': ' + this.getCharAtCursorPosition() + '( ' + this.getCharAtCursorPosition().charCodeAt(0) + ')')

	MultiLineEditTextView.super_.prototype.onSpecialKeyPress.call(this, keyName);
};
<commit_msg>* Not much<commit_after>/* jslint node: true */
'use strict';

var View			= require('./view.js').View;
var miscUtil		= require('./misc_util.js');
var strUtil			= require('./string_util.js');
var ansi			= require('./ansi_term.js');

var assert			= require('assert');
var _				= require('lodash');
var GapBuffer		= require('gapbuffer').GapBuffer;

//
//	Notes
//	* options.tabSize can be used to resolve \t
//	* See https://github.com/dominictarr/hipster/issues/15 about insert/delete lines
//
//	Blessed
//		insertLine: CSR(top, bottom) + CUP(y, 0) + IL(1) + CSR(0, height)
//		deleteLine: CSR(top, bottom) + CUP(y, 0) + DL(1) + CSR(0, height)
//	Quick Ansi -- update only what was changed:
//	https://github.com/dominictarr/quickansi
//
//	This thread is awesome:
//	https://github.com/dominictarr/hipster/issues/15
//
//	See Atom's implementations
//	Newer TextDocument
//		https://github.com/atom/text-document
//
//	Older TextBuffer
//		http://www.oscon.com/oscon2014/public/schedule/detail/37593
//
//	Span Skip List could be used for mappings of rows/cols (display) to
//	character offsets in a buffer
//		https://github.com/atom/span-skip-list

//
//	Buffer: Actual text buffer
//	Transform: Display of soft wrap & tab expansion (e.g. tab -> ' ' * tabWidth)
//

//
//	General Design
//
//	*	Take any existing input & word wrap into lines[] preserving
//		formatting characters.
//	*	When drawing, formatting characters are processed but not shown
//		or processed directly in many cases. E.g., \n is processed but simply
//		causes us to go to our "next line" visibly.
//	*	Empty/blank lines = \n
//
exports.MultiLineEditTextView	= MultiLineEditTextView;

//
//	Some resources & comparisons
//
//	Enthral @ https://github.com/M-griffin/Enthral/blob/master/src/msg_fse.cpp
//		* Tabs are ignored
//		* Preview/reading mode processes colors, otherwise just text (e.g. editor)
//
//	x84 @ https://github.com/jquast/x84/blob/master/x84/bbs/editor.py
//
//	Syncronet
//
//
//	Projects of use/interest:
//
//	https://github.com/atom/text-buffer
//	http://danieltao.com/lazy.js/
//	http://www.jbox.dk/downloads/edit.c
//	https://github.com/slap-editor/slap
//	https://github.com/chjj/blessed
//

//	need self.skipTabs(dir): if pos='\t', skip ahead (in dir) until reg char. This can be used @ up, left, right, down

function MultiLineEditTextView(options) {

	if(!_.isBoolean(options.acceptsFocus)) {
		options.acceptsFocus = true;
	}

	if(!_.isBoolean(this.acceptsInput)) {
		options.acceptsInput = true;
	}

	View.call(this, options);

	//
	//	defualt tabWidth is 4
	//	See the following:
	//	* http://www.ansi-bbs.org/ansi-bbs2/control_chars/
	//	* http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
	//
	this.tabWidth	= _.isNumber(options.tabWidth) ? options.tabWidth : 8;

	var self = this;

	this.renderBuffer	= [];
	this.textBuffer		= new GapBuffer(1024);

	this.lines			= [];				//	a given line is text...until EOL
	this.topLineIndex	= 0;
	this.cursorPos		= { row : 0, col : 0 };	//	relative to view window
	this.renderStartIndex	= 0;

	this.getTabString = function() {
		return new Array(self.tabWidth).join(' ');
	};

	this.redrawViewableText = function() {
		var row		= self.position.row;
		var bottom	= row + self.dimens.height;
		var i		= self.topLineIndex;

		self.client.term.write(self.getSGR());

		while(i < self.renderBuffer.length && row < bottom) {
			self.client.term.write(ansi.goto(row, this.position.col));
			self.client.term.write(self.getRenderLine(self.renderBuffer[i]));
			++row;
			++i;
		}
	};

	this.wordWrap = function(line) {
		//
		//	Other implementations:
		//	* http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
		//	* http://james.padolsey.com/snippets/wordwrap-for-javascript/
		//	* http://phpjs.org/functions/wordwrap/
		//	* https://github.com/jonschlinkert/word-wrap
		//
		var re = new RegExp(
			'.{1,' + self.dimens.width + '}(\\s+|$)|\\S+?(\\s+|$)', 'g');
		return line.match(re) || [];
	};

	this.wordWrap3 = function(line, width) {
		var re = new RegExp('.{1,' + width + '}(\\s+|$)|\\S+?(\\s+|$)', 'g');
	    return line.replace(/\t/g, new Array(self.tabWidth).join('\t')).match(re) || [];
	};

	this.getRenderLine = function(line) {
		var replaceTabsRe	= new RegExp('\\t{' + (self.tabWidth - 1) + '}', 'g');
		var tabSpaces		= new Array(self.tabWidth).join(' ');
		return line.replace(replaceTabsRe, tabSpaces).replace(/\n/g, '');
	};

	this.updateRenderBuffer = function() {
		self.renderBuffer = [];
		//	:TODO: optimize this with asArray() taking the slice information
		var lines = self.textBuffer.asArray().slice(self.renderStartIndex, self.renderStartIndex + self.dimens.width * self.dimens.height)
			.join('')
			.split(/\r\n|\n|\r/g);

		var maxLines = self.dimens.height - self.position.row;

		for(var i = 0; i < lines.length && self.renderBuffer.length < maxLines; ++i) {
			if(0 === lines[i].length) {
				self.renderBuffer.push('');
			} else {
				Array.prototype.push.apply(self.renderBuffer, self.wordWrap3(lines[i] + '\n', self.dimens.width));

			}
		}
	};

	this.getReplaceTabsRegExp = function() {
		return new RegExp('\\t{' + (self.tabWidth - 1) + '}', 'g');
	};

	this.getTextBufferPosition = function(row, col) {
		var replaceTabsRe	= self.getReplaceTabsRegExp();
		var pos = 0;
		for(var r = 0; r < row; ++r) {
			if(self.renderBuffer[r].length > 0) {
				pos += self.renderBuffer[r].replace(replaceTabsRe, '\t').length;
			} else {
				pos += 1;
			}
		}

		pos += self.renderBuffer[row]
			.slice(0, Math.min(col, self.dimens.width))
			.replace(replaceTabsRe, '\t')
			.length;

		return pos;
	};

	this.getLineTextLength = function(row) {
		return self.renderBuffer[row].replace(self.getReplaceTabsRegExp(), '\t').replace(/\n/g, '').length;
		//return self.renderBuffer[row].replace(/\n/g, '').length;
	};

	this.getLineTextLengthToColumn = function(row, col) {
		return self.renderBuffer[row].replace(self.getReplaceTabsRegExp(), '\t').replace(/\n/g, '').slice(0, col).length;
	};

	this.getEndOfLinePosition = function(row) {
		row = row || self.cursorPos.row;
		return self.position.col + self.getLineTextLength(row);
	};

	this.getAbsolutePosition = function(row, col) {
		row = row || self.cursorPos.row;
		col = col || self.cursorPos.col;
		return { row : self.position.row + row, col : self.position.col + col };
	};

	this.getCharAtCursorPosition = function() {
		var pos = self.getTextBufferPosition(self.cursorPos.row, self.cursorPos.col);
		return self.textBuffer.get(pos);
	};

	this.moveCursorTo = function(row, col) {
		var absPos = self.getAbsolutePosition(row, col);
		self.client.term.write(ansi.goto(absPos.row, absPos.col));
	};

	this.scrollUp = function(count) {

	};

	this.scrollDown = function(count) {

	};

	this.cursorUp = function() {
		if(self.cursorPos.row > 0) {
			self.cursorPos.col = self.getLineTextLengthToColumn(self.cursorPos.row, self.cursorPos.col);
			self.cursorPos.row--;
			self.client.term.write(ansi.up());
		} else if(self.topLineIndex > 0) {
			//	:TODO: scroll up if possible to do so
		}

		var endOfLinePos = self.getEndOfLinePosition();
		//console.log('col=' + self.cursorPos.col + ' / eolPos=' + endOfLinePos)
		if(self.cursorPos.col > endOfLinePos) {
			self.client.term.write(ansi.right(self.cursorPos.col - endOfLinePos));
			self.cursorPos.col = endOfLinePos;
		}

		//	:TODO: if there is text @ cursor y position we're ok, otherwise,
		//	jump to the end of the line

	};

	this.cursorDown = function() {
		var lastRow = self.dimens.height - self.position.row;	//	:TODO: should be calculated elsewhere
		if(self.cursorPos.row > lastRow) {
			self.cursorPos.col = self.getLineTextLengthToColumn(self.cursorPos.row, self.cursorPos.col);
			self.cursorPos.row++;
			self.client.term.write(ansi.down());
		}
	};

	this.cursorRight = function() {
		var rowVisibleLen = self.renderBuffer[self.cursorPos.row].replace(/\n/g, '').length;
		var max = Math.min(self.dimens.width, rowVisibleLen - 1);//selfself.getLineTextLength(self.cursorPos.row) - 1);
		console.log('self.dimens.width: ' + self.dimens.width + ' / lineLength: ' + (self.getLineTextLength(self.cursorPos.row) - 1))
		if(self.cursorPos.col < max) {
			self.cursorPos.col++;
			self.client.term.write(ansi.right());

			//	make tab adjustment if necessary
			if('\t' === self.getCharAtCursorPosition()) {
				//self.cursorPos.col++;
				self.cursorPos.col += (self.tabWidth - 1);
				self.client.term.write(ansi.right(self.tabWidth - 1));
			}
		} else {
			//	:TODO: completely backwards -- should go down
			if(self.cursorPos.row > 0) {
				self.cursorPos.row--;
				self.cursorPos.col = 0;
				self.moveCursorTo(self.cursorPos.row, self.cursorPos.col);
			}
		}
	};

	this.cursorUpOneLine = function() {
		if(self.cursorPos.row > 0) {
			self.cursorPos.row--;
			self.cursorPos.col = 0;
			self.moveCursorTo(self.cursorPos.row, self.cursorPos.col);
		} else {
			//	can we scroll up?
		}
	};

	this.cursorDownOneLine = function() {
	};

	this.cursorLeft = function() {
		if(self.cursorPos.col > 0) {
			self.cursorPos.col--;
		} else {
			if(self.cursorPos.row > 0) {
				self.cursorPos.row--;
				self.cursorPos.col = self.renderBuffer[self.cursorPos.row].length;
			}
			//self.cursorUp();
		}
	};

	this.getLineIndex = function() {
		return self.topLineIndex + self.cursorPos.row;
	};

}

require('util').inherits(MultiLineEditTextView, View);

MultiLineEditTextView.prototype.setPosition = function(pos) {
	MultiLineEditTextView.super_.prototype.setPosition.call(this, pos);

};

MultiLineEditTextView.prototype.redraw = function() {
	MultiLineEditTextView.super_.prototype.redraw.call(this);

	this.redrawViewableText();
	//this.client.term.write(this.text);
};

MultiLineEditTextView.prototype.setFocus = function(focused) {

	MultiLineEditTextView.super_.prototype.setFocus.call(this, focused);

	this.moveCursorTo(this.cursorPos.row, this.cursorPos.col);
};

MultiLineEditTextView.prototype.setText = function(text) {
	//this.cursorPos.row = this.position.row + this.dimens.height;
	//this.lines = this.wordWrap(text);

	if(this.textBuffer.length > 0) {	//	:TODO: work around GapBuffer bug: if it's already empty this will cause gapEnd to be undefined
		this.textBuffer.clear();
	}

	//this.textBuffer.insertAll(0, text);
	text = text.replace(/\b/g, '');

	this.textBuffer.insertAll(0, text);

	this.updateRenderBuffer();

	console.log(this.renderBuffer)
	/*
	var idx = this.getTextBufferPosition(4, 0);
	for(var i = idx; i < idx + 4; ++i) {
		console.log(i + ' = "' + this.textBuffer.asArray()[i] + '"');
	}
	this.cursorPos.row = 15;
	this.cursorPos.col = 0;
	*/

	this.cursorPos.row = 14;
	this.cursorPos.col = 0;
};

MultiLineEditTextView.prototype.onSpecialKeyPress = function(keyName) {
	if(this.isSpecialKeyMapped('up', keyName)) {
		this.cursorUp();
	} else if(this.isSpecialKeyMapped('down', keyName)) {
		this.cursorDown();
	} else if(this.isSpecialKeyMapped('left', keyName)) {
		this.cursorLeft();
	} else if(this.isSpecialKeyMapped('right', keyName)) {
		this.cursorRight();
	}

	console.log(
		'row=' + this.cursorPos.row + ' / col=' + this.cursorPos.col +
		' / abs=' + JSON.stringify(this.getAbsolutePosition()) +
		': ' + this.getCharAtCursorPosition() + '( ' + this.getCharAtCursorPosition().charCodeAt(0) + ')')

	MultiLineEditTextView.super_.prototype.onSpecialKeyPress.call(this, keyName);
};
<|endoftext|>


<commit_before>/*globals console:false, Parsers:false*/
'use strict';

Polymer('seed-harvester', {
  db: '',
  progress: '',
  delimiter: ',',
  ready: function() {
    this.seed = this.seed || {
      data: '',
      url: '',
      replace: true,
      language: 'javascript'
    };

    this.items = this.items || [];
  },

  openEditor: function() {
    this.editing = {};

    Object.keys(this.seed).forEach(function(key) {
      this.editing[key] = this.seed[key];
    }.bind(this));
  },

  closeEditor: function() {
    Object.keys(this.editing).forEach(function(key) {
      this.seed[key] = this.editing[key];
    }.bind(this));

    this.editing = null;

    this.fire('seed-changed');
  },

  cancelEditor: function() {
    this.editing = null;
  },

  onFetchError: function(e) {
    console.error(e);
    this.error = 'Response code: ' + e.xhr.status + ' from ' + e.request.originalURL;
  },

  clearItems: function() {
    this.fire('clear-items');
  },

  addItems: function(items) {
    console.log('adding items', items ? items.length : items);

    if (!items || !items.length) {
      return;
    }

    var seeds = items.map(function(item) {
      return { seed: item };
    });

    this.fire('add-items', seeds);
  },

  saveSeeds: function(event) {
    event.preventDefault();

    this.closeEditor();
    this.plantSeeds();
  },

  plantSeeds: function() {
    var data;

    this.error = null;

    switch (this.seed.language) {
      case 'javascript':
        var harvester = new Function(this.seed.data); // TODO: wrap in closure?
        var result = harvester();

        result.then(this.addItems.bind(this), this.onFetchError.bind(this));
      break;

      case 'csv':
      // TODO: set enclosure, etc
      var parser = new Parsers.CSV({
        delimiter: this.delimiter
      });

      // TODO: normalise field names

      data = parser.parse(this.seed.data).results.rows;
      // TODO: log errors, preview?

      this.addItems(data);
      break;

      case 'json':
      data = JSON.parse(this.seed.data);

      this.addItems(data);
      break;
    }
  }
});
<commit_msg>gloabl variable<commit_after>/*globals console:false, Parsers:false*/
'use strict';

Polymer('seed-harvester', {
  db: '',
  progress: '',
  delimiter: ',',
  ready: function() {
    this.seed = this.seed || {
      data: '',
      url: '',
      replace: true,
      language: 'javascript'
    };

    this.items = this.items || [];
  },

  openEditor: function() {
    this.editing = {};

    Object.keys(this.seed).forEach(function(key) {
      this.editing[key] = this.seed[key];
    }.bind(this));
  },

  closeEditor: function() {
    Object.keys(this.editing).forEach(function(key) {
      this.seed[key] = this.editing[key];
    }.bind(this));

    this.editing = null;

    this.fire('seed-changed');
  },

  cancelEditor: function() {
    this.editing = null;
  },

  onFetchError: function(e) {
    console.error(e);
    this.error = 'Response code: ' + e.xhr.status + ' from ' + e.request.originalURL;
  },

  clearItems: function() {
    this.fire('clear-items');
  },

  addItems: function(items) {
    console.log('adding items', items ? items.length : items);

    if (!items || !items.length) {
      return;
    }

    var seeds = items.map(function(item) {
      return { seed: item };
    });

    this.fire('add-items', seeds);
  },

  saveSeeds: function(event) {
    event.preventDefault();

    this.closeEditor();
    this.plantSeeds();
  },

  plantSeeds: function() {
    var data;

    this.error = null;

    switch (this.seed.language) {
      case 'javascript':
        var harvester = new Function(this.seed.data); // TODO: wrap in closure?
        var result = harvester();

        result.then(this.addItems.bind(this), this.onFetchError.bind(this));
      break;

      case 'csv':
      // TODO: set enclosure, etc
      var parser = new CSVParser({
        delimiter: this.delimiter
      });

      // TODO: normalise field names

      data = parser.parse(this.seed.data).results.rows;
      // TODO: log errors, preview?

      this.addItems(data);
      break;

      case 'json':
      data = JSON.parse(this.seed.data);

      this.addItems(data);
      break;
    }
  }
});
<|endoftext|>


<commit_before>const http = require('http');
exports.call = function(db, url, target) {
    return new Promise((resolve, reject) => {
        http.get(url, (res) => {
            const file = new db.File({path: target});
            const size = res.headers['content-length'];
            const mimeType = res.headers['content-type'];
            if (size) {
                resolve(file.upload({mimeType, size, type: 'stream', data: res}));
                return;
            }
            const chunks = [];
            res.on('data', chunks.push.bind(chunks));
            res.on('end', () => {
                const buf = Buffer.concat(chunks);
                resolve(file.upload({mimeType, size: buf.length, type: 'buffer', data: buf, force: true}));
            });
        }).on('error', (e) => {
            reject(e);
        });
    });
};<commit_msg>added DL script<commit_after>const http = require('http');

/**
 * Fetches a URL an saves it to a Baqend file.
 *
 * Usage example:
 * download = require('./download');
 * return download.toFile(db, "http://...test.jpg", "/www/image.jpg", 0);
 *
 * @param db Baqend db to use
 * @param url the URL that should be downloaded
 * @param target the name of the Baqend file to save it into
 * @param maxRetries number of retries if the response is a 4xx or 5xx status code
 * @returns {Promise} a Promise that resolved to the uploaded file
 */
exports.call = function(db, url, target, maxRetries = 10) {
    return new Promise((resolve, reject) => {
        http.get(url, (res) => {
            const file = new db.File({path: target});
            const size = res.headers['content-length'];
            const mimeType = res.headers['content-type'];

            //Retry on error
            if (res.statusCode >= 400 && res.statusCode < 600) {
                if(maxRetries <= 0) {
                    reject(new Error("Maximum number of retries reached without success"));
                } else {
                    resolve(get(db, url, target, maxRetries - 1));
                }
                return;
            }

            //Full response
            if (size) {
                resolve(file.upload({mimeType, size, type: 'stream', data: res}));
                return;
            }

            //Or chunked encoding
            const chunks = [];
            res.on('data', chunks.push.bind(chunks));
            res.on('end', () => {
                const buf = Buffer.concat(chunks);
                resolve(file.upload({mimeType, size: buf.length, type: 'buffer', data: buf, force: true}));
            });
        }).on('error', (e) => {
            reject(e);
        });
    });
};<|endoftext|>


<commit_before>'use strict'

// TODO : All the 2d gui work should be abstracted
// TODO : research the efficiency of updating html text elements

var PAUSED_MENU = new PausedMenu()

function PausedMenu() {
    this.__init__()
}

PausedMenu.prototype = {

    button_resume: null,
    button_settings: null,
    button_help_controls: null,
    button_log_out: null,

    __init__: function() {
        // this.x_coordinate = document.getElementById('x_coordinate')

        this.button_resume = document.getElementById('button_resume')
        this.button_settings = document.getElementById('button_settings')
        this.button_help_controls = document.getElementById('button_help_controls')
        this.button_log_out = document.getElementById('button_log_out')
    },

    provide_player_object: function(player_object) {
        this.player = player_object
    },

    make_visible: function() {
        this.button_resume.style.visibility = 'visible'
        this.button_help_controls.style.visibility = 'hidden'
        if (this.player.logged_in) {
            this.button_settings.style.visibility = 'hidden'
            this.button_log_out.style.visibility = 'hidden'
        }
    },

    make_invisible: function() {
        this.button_resume.style.visibility = 'hidden'
        this.button_settings.style.visibility = 'hidden'
        this.button_help_controls.style.visibility = 'hidden'
        this.button_log_out.style.visibility = 'hidden'
    }
}
<commit_msg>adding 2D gui overlay 13<commit_after>'use strict'

// TODO : All the 2d gui work should be abstracted
// TODO : research the efficiency of updating html text elements

function PausedMenu() {
    this.__init__()
}

PausedMenu.prototype = {

    button_resume: null,
    button_settings: null,
    button_help_controls: null,
    button_log_out: null,

    __init__: function() {
        // this.x_coordinate = document.getElementById('x_coordinate')

        this.button_resume = document.getElementById('button_resume')
        this.button_settings = document.getElementById('button_settings')
        this.button_help_controls = document.getElementById('button_help_controls')
        this.button_log_out = document.getElementById('button_log_out')
    },

    provide_player_object: function(player_object) {
        this.player = player_object
    },

    make_visible: function() {
        this.button_resume.style.visibility = 'visible'
        this.button_help_controls.style.visibility = 'hidden'
        if (this.player.logged_in) {
            this.button_settings.style.visibility = 'hidden'
            this.button_log_out.style.visibility = 'hidden'
        }
    },

    make_invisible: function() {
        this.button_resume.style.visibility = 'hidden'
        this.button_settings.style.visibility = 'hidden'
        this.button_help_controls.style.visibility = 'hidden'
        this.button_log_out.style.visibility = 'hidden'
    }
}

var PAUSED_MENU = new PausedMenu()
<|endoftext|>


<commit_before>var util = require('../util'),
    _ = util.lodash,
    fileType = require('file-type'),
    mimeType = require('mime-types'),
    mimeFormat = require('mime-format'),
    httpReasons = require('http-reasons'),
    LJSON = require('liquid-json'),
    Property = require('./property').Property,
    Request = require('./request').Request,
    Header = require('./header').Header,
    CookieList = require('./cookie-list').CookieList,
    HeaderList = require('./header-list').HeaderList,

    /**
     * @private
     * @const
     * @type {string}
     */
    E = '',

    /**
     * @private
     * @const
     * @type {string}
     */
    DOT = '.',

    /**
     * @private
     * @const
     * @type {String}
     */
    HEADER = 'header',

    /**
     * @private
     * @const
     * @type {String}
     */
    BODY = 'body',

    /**
     * @private
     * @const
     * @type {String}
     */
    GZIP = 'gzip',

    /**
     * @private
     * @const
     * @type {String}
     */
    CONTENT_ENCODING = 'Content-Encoding',

    /**
     * @private
     * @const
     * @type {String}
     */
    CONTENT_LENGTH = 'Content-Length',

    /**
     * @private
     * @const
     * @type {string}
     */
    DEFAULT_RESPONSE_FILENAME = 'response',

    /**
     * @private
     * @const
     * @type {string}
     */
    UTF8 = 'utf8',

    /**
     * @private
     * @const
     * @type {string}
     */
    BUFFER = 'Buffer',

    /**
     * @private
     * @const
     * @type {string}
     */
    FUNCTION = 'function',

    /**
     * @private
     * @const
     * @type {String}
     */
    HTTP_X_X = 'HTTP/X.X ',

    /**
     * @private
     * @type {Boolean}
     */
    supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),

    /**
     * Normalises an input Buffer or buffer.toJSON() into a Buffer or ArrayBuffer.
     *
     * @private
     * @param {Buffer|Object} stream - An instance of Buffer of an object representation of Buffer(Buffer.toJSON())
     * @returns {Buffer|ArrayBuffer|undefined}
     */
    normaliseStream = function (stream) {
        if (stream && stream.type === BUFFER && _.isArray(stream.data)) {
            // @todo Add tests for Browser environments, where ArrayBuffer is returned instead of Buffer
            return typeof Buffer === FUNCTION ? new Buffer(stream.data) : new Uint8Array(stream.data).buffer;
        }
        return stream;
    },

    Response; // constructor

/**
 * @typedef Response~definition
 * @property {Number} code - define the response code
 * @property {String=} [reason] - optionally, if the response has a non-standard response code reason, provide it here
 * @property {Array<Header~definition>} [header]
 * @property {Array<Cookie~definition>} [cookie]
 * @property {String} [body]
 * @property {Buffer|ByteArray} [stream]
 * @property {Number} responseTime
 *
 * @todo pluralise `header`, `cookie`
 */
_.inherit((

    /**
     * Response holds data related to the request body. By default, it provides a nice wrapper for url-encoded,
     * form-data, and raw types of request bodies.
     *
     * @constructor
     * @extends {Property}
     *
     * @param {Response~definition} options
     */
    Response = function PostmanResponse (options) {
        // this constructor is intended to inherit and as such the super constructor is required to be executed
        Response.super_.apply(this, arguments);
        this.update(options || {});
    }), Property);

_.assign(Response.prototype, /** @lends Response.prototype */ {
    update: function (options) {
        // options.stream accepts new Buffer() as well as new Buffer().toJSON()
        var stream = normaliseStream(options.stream);

        _.mergeDefined((this._details = _.clone(httpReasons.lookup(options.code))), {
            name: _.choose(options.reason, options.status),
            code: options.code,
            standardName: this._details.name
        });

        _.mergeDefined(this, /** @lends Response.prototype */ {
            /**
             * @type {Request}
             */
            originalRequest: options.originalRequest ? new Request(options.originalRequest) : undefined,

            /**
             * @type {String}
             * @deprecated use .reason()
             */
            status: this._details.name,

            /**
             * @type {Number}
             */
            code: options.code,

            /**
             * @type {HeaderList}
             */
            headers: new HeaderList(this, options.header),

            /**
             * @type {String}
             */
            body: options.body,

            /**
             * @private
             *
             * @type {Buffer|UInt8Array}
             */
            stream: options.body && !_.isString(options.body) && _.isObject(options.body) ?
                options.body : stream,

            /**
             * @type {CookieList}
             */
            cookies: new CookieList(this, options.cookie),

            /**
             * Time taken for the request to complete.
             *
             * @type {Number}
             */
            responseTime: options.responseTime,

            /**
             * @private
             * @type {Number}
             */
            responseSize: stream && stream.byteLength
        });
    }
});

_.assign(Response.prototype, /** @lends Response.prototype */ {
    /**
     * Defines that this property requires an ID field
     * @private
     * @readOnly
     */
    _postman_requiresId: true,

    /**
     * Get the http response reason phrase based on the current response code.
     *
     * @returns {String|undefined}
     */
    reason: function () {
        return this.status || httpReasons.lookup(this.code).name;
    },

    /**
     * Creates a JSON representation of the current response details, and returns it.
     *
     * @returns {Object} A set of response details, including the custom server reason.
     * @private
     */
    details: function () {
        if (!this._details || this._details.code !== this.code) {
            this._details = _.clone(httpReasons.lookup(this.code));
            this._details.code = this.code;
            this._details.standardName = this._details.name;
        }
        return _.clone(this._details);
    },

    /**
     * Get the response body as a string/text.
     *
     * @returns {String|undefined}
     */
    text: function () {
        return (this.stream ? util.bufferOrArrayBufferToString(this.stream, this.mime().charset) : this.body);
    },

    /**
     * Get the response body as a JavaScript object. Note that it throws an error if the response is not a valid JSON
     *
     * @param {Function=} [reviver]
     * @param {Boolean} [strict=false] Specify whether JSON parsing will be strict. This will fail on comments and BOM
     * @example
     * // assuming that the response is stored in a collection instance `myCollection`
     * var response = myCollection.items.one('some request').responses.idx(0),
     *     jsonBody;
     * try {
     *     jsonBody = response.json();
     * }
     * catch (e) {
     *     console.log("There was an error parsing JSON ", e);
     * }
     * // log the root-level keys in the response JSON.
     * console.log('All keys in json response: ' + Object.keys(json));
     *
     * @returns {Object}
     */
    json: function (reviver, strict) {
        return LJSON.parse(this.text(), reviver, strict);
    },

    /**
     * @private
     *
     * @param {String|Header} contentType - override the content-type of the response using this parameter before
     * computing the mime  type.
     * @param {String|Header} contentDisposition - override the content-disposition of the response before calculating
     * mime info.
     *
     * @returns {Object}
     *
     * @note example object returned
     * {
     *   source: string // 'header', 'content', 'default' or 'forced'
     *   type: normalised.type, // sanitised mime type base
     *   format: normalised.format, // format specific to the type returned
     *   name: DEFAULT_RESPONSE_FILENAME, // @todo - get from disposition
     *   ext: mimeType.extension(normalised.source) || E, // file extension from sanitised content type
     *   filename: name + ext,
     *   // also storing some meta info for possible debugging
     *   _originalContentType: type, // the user provided mime type
     *   _sanitisedContentType: normalised.source, // sanitised mime type
     *   _accuratelyDetected: !normalised.orphan // this being true implies worse case (raw render)
     *   detected: {} // same as root object, but based on what is detected from content
     * }
     *
     * @todo write unit tests
     */
    mime: function (contentType, contentDisposition) {
        var detected = fileType(this.stream || this.body), // detect the mime from response body
            source = 'forced',
            mime;

        // if no overrides provided, we take the values from headers
        !contentDisposition && (contentDisposition = this.headers.one('content-disposition'));
        if (!contentType) {
            contentType = this.headers.one('content-type') && this.headers.one('content-type').value;
            source = HEADER;
        }

        // if content type is not found in header, we fall back to the mime type detected
        if (!contentType && detected) {
            contentType = detected.mime;
            source = BODY;
        }

        // if still not found, then we use default text
        if (!contentType) {
            contentType = 'text/plain';
            source = 'default';
        }

        mime = Response.mimeInfo(contentType, contentDisposition);
        mime.source = source;
        mime.detected = detected && Response.mimeInfo(detected.mime, contentDisposition);

        return mime;
    },

    /**
     * Converts the response to a dataURI that can be used for storage or serialisation. The data URI is formed using
     * the following syntax `data:<content-type>;baseg4, <base64-encoded-body>`.
     *
     * @returns {String}
     * @todo write unit tests
     */
    dataURI: function () {
        var mime = this.mime();

        // if there is no mime detected, there is no accurate way to render this thing
        if (!this.mime) {
            return E;
        }

        // we create the body string first from stream and then fallback to body
        return 'data:' + mime._sanitisedContentType + ';base64, ' + ((!_.isNil(this.stream) &&
            util.bufferOrArrayBufferToBase64(this.stream)) || (!_.isNil(this.body) && util.btoa(this.body)) || E);
    },

    /**
     * Get the response size by computing the same from content length header or using the actual response body.
     *
     * @returns {Number}
     * @todo write unit tests
     */
    size: function () {
        var sizeInfo = {
                body: 0,
                header: 0,
                total: 0
            },

            contentEncoding = this.headers.get(CONTENT_ENCODING),
            contentLength = this.headers.get(CONTENT_LENGTH),
            isCompressed = false,
            byteLength;

        // if server sent encoded data, we should first try deriving length from headers
        if (_.isString(contentEncoding)) {
            // desensitise case of content encoding
            contentEncoding = contentEncoding.toLowerCase();
            // eslint-disable-next-line lodash/prefer-includes
            isCompressed = (contentEncoding.indexOf('gzip') > -1) || (contentEncoding.indexOf('deflate') > -1);
        }

        // if 'Content-Length' header is present and encoding is of type gzip/deflate, we take body as declared by
        // server. else we need to compute the same.
        if (contentLength && isCompressed && util.isNumeric(contentLength)) {
            sizeInfo.body = _.parseInt(contentLength, 10);
        }
        // if there is a stream defined which looks like buffer, use it's data and move on
        else if (this.stream) {
            byteLength = this.stream.byteLength;
            sizeInfo.body = util.isNumeric(byteLength) ? byteLength : 0;
        }
        // otherwise, if body is defined, we try get the true length of the body
        else if (!_.isNil(this.body)) {
            sizeInfo.body = supportsBuffer ? Buffer.byteLength(this.body.toString()) : this.body.toString().length;
        }

        // size of header is added
        // https://tools.ietf.org/html/rfc7230#section-3.1.2
        // status-line = HTTP-version SP status-code SP reason-phrase CRLF
        sizeInfo.header = (HTTP_X_X + this.code + ' ' + this.reason() + '\r\n').length + this.headers.contentSize();

        // compute the approximate total body size by adding size of header and body
        sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header || 0);
        return sizeInfo;
    },

    /**
     * Returns the response encoding defined as header or detected from body.
     *
     * @private
     * @returns {Object} - {format: string, source: string}
     */
    encoding: function () {
        var contentEncoding = this.headers.get(CONTENT_ENCODING),
            body = this.stream || this.body,
            source;

        if (contentEncoding) {
            source = HEADER;
        }

        // if the encoding is not found, we check
        else if (body) { // @todo add detection for deflate
            // eslint-disable-next-line lodash/prefer-matches
            if (body[0] === 0x1F && body[1] === 0x8B && body[2] === 0x8) {
                contentEncoding = GZIP;
            }

            if (contentEncoding) {
                source = BODY;
            }
        }

        return {
            format: contentEncoding,
            source: source
        };
    }
});

_.assign(Response, /** @lends Response */ {
    /**
     * Defines the name of this property for internal use.
     * @private
     * @readOnly
     * @type {String}
     */
    _postman_propertyName: 'Response',

    /**
     * Check whether an object is an instance of {@link ItemGroup}.
     *
     * @param {*} obj
     * @returns {Boolean}
     */
    isResponse: function (obj) {
        return Boolean(obj) && ((obj instanceof Response) ||
            _.inSuperChain(obj.constructor, '_postman_propertyName', Response._postman_propertyName));
    },

    /**
     * Converts the response object from the request module to the postman responseBody format
     *
     * @param {Object} response The response object, as received from the request module
     * @param {Object} cookies
     * @returns {Object} The transformed responseBody
     * @todo Add a key: `originalRequest` to the returned object as well, referring to response.request
     */
    createFromNode: function (response, cookies) {
        return new Response({
            cookie: cookies,
            body: response.body.toString(),
            stream: response.body,
            header: response.headers,
            code: response.statusCode,
            status: response.statusMessage,
            responseTime: response.elapsedTime
        });
    },

    /**
     * @private
     *
     * @param {String|Header} type
     * @param {String|Header} disposition
     * @returns {Object}
     *
     * @todo write unit tests
     */
    mimeInfo: function (type, disposition) {
        Header.isHeader(type) && (type = type.value);
        Header.isHeader(disposition) && (disposition = disposition.value);

        // validate that the content type exists
        if (!(type && _.isString(type))) { return; }

        var normalised = mimeFormat.lookup(type),
            info = {};

        _.assign(info, {
            type: normalised.type, // sanitised mime type base
            format: normalised.format, // format specific to the type returned
            name: DEFAULT_RESPONSE_FILENAME, // @todo - get from disposition
            ext: mimeType.extension(normalised.source) || E, // file extension from sanitised content type
            charset: normalised.charset || UTF8,

            // also storing some meta info for possible debugging
            _originalContentType: type, // the user provided mime type
            _sanitisedContentType: normalised.source, // sanitised mime type
            _accuratelyDetected: !normalised.orphan // this being true implies worse case (raw render)
        });

        // build the file name from extension
        info.filename = info.name;
        info.ext && (info.filename += (DOT + info.ext));

        return info;
    }
});

module.exports = {
    Response: Response
};
<commit_msg>Prevented _details from showing up in Response#toJSON :fire:<commit_after>var util = require('../util'),
    _ = util.lodash,
    fileType = require('file-type'),
    mimeType = require('mime-types'),
    mimeFormat = require('mime-format'),
    httpReasons = require('http-reasons'),
    LJSON = require('liquid-json'),
    Property = require('./property').Property,
    PropertyBase = require('./property-base').PropertyBase,
    Request = require('./request').Request,
    Header = require('./header').Header,
    CookieList = require('./cookie-list').CookieList,
    HeaderList = require('./header-list').HeaderList,

    /**
     * @private
     * @const
     * @type {string}
     */
    E = '',

    /**
     * @private
     * @const
     * @type {string}
     */
    DOT = '.',

    /**
     * @private
     * @const
     * @type {String}
     */
    HEADER = 'header',

    /**
     * @private
     * @const
     * @type {String}
     */
    BODY = 'body',

    /**
     * @private
     * @const
     * @type {String}
     */
    GZIP = 'gzip',

    /**
     * @private
     * @const
     * @type {String}
     */
    CONTENT_ENCODING = 'Content-Encoding',

    /**
     * @private
     * @const
     * @type {String}
     */
    CONTENT_LENGTH = 'Content-Length',

    /**
     * @private
     * @const
     * @type {string}
     */
    DEFAULT_RESPONSE_FILENAME = 'response',

    /**
     * @private
     * @const
     * @type {string}
     */
    UTF8 = 'utf8',

    /**
     * @private
     * @const
     * @type {string}
     */
    BUFFER = 'Buffer',

    /**
     * @private
     * @const
     * @type {string}
     */
    FUNCTION = 'function',

    /**
     * @private
     * @const
     * @type {String}
     */
    HTTP_X_X = 'HTTP/X.X ',

    /**
     * @private
     * @type {Boolean}
     */
    supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),

    /**
     * Normalises an input Buffer or buffer.toJSON() into a Buffer or ArrayBuffer.
     *
     * @private
     * @param {Buffer|Object} stream - An instance of Buffer of an object representation of Buffer(Buffer.toJSON())
     * @returns {Buffer|ArrayBuffer|undefined}
     */
    normaliseStream = function (stream) {
        if (stream && stream.type === BUFFER && _.isArray(stream.data)) {
            // @todo Add tests for Browser environments, where ArrayBuffer is returned instead of Buffer
            return typeof Buffer === FUNCTION ? new Buffer(stream.data) : new Uint8Array(stream.data).buffer;
        }
        return stream;
    },

    Response; // constructor

/**
 * @typedef Response~definition
 * @property {Number} code - define the response code
 * @property {String=} [reason] - optionally, if the response has a non-standard response code reason, provide it here
 * @property {Array<Header~definition>} [header]
 * @property {Array<Cookie~definition>} [cookie]
 * @property {String} [body]
 * @property {Buffer|ByteArray} [stream]
 * @property {Number} responseTime
 *
 * @todo pluralise `header`, `cookie`
 */
_.inherit((

    /**
     * Response holds data related to the request body. By default, it provides a nice wrapper for url-encoded,
     * form-data, and raw types of request bodies.
     *
     * @constructor
     * @extends {Property}
     *
     * @param {Response~definition} options
     */
    Response = function PostmanResponse (options) {
        // this constructor is intended to inherit and as such the super constructor is required to be executed
        Response.super_.apply(this, arguments);
        this.update(options || {});
    }), Property);

_.assign(Response.prototype, /** @lends Response.prototype */ {
    update: function (options) {
        // options.stream accepts new Buffer() as well as new Buffer().toJSON()
        var stream = normaliseStream(options.stream);

        _.mergeDefined((this._details = _.clone(httpReasons.lookup(options.code))), {
            name: _.choose(options.reason, options.status),
            code: options.code,
            standardName: this._details.name
        });

        _.mergeDefined(this, /** @lends Response.prototype */ {
            /**
             * @type {Request}
             */
            originalRequest: options.originalRequest ? new Request(options.originalRequest) : undefined,

            /**
             * @type {String}
             * @deprecated use .reason()
             */
            status: this._details.name,

            /**
             * @type {Number}
             */
            code: options.code,

            /**
             * @type {HeaderList}
             */
            headers: new HeaderList(this, options.header),

            /**
             * @type {String}
             */
            body: options.body,

            /**
             * @private
             *
             * @type {Buffer|UInt8Array}
             */
            stream: options.body && !_.isString(options.body) && _.isObject(options.body) ?
                options.body : stream,

            /**
             * @type {CookieList}
             */
            cookies: new CookieList(this, options.cookie),

            /**
             * Time taken for the request to complete.
             *
             * @type {Number}
             */
            responseTime: options.responseTime,

            /**
             * @private
             * @type {Number}
             */
            responseSize: stream && stream.byteLength
        });
    }
});

_.assign(Response.prototype, /** @lends Response.prototype */ {
    /**
     * Defines that this property requires an ID field
     * @private
     * @readOnly
     */
    _postman_requiresId: true,

    /**
     * Convert this response into a JSON serialisable object. The _details meta property is omitted.
     *
     * @returns {Object}
     */
    toJSON: function () {
        var response = PropertyBase.toJSON(this);

        response._details && (delete response._details);
        return response;
    },

    /**
     * Get the http response reason phrase based on the current response code.
     *
     * @returns {String|undefined}
     */
    reason: function () {
        return this.status || httpReasons.lookup(this.code).name;
    },

    /**
     * Creates a JSON representation of the current response details, and returns it.
     *
     * @returns {Object} A set of response details, including the custom server reason.
     * @private
     */
    details: function () {
        if (!this._details || this._details.code !== this.code) {
            this._details = _.clone(httpReasons.lookup(this.code));
            this._details.code = this.code;
            this._details.standardName = this._details.name;
        }
        return _.clone(this._details);
    },

    /**
     * Get the response body as a string/text.
     *
     * @returns {String|undefined}
     */
    text: function () {
        return (this.stream ? util.bufferOrArrayBufferToString(this.stream, this.mime().charset) : this.body);
    },

    /**
     * Get the response body as a JavaScript object. Note that it throws an error if the response is not a valid JSON
     *
     * @param {Function=} [reviver]
     * @param {Boolean} [strict=false] Specify whether JSON parsing will be strict. This will fail on comments and BOM
     * @example
     * // assuming that the response is stored in a collection instance `myCollection`
     * var response = myCollection.items.one('some request').responses.idx(0),
     *     jsonBody;
     * try {
     *     jsonBody = response.json();
     * }
     * catch (e) {
     *     console.log("There was an error parsing JSON ", e);
     * }
     * // log the root-level keys in the response JSON.
     * console.log('All keys in json response: ' + Object.keys(json));
     *
     * @returns {Object}
     */
    json: function (reviver, strict) {
        return LJSON.parse(this.text(), reviver, strict);
    },

    /**
     * @private
     *
     * @param {String|Header} contentType - override the content-type of the response using this parameter before
     * computing the mime  type.
     * @param {String|Header} contentDisposition - override the content-disposition of the response before calculating
     * mime info.
     *
     * @returns {Object}
     *
     * @note example object returned
     * {
     *   source: string // 'header', 'content', 'default' or 'forced'
     *   type: normalised.type, // sanitised mime type base
     *   format: normalised.format, // format specific to the type returned
     *   name: DEFAULT_RESPONSE_FILENAME, // @todo - get from disposition
     *   ext: mimeType.extension(normalised.source) || E, // file extension from sanitised content type
     *   filename: name + ext,
     *   // also storing some meta info for possible debugging
     *   _originalContentType: type, // the user provided mime type
     *   _sanitisedContentType: normalised.source, // sanitised mime type
     *   _accuratelyDetected: !normalised.orphan // this being true implies worse case (raw render)
     *   detected: {} // same as root object, but based on what is detected from content
     * }
     *
     * @todo write unit tests
     */
    mime: function (contentType, contentDisposition) {
        var detected = fileType(this.stream || this.body), // detect the mime from response body
            source = 'forced',
            mime;

        // if no overrides provided, we take the values from headers
        !contentDisposition && (contentDisposition = this.headers.one('content-disposition'));
        if (!contentType) {
            contentType = this.headers.one('content-type') && this.headers.one('content-type').value;
            source = HEADER;
        }

        // if content type is not found in header, we fall back to the mime type detected
        if (!contentType && detected) {
            contentType = detected.mime;
            source = BODY;
        }

        // if still not found, then we use default text
        if (!contentType) {
            contentType = 'text/plain';
            source = 'default';
        }

        mime = Response.mimeInfo(contentType, contentDisposition);
        mime.source = source;
        mime.detected = detected && Response.mimeInfo(detected.mime, contentDisposition);

        return mime;
    },

    /**
     * Converts the response to a dataURI that can be used for storage or serialisation. The data URI is formed using
     * the following syntax `data:<content-type>;baseg4, <base64-encoded-body>`.
     *
     * @returns {String}
     * @todo write unit tests
     */
    dataURI: function () {
        var mime = this.mime();

        // if there is no mime detected, there is no accurate way to render this thing
        if (!this.mime) {
            return E;
        }

        // we create the body string first from stream and then fallback to body
        return 'data:' + mime._sanitisedContentType + ';base64, ' + ((!_.isNil(this.stream) &&
            util.bufferOrArrayBufferToBase64(this.stream)) || (!_.isNil(this.body) && util.btoa(this.body)) || E);
    },

    /**
     * Get the response size by computing the same from content length header or using the actual response body.
     *
     * @returns {Number}
     * @todo write unit tests
     */
    size: function () {
        var sizeInfo = {
                body: 0,
                header: 0,
                total: 0
            },

            contentEncoding = this.headers.get(CONTENT_ENCODING),
            contentLength = this.headers.get(CONTENT_LENGTH),
            isCompressed = false,
            byteLength;

        // if server sent encoded data, we should first try deriving length from headers
        if (_.isString(contentEncoding)) {
            // desensitise case of content encoding
            contentEncoding = contentEncoding.toLowerCase();
            // eslint-disable-next-line lodash/prefer-includes
            isCompressed = (contentEncoding.indexOf('gzip') > -1) || (contentEncoding.indexOf('deflate') > -1);
        }

        // if 'Content-Length' header is present and encoding is of type gzip/deflate, we take body as declared by
        // server. else we need to compute the same.
        if (contentLength && isCompressed && util.isNumeric(contentLength)) {
            sizeInfo.body = _.parseInt(contentLength, 10);
        }
        // if there is a stream defined which looks like buffer, use it's data and move on
        else if (this.stream) {
            byteLength = this.stream.byteLength;
            sizeInfo.body = util.isNumeric(byteLength) ? byteLength : 0;
        }
        // otherwise, if body is defined, we try get the true length of the body
        else if (!_.isNil(this.body)) {
            sizeInfo.body = supportsBuffer ? Buffer.byteLength(this.body.toString()) : this.body.toString().length;
        }

        // size of header is added
        // https://tools.ietf.org/html/rfc7230#section-3.1.2
        // status-line = HTTP-version SP status-code SP reason-phrase CRLF
        sizeInfo.header = (HTTP_X_X + this.code + ' ' + this.reason() + '\r\n').length + this.headers.contentSize();

        // compute the approximate total body size by adding size of header and body
        sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header || 0);
        return sizeInfo;
    },

    /**
     * Returns the response encoding defined as header or detected from body.
     *
     * @private
     * @returns {Object} - {format: string, source: string}
     */
    encoding: function () {
        var contentEncoding = this.headers.get(CONTENT_ENCODING),
            body = this.stream || this.body,
            source;

        if (contentEncoding) {
            source = HEADER;
        }

        // if the encoding is not found, we check
        else if (body) { // @todo add detection for deflate
            // eslint-disable-next-line lodash/prefer-matches
            if (body[0] === 0x1F && body[1] === 0x8B && body[2] === 0x8) {
                contentEncoding = GZIP;
            }

            if (contentEncoding) {
                source = BODY;
            }
        }

        return {
            format: contentEncoding,
            source: source
        };
    }
});

_.assign(Response, /** @lends Response */ {
    /**
     * Defines the name of this property for internal use.
     * @private
     * @readOnly
     * @type {String}
     */
    _postman_propertyName: 'Response',

    /**
     * Check whether an object is an instance of {@link ItemGroup}.
     *
     * @param {*} obj
     * @returns {Boolean}
     */
    isResponse: function (obj) {
        return Boolean(obj) && ((obj instanceof Response) ||
            _.inSuperChain(obj.constructor, '_postman_propertyName', Response._postman_propertyName));
    },

    /**
     * Converts the response object from the request module to the postman responseBody format
     *
     * @param {Object} response The response object, as received from the request module
     * @param {Object} cookies
     * @returns {Object} The transformed responseBody
     * @todo Add a key: `originalRequest` to the returned object as well, referring to response.request
     */
    createFromNode: function (response, cookies) {
        return new Response({
            cookie: cookies,
            body: response.body.toString(),
            stream: response.body,
            header: response.headers,
            code: response.statusCode,
            status: response.statusMessage,
            responseTime: response.elapsedTime
        });
    },

    /**
     * @private
     *
     * @param {String|Header} type
     * @param {String|Header} disposition
     * @returns {Object}
     *
     * @todo write unit tests
     */
    mimeInfo: function (type, disposition) {
        Header.isHeader(type) && (type = type.value);
        Header.isHeader(disposition) && (disposition = disposition.value);

        // validate that the content type exists
        if (!(type && _.isString(type))) { return; }

        var normalised = mimeFormat.lookup(type),
            info = {};

        _.assign(info, {
            type: normalised.type, // sanitised mime type base
            format: normalised.format, // format specific to the type returned
            name: DEFAULT_RESPONSE_FILENAME, // @todo - get from disposition
            ext: mimeType.extension(normalised.source) || E, // file extension from sanitised content type
            charset: normalised.charset || UTF8,

            // also storing some meta info for possible debugging
            _originalContentType: type, // the user provided mime type
            _sanitisedContentType: normalised.source, // sanitised mime type
            _accuratelyDetected: !normalised.orphan // this being true implies worse case (raw render)
        });

        // build the file name from extension
        info.filename = info.name;
        info.ext && (info.filename += (DOT + info.ext));

        return info;
    }
});

module.exports = {
    Response: Response
};
<|endoftext|>


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

var v = require('./validate');

var asArray = function(arg) {
  return Array.isArray(arg) ? arg : [arg];
};

exports.pipedKeyValues = function(arg) {
  if (!arg || typeof arg !== 'object') {
    throw new Error('not an Object');
  }
  return Object.keys(arg).sort().map(function(key) {
    if (typeof arg[key] === 'object') {
      return arg[key].map(function(type) {
        return key + ':' + type
      }).join('|')
    }
    return key + ':' + arg[key];
  }).join('|');
}

exports.locations = function(arg) {
  if (Array.isArray(arg) && arg.length == 2 && typeof arg[0] == 'number' && typeof arg[1] == 'number') {
    arg = [arg];
  }
  return asArray(arg).map(exports.latLng).join('|');
};

exports.arrayOf = function(validateItem, sep) {
  var validateArray = v.array(validateItem);
  return function(value) {
    value = validateArray(asArray(value));
    return value.join(sep || '|');
  };
};

exports.latLng = function(arg) {
  if (!arg) {
    throw new v.InvalidValueError();
  } else if (arg.lat != undefined && arg.lng != undefined) {
    arg = [arg.lat, arg.lng];
  } else if (arg.latitude != undefined && arg.longitude != undefined) {
    arg = [arg.latitude, arg.longitude];
  }
  return asArray(arg).join(',');
};

var validateBounds = v.object({
  south: v.number,
  west: v.number,
  north: v.number,
  east: v.number
});

exports.bounds = function(arg) {
  arg = validateBounds(arg);
  return arg.south + ',' + arg.west + '|' + arg.north + ',' + arg.east;
};

exports.timeStamp = function(arg) {
  if (arg == undefined) {
    arg = new Date();
  }
  if (arg.getTime) {
    arg = arg.getTime();
    // NOTE: Unix time is seconds past epoch.
    return Math.round(arg / 1000);
  }

  // Otherwise assume arg is Unix time
  return arg;
};

exports.retryOptions = v.object({
  timeout: v.optional(v.number),
  interval: v.optional(v.number),
  increment: v.optional(v.number),
  jitter: v.optional(v.number)
});
<commit_msg>added semicolons<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var v = require('./validate');

var asArray = function(arg) {
  return Array.isArray(arg) ? arg : [arg];
};

exports.pipedKeyValues = function(arg) {
  if (!arg || typeof arg !== 'object') {
    throw new Error('not an Object');
  }
  return Object.keys(arg).sort().map(function(key) {
    if (typeof arg[key] === 'object') {
      return arg[key].map(function(type) {
        return key + ':' + type;
      }).join('|');
    }
    return key + ':' + arg[key];
  }).join('|');
}

exports.locations = function(arg) {
  if (Array.isArray(arg) && arg.length == 2 && typeof arg[0] == 'number' && typeof arg[1] == 'number') {
    arg = [arg];
  }
  return asArray(arg).map(exports.latLng).join('|');
};

exports.arrayOf = function(validateItem, sep) {
  var validateArray = v.array(validateItem);
  return function(value) {
    value = validateArray(asArray(value));
    return value.join(sep || '|');
  };
};

exports.latLng = function(arg) {
  if (!arg) {
    throw new v.InvalidValueError();
  } else if (arg.lat != undefined && arg.lng != undefined) {
    arg = [arg.lat, arg.lng];
  } else if (arg.latitude != undefined && arg.longitude != undefined) {
    arg = [arg.latitude, arg.longitude];
  }
  return asArray(arg).join(',');
};

var validateBounds = v.object({
  south: v.number,
  west: v.number,
  north: v.number,
  east: v.number
});

exports.bounds = function(arg) {
  arg = validateBounds(arg);
  return arg.south + ',' + arg.west + '|' + arg.north + ',' + arg.east;
};

exports.timeStamp = function(arg) {
  if (arg == undefined) {
    arg = new Date();
  }
  if (arg.getTime) {
    arg = arg.getTime();
    // NOTE: Unix time is seconds past epoch.
    return Math.round(arg / 1000);
  }

  // Otherwise assume arg is Unix time
  return arg;
};

exports.retryOptions = v.object({
  timeout: v.optional(v.number),
  interval: v.optional(v.number),
  increment: v.optional(v.number),
  jitter: v.optional(v.number)
});
<|endoftext|>