<commit_before>/**
 * Copyright Dailymotion S.A.
 *
 * 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.
 *
 * @provides dm.player
 * @requires dm.prelude
 *           dm.qs
 *           dm.xdcom
 */

/**
 * Dailymotion Player.
 *
 * @class dm
 * @static
 * @access private
 */
DM.provide('',
{
    player: function(element, options)
    {
        return DM.Player.create(element, options);
    }
});

/**
 * The Player object.
 *
 * @class DM.Player
 * @access private
 */
DM.provide('Player',
{
    _INSTANCES: {},
    _INTERVAL_ID: null,
    API_MODE: null,
    EVENT_HANDLERS: {},

    // video properties
    apiReady: false,
    autoplay: false,
    currentTime: 0,
    bufferedTime: 0,
    duration: NaN,
    seeking: false,
    error: null,
    ended: false,
    muted: false,
    volume: 1,
    paused: true,
    fullscreen: false,

    play: function() {this.api('play');},
    togglePlay: function() {this.api('toggle-play');},
    pause: function() {this.api('pause');},
    seek: function(time) {this.api('seek', time);},
    load: function(id) {this.api('load', id);},
    setMuted: function(muted) {this.api('muted', muted);},
    toggleMuted: function() {this.api('toggle-muted');},
    setVolume: function(volume) {this.api('volume', volume);},
    setFullscreen: function(fullscreen) {this.api('fullscreen', fullscreen);},
    watchOnSite: function(muted) {this.api('watch-on-site');},

    api: function(command, arg)
    {
        if(typeof arg !== 'undefined') command += '=' + arg;
        this._send(command);
    },

    create: function(element, options)
    {
        element = DM.$(element);
        if (!element || element.nodeType != 1)
            throw new Error("Invalid first argument sent to DM.player(), requires a HTML element or element id: " + element);
        if (!options || typeof options != 'object')
            throw new Error("Missing `options' parameter for DM.player()");

        options = DM.copy(options,
        {
            width: 480,
            height: 270,
            title: "video player",
            params: {},
            events: {}
        });

        // see #5 : _domain.www should be protocol independent
        // remove protocol from existing value to preserve backward compatibility
        DM._domain.www = DM._domain.www.replace(/^https?\:/, '');

        var player = document.createElement("iframe");
        DM.Array.forEach(['id', 'style', 'class'], function(attr)
        {
            var val = element.getAttribute(attr);
            if (val) player.setAttribute(attr, val);
        });
        element.parentNode.replaceChild(player, element);
        player.setAttribute("frameborder", "0");
        player.title = "Dailymotion " + options.title;
        player.type = "text/html";
        player.width = options.width;
        player.height = options.height;

        DM.copy(player, DM.Player);

        player.init(options.video, options.params);

        if (typeof options.events == "object")
        {
            for (var name in options.events)
            {
                player.addEventListener(name, options.events[name], false);
            }
        }

        return player;
    },

    init: function(video, params)
    {
        var self = this;
        DM.Player._installHandlers(function()
        {
            // ask the peer to resend the apiready event in case it missed it while installing handlers
            self._send('check');
        });
        params = typeof params == "object" ? params : {};
        params.api = DM.Player.API_MODE;
        if (DM.Player.API_MODE == 'xdcom')
        {
            params.xdcomId = DM.Player.xdcomChannel.connectionId;
        }
        this.id = params.id = this.id ? this.id : DM.guid();
        this.src = DM._domain.www + "/embed" + (video ? "/video/" + video : "") + '?' + DM.QS.encode(params);
        if (DM.Player._INSTANCES[this.id] != this)
        {
            DM.Player._INSTANCES[this.id] = this;
            this.addEventListener('unload', function() {delete DM.Player._INSTANCES[this.id];});
        }

        this.autoplay = DM.parseBool(params.autoplay);
    },

    _installHandlers: function(initedCallback)
    {
        if (DM.Player.API_MODE !== null) return;
        if (window.postMessage)
        {
            DM.Player.API_MODE = "postMessage";

            var handler = function(e)
            {
                if (!e.origin || e.origin.indexOf(location.protocol + DM._domain.www) !== 0) return;
                var event = DM.QS.decode(e.data);
                if (!event.id || !event.event) return;
                var player = DM.$(event.id);
                player._recvEvent(event);
            };
            if (window.addEventListener) window.addEventListener("message", handler, false);
            else if (window.attachEvent) window.attachEvent("onmessage", handler);
        }
        else if(DM.XDCom.capable())
        {
            DM.Player.API_MODE = "xdcom";
            DM.Player.xdcomChannel = DM.XDCom.createChannel(function(data)
            {
                var event = DM.QS.decode(data);
                if (!event.id || !event.event) return;
                var player = DM.$(event.id);
                player._recvEvent(event);
            }, initedCallback);
        }

        if (DM.Player.API_MODE === null)
        {
            DM.Player.API_MODE = "fragment";
            return; // no yet ready

            if (!DM.Player._INTERVAL_ID)
            {
                DM.Player._INTERVAL_ID = setInterval(function()
                {
                    for (var id in DM.Player._INSTANCES)
                    {
                        var player = DM.Player._INSTANCES[id], pos;
                        if ((pos = player.src.indexOf('#')) != -1)
                        {
                            var event = DM.QS.decode(src.substring(pos + 1));
                            player.src = src.substring(0, pos); // reset fragment
                            if (event.id && event.event) player._recvEvent(event);
                        }
                    }
                    if (DM.Player._INSTANCES.length === 0) clearInterval(DM.Player._INTERVAL_ID);
                }, 0);
            }
        }
    },

    _send: function(command) // fragment API mode fallback
    {
        switch (DM.Player.API_MODE)
        {
            case 'postMessage':
                this.contentWindow.postMessage(command, location.protocol + DM._domain.www);
                break;

            case 'xdcom':
                DM.Player.xdcomChannel.postMessage(this.id, command);
                break;

            case 'fragment':
                var src = this.src, pos;
                if ((pos = src.indexOf('#')) != -1) src = src.substring(0, pos);
                this.src = src + '#' + command;
                break;
        }
    },

    _dispatch: document.createEvent ? function(type)
    {
        var e = document.createEvent("HTMLEvents");
        e.initEvent(type, true, true);
        this.dispatchEvent(e);
    }
    : function(type) // IE compat
    {
        if ('on' + type in this)
        {
            e = document.createEventObject();
            this.fireEvent('on' + type, e);
        }
        else if (type in this.EVENT_HANDLERS)
        {
            var e = {type: type, target: this};
            DM.Array.forEach(this.EVENT_HANDLERS[type], function(handler)
            {
                handler(e);
            });
        }
    },

    _recvEvent: function(event)
    {
        switch (event.event)
        {
            case 'apiready': if (this.apiReady) return /* dispatch only once */; else this.apiReady = true; break;
            case 'loadedmetadata': this.error = null; this.ended = false; break;
            case 'timeupdate': this.currentTime = parseFloat(event.time); break;
            case 'progress': this.bufferedTime = parseFloat(event.time); break;
            case 'durationchange': this.duration = parseFloat(event.duration); break;
            case 'seeking': this.seeking = true; this.currentTime = parseFloat(event.time); break;
            case 'seeked': this.seeking = false; this.currentTime = parseFloat(event.time); break;
            case 'fullscreenchange': this.fullscreen = DM.parseBool(event.fullscreen); break;
            case 'volumechange': this.volume = parseFloat(event.volume); this.muted = DM.parseBool(event.muted); break;
            case 'playing':
            case 'play': this.paused = false; break;
            case 'ended': this.ended = true; break; // no break, also set paused
            case 'ended':
            case 'pause': this.paused = true; break;
            case 'error': this.error = {code: event.code, title: event.title, message: event.message}; break;
        }

        this._dispatch(event.event);
    },

    // IE compat (DM.copy won't set this if already defined)
    addEventListener: function(name, callback, capture)
    {
        if ('on' + name in this && this.attachEvent)
        {
            this.attachEvent("on" + name, callback, capture);
        }
        else
        {
            if (!(name in this.EVENT_HANDLERS))
            {
                this.EVENT_HANDLERS[name] = [];
            }
            this.EVENT_HANDLERS[name].push(callback);
        }
    }
});
<commit_msg>Fallback on 'http:' if window.protocol doesn't match http or https<commit_after>/**
 * Copyright Dailymotion S.A.
 *
 * 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.
 *
 * @provides dm.player
 * @requires dm.prelude
 *           dm.qs
 *           dm.xdcom
 */

/**
 * Dailymotion Player.
 *
 * @class dm
 * @static
 * @access private
 */
DM.provide('',
{
    player: function(element, options)
    {
        return DM.Player.create(element, options);
    }
});

/**
 * The Player object.
 *
 * @class DM.Player
 * @access private
 */
DM.provide('Player',
{
    _INSTANCES: {},
    _INTERVAL_ID: null,
    _PROTOCOL: null,
    API_MODE: null,
    EVENT_HANDLERS: {},

    // video properties
    apiReady: false,
    autoplay: false,
    currentTime: 0,
    bufferedTime: 0,
    duration: NaN,
    seeking: false,
    error: null,
    ended: false,
    muted: false,
    volume: 1,
    paused: true,
    fullscreen: false,

    play: function() {this.api('play');},
    togglePlay: function() {this.api('toggle-play');},
    pause: function() {this.api('pause');},
    seek: function(time) {this.api('seek', time);},
    load: function(id) {this.api('load', id);},
    setMuted: function(muted) {this.api('muted', muted);},
    toggleMuted: function() {this.api('toggle-muted');},
    setVolume: function(volume) {this.api('volume', volume);},
    setFullscreen: function(fullscreen) {this.api('fullscreen', fullscreen);},
    watchOnSite: function(muted) {this.api('watch-on-site');},

    api: function(command, arg)
    {
        if(typeof arg !== 'undefined') command += '=' + arg;
        this._send(command);
    },

    create: function(element, options)
    {
        element = DM.$(element);
        if (!element || element.nodeType != 1)
            throw new Error("Invalid first argument sent to DM.player(), requires a HTML element or element id: " + element);
        if (!options || typeof options != 'object')
            throw new Error("Missing `options' parameter for DM.player()");

        options = DM.copy(options,
        {
            width: 480,
            height: 270,
            title: "video player",
            params: {},
            events: {}
        });

        // see #5 : _domain.www should be protocol independent
        // remove protocol from existing value to preserve backward compatibility
        DM._domain.www = DM._domain.www.replace(/^https?\:/, '');
        DM.Player._PROTOCOL = (window.location && /^https?:$/.test(window.location.protocol)) ? window.location.protocol : 'http:';

        var player = document.createElement("iframe");
        DM.Array.forEach(['id', 'style', 'class'], function(attr)
        {
            var val = element.getAttribute(attr);
            if (val) player.setAttribute(attr, val);
        });
        element.parentNode.replaceChild(player, element);
        player.setAttribute("frameborder", "0");
        player.title = "Dailymotion " + options.title;
        player.type = "text/html";
        player.width = options.width;
        player.height = options.height;

        DM.copy(player, DM.Player);

        player.init(options.video, options.params);

        if (typeof options.events == "object")
        {
            for (var name in options.events)
            {
                player.addEventListener(name, options.events[name], false);
            }
        }

        return player;
    },

    init: function(video, params)
    {
        var self = this;
        DM.Player._installHandlers(function()
        {
            // ask the peer to resend the apiready event in case it missed it while installing handlers
            self._send('check');
        });
        params = typeof params == "object" ? params : {};
        params.api = DM.Player.API_MODE;
        if (DM.Player.API_MODE == 'xdcom')
        {
            params.xdcomId = DM.Player.xdcomChannel.connectionId;
        }
        this.id = params.id = this.id ? this.id : DM.guid();
        this.src = DM.Player._PROTOCOL + DM._domain.www + "/embed" + (video ? "/video/" + video : "") + '?' + DM.QS.encode(params);
        if (DM.Player._INSTANCES[this.id] != this)
        {
            DM.Player._INSTANCES[this.id] = this;
            this.addEventListener('unload', function() {delete DM.Player._INSTANCES[this.id];});
        }

        this.autoplay = DM.parseBool(params.autoplay);
    },

    _installHandlers: function(initedCallback)
    {
        if (DM.Player.API_MODE !== null) return;
        if (window.postMessage)
        {
            DM.Player.API_MODE = "postMessage";

            var handler = function(e)
            {
                if (!e.origin || e.origin.indexOf(DM.Player._PROTOCOL + DM._domain.www) !== 0) return;
                var event = DM.QS.decode(e.data);
                if (!event.id || !event.event) return;
                var player = DM.$(event.id);
                player._recvEvent(event);
            };
            if (window.addEventListener) window.addEventListener("message", handler, false);
            else if (window.attachEvent) window.attachEvent("onmessage", handler);
        }
        else if(DM.XDCom.capable())
        {
            DM.Player.API_MODE = "xdcom";
            DM.Player.xdcomChannel = DM.XDCom.createChannel(function(data)
            {
                var event = DM.QS.decode(data);
                if (!event.id || !event.event) return;
                var player = DM.$(event.id);
                player._recvEvent(event);
            }, initedCallback);
        }

        if (DM.Player.API_MODE === null)
        {
            DM.Player.API_MODE = "fragment";
            return; // no yet ready

            if (!DM.Player._INTERVAL_ID)
            {
                DM.Player._INTERVAL_ID = setInterval(function()
                {
                    for (var id in DM.Player._INSTANCES)
                    {
                        var player = DM.Player._INSTANCES[id], pos;
                        if ((pos = player.src.indexOf('#')) != -1)
                        {
                            var event = DM.QS.decode(src.substring(pos + 1));
                            player.src = src.substring(0, pos); // reset fragment
                            if (event.id && event.event) player._recvEvent(event);
                        }
                    }
                    if (DM.Player._INSTANCES.length === 0) clearInterval(DM.Player._INTERVAL_ID);
                }, 0);
            }
        }
    },

    _send: function(command) // fragment API mode fallback
    {
        switch (DM.Player.API_MODE)
        {
            case 'postMessage':
                this.contentWindow.postMessage(command, DM.Player._PROTOCOL + DM._domain.www);
                break;

            case 'xdcom':
                DM.Player.xdcomChannel.postMessage(this.id, command);
                break;

            case 'fragment':
                var src = this.src, pos;
                if ((pos = src.indexOf('#')) != -1) src = src.substring(0, pos);
                this.src = src + '#' + command;
                break;
        }
    },

    _dispatch: document.createEvent ? function(type)
    {
        var e = document.createEvent("HTMLEvents");
        e.initEvent(type, true, true);
        this.dispatchEvent(e);
    }
    : function(type) // IE compat
    {
        if ('on' + type in this)
        {
            e = document.createEventObject();
            this.fireEvent('on' + type, e);
        }
        else if (type in this.EVENT_HANDLERS)
        {
            var e = {type: type, target: this};
            DM.Array.forEach(this.EVENT_HANDLERS[type], function(handler)
            {
                handler(e);
            });
        }
    },

    _recvEvent: function(event)
    {
        switch (event.event)
        {
            case 'apiready': if (this.apiReady) return /* dispatch only once */; else this.apiReady = true; break;
            case 'loadedmetadata': this.error = null; this.ended = false; break;
            case 'timeupdate': this.currentTime = parseFloat(event.time); break;
            case 'progress': this.bufferedTime = parseFloat(event.time); break;
            case 'durationchange': this.duration = parseFloat(event.duration); break;
            case 'seeking': this.seeking = true; this.currentTime = parseFloat(event.time); break;
            case 'seeked': this.seeking = false; this.currentTime = parseFloat(event.time); break;
            case 'fullscreenchange': this.fullscreen = DM.parseBool(event.fullscreen); break;
            case 'volumechange': this.volume = parseFloat(event.volume); this.muted = DM.parseBool(event.muted); break;
            case 'playing':
            case 'play': this.paused = false; break;
            case 'ended': this.ended = true; break; // no break, also set paused
            case 'ended':
            case 'pause': this.paused = true; break;
            case 'error': this.error = {code: event.code, title: event.title, message: event.message}; break;
        }

        this._dispatch(event.event);
    },

    // IE compat (DM.copy won't set this if already defined)
    addEventListener: function(name, callback, capture)
    {
        if ('on' + name in this && this.attachEvent)
        {
            this.attachEvent("on" + name, callback, capture);
        }
        else
        {
            if (!(name in this.EVENT_HANDLERS))
            {
                this.EVENT_HANDLERS[name] = [];
            }
            this.EVENT_HANDLERS[name].push(callback);
        }
    }
});
<|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.
 */

/**
 * Top-Level of foam package
 * @exports foam
 */
foam = {
  isServer: typeof process === 'object',
  core:     {},
  Array:    Array.prototype,
  Function: Function.prototype,
  Number:   Number.prototype,
  Object:   Object.prototype,
  String:   String.prototype,
  Date:     Date.prototype,
};

/** Setup nodejs-like 'global' on web */
if ( ! foam.isServer ) global = this;

/**
 * A LIB is a collection of static constants, properties and functions.
 */
foam.LIB = function LIB(model) {
  var proto;

  function defineProperty(proto, key, map) {
    if ( ! map.value || proto === Object.prototype || proto === Array.prototype )
      Object.defineProperty.apply(this, arguments);
    else
      proto[key] = map.value;
  }

  proto = model.name ? foam[model.name] || ( foam[model.name] = {} ) : foam;

  if ( model.properties ) for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var p = model.properties[i];
    defineProperty(
      proto,
      p.name,
      { get: p.getter, enumerable: false });
  }

  for ( key in model.constants )
    defineProperty(
      proto,
      key,
      { value: model.constants[key], writable: true, enumerable: false });

  if ( model.methods ) for ( var i = 0 ; i < model.methods.length ; i++ ) {
    var m = model.methods[i];
    defineProperty(
      proto,
      m.name,
      { value: m.code || m, writable: true, enumerable: false });
  }
};

/*
 * Add Unique Identifiers (UIDs) to all Objects.
 * UID's are created when first accessed.
 */
foam.LIB({
  name: 'Object',

  properties: [
    {
      name: '$UID',
      getter: (function() {
        var id = 1;
        return function() {
          if ( Object.hasOwnProperty.call(this, '$UID__') ) return this.$UID__;
          Object.defineProperty(this, '$UID__', {value: id, enumerable: false});
          id++;
          return this.$UID__;
        };
      })()
    }
  ]
});

foam.LIB({
  name: 'Number',

  methods: [
    function compareTo(o) { return ( o == this ) ? 0 : this < o ? -1 : 1; },
  ]
});

foam.LIB({
  name: 'String',

  methods: [
    function compareTo(o) { return ( o == this ) ? 0 : this < o ? -1 : 1; },

    /** Adds hashCode functionality to all strings. */
    function hashCode() {
      var hash = 0;
      if ( this.length == 0 ) return hash;

      for (i = 0; i < this.length; i++) {
        var code = this.charCodeAt(i);
        hash = ((hash << 5) - hash) + code;
        hash &= hash;
      }

      return hash;
    }
  ]
});

foam.LIB({
  name: 'Array',
  methods: [
    function diff(other) {
      /** Finds elements added (found in other, not in this) and removed
          (found in this, not in other). Repeated values are treated
          as separate elements, but ordering changes are ignored. */
      var added = other.slice(0);
      var removed = [];
      for ( var i = 0 ; i < this.length ; i++ ) {
        for ( var j = 0 ; j < added.length ; j++ ) {
          if ( this[i].compareTo(added[j]) == 0 ) {
            added.splice(j, 1);
            j--;
            break;
          }
        }
        if ( j == added.length ) removed.push(this[i]);
      }
      return { added: added, removed: removed };
    },
    function clone() {
      /** Returns a deep copy of this array and its contents. */
      var ret = new Array(this.length);
      for ( var i = 0; i < this.length; i++ ) {
        ret[i] = ( this[i].clone ) ? this[i].clone() : this[i];
      }
      return ret;
    },
    function shallowClone() {
      /** Returns a new array containing the same elements as this. */
      return this.slice();
    }
  ]
});

foam.LIB({
  name: 'Date',
  methods: [
    function toRelativeDateString(){
      // TODO i18n: make this translatable
      var seconds = Math.floor((Date.now() - this.getTime())/1000);

      if ( seconds < 60 ) return 'moments ago';

      var minutes = Math.floor((seconds)/60);

      if ( minutes == 1 ) return '1 minute ago';

      if ( minutes < 60 ) return minutes + ' minutes ago';

      var hours = Math.floor(minutes/60);
      if ( hours == 1 ) return '1 hour ago';

      if ( hours < 24 ) return hours + ' hours ago';

      var days = Math.floor(hours / 24);
      if ( days == 1 ) return '1 day ago';

      if ( days < 7 ) return days + ' days ago';

      if ( days < 365 ) {
        var year = 1900+this.getYear();
        var noyear = this.toDateString().replace(' ' + year, '');
        return noyear.substring(4);
      }

      return this.toDateString().substring(4);
    },

    function equals(o) {
      if ( ! o ) return false;
      if ( ! o.getTime ) return false;
      return this.getTime() === o.getTime();
    },

    function compareTo(o){
      if ( o === this ) return 0;
      if ( ! o ) return 1;
      var d = this.getTime() - o.getTime();
      return d == 0 ? 0 : d > 0 ? 1 : -1;
    }
  ]
});

foam.LIB({
  name: 'array',

  methods: [
    function argsToArray(args) {
      /** convenience method to turn 'arguments' into a real array */
      return foam.fn.appendArguments([], args, 0);
    }
  ]
});

foam.LIB({
  name: 'events',

  methods: [
    function oneTime(listener) {
      /** Create a "one-time" listener which unsubscribes itself after its first invocation. **/
      return function(subscription) {
        listener.apply(this, foam.array.argsToArray(arguments));
        subscription.destroy();
      };
    },

    function consoleLog(listener) {
      /** Log all listener invocations to console. **/
      return function() {
        var args = foam.array.argsToArray(arguments);
        console.log(args);
        listener && listener.apply(this, args);
      };
    }
  ]
});

foam.LIB({
  name: 'util',
  methods: [
    function equals(a, b) {
      if ( a === b ) return true;
      if ( ! a || ! b ) return false;
      if ( a.equals ) return a.equals(b);
      return a == b;
    }
  ]
});

foam.LIB({
  name: 'fn',

  methods: [
    /** Faster version of memoize() when only dealing with one argument. */
    function memoize1(f) {
      var cache = {};
      var g = function(arg) {
        console.assert(arguments.length == 1, "Memoize1'ed functions must take exactly one argument.");
        var key = arg ? arg.toString() : '';
        if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg);
        return cache[key];
      };
      foam.fn.setName(g, 'memoize1(' + f.name + ')');
      return g;
    },

    function setName(f, name) {
      /** Set a function's name for improved debugging and profiling **/
      Object.defineProperty(f, 'name', {value: name, configurable: true});
    },

    function appendArguments(a, args, start) {
      /** Convenience method to append 'arguments' onto a real array **/
      for ( var i = start ; i < args.length ; i++ ) a.push(args[i]);
      return a;
    },

    function argsStr(f) {
      /** Finds the function(...) declaration arguments part. Strips newlines. */
      return f.toString().replace(/(\r\n|\n|\r)/gm,"").match(/^function(\s+[_$\w]+|\s*)\((.*?)\)/)[2] || '';
    },

    function argsArray(f) {
      /**
       * Return a function's arguments as an array.
       * Ex. args(function(a,b) {...}) == ['a', 'b']
       **/
      var args = foam.fn.argsStr(f);
      if ( ! args ) return [];
      args += ',';

      var ret = [];
      // [ ws /* anything */ ] ws arg_name ws [ /* anything */ ],
      var argMatcher = /(\s*\/\*.*?\*\/)?\s*([\w_$]+)\s*(\/\*.*?\*\/)?\s*\,+/g;
      var typeMatch;
      while ((typeMatch = argMatcher.exec(args)) !== null) {
        ret.push(typeMatch[2]);
      }
      return ret;
    },

    // ???: Is this needed?
    /*
    function passProperties(args, f) {
      return function passProperties() {
        var self = this;
        return f.apply(this, args.map(function(arg) { return self[arg]; }));
      };
    }
    */
  ]
});

(function() {
  // Disable setName if not supported on this platform.
  try {
    foam.fn.setName(function() {}, '');
  } catch (x) {
    /**
      @class fn
      @ignore */
    foam.LIB({
      name: 'fn',
      methods: [ function setName() { /* NOP */ } ]
    });
  }
})();

foam.LIB({
  name: 'string',

  methods: [
    {
      name: 'constantize',
      code: foam.fn.memoize1(function(str) {
        // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT
        return str.replace(/[a-z][^0-9a-z_]/g, function(a) {
          return a.substring(0,1) + '_' + a.substring(1,2);
        }).toUpperCase();
      })
    },

    {
      name: 'labelize',
      code: foam.fn.memoize1(function(str) {
        if ( ! str || str === '' ) return str;
        return this.capitalize(str.replace(/[a-z][A-Z]/g, function (a) { return a.charAt(0) + ' ' + a.charAt(1); }));
      })
    },

    {
      name: 'capitalize',
      code: foam.fn.memoize1(function(str) {
        // switchFromProperyName to //SwitchFromPropertyName
        return str[0].toUpperCase() + str.substring(1);
      })
    },

    function pad(str, size) {
      // Right pads to size if size > 0, Left pads to -size if size < 0
      return size < 0 ?
        (new Array(-size).join(' ') + str).slice(size)       :
        (str + new Array(size).join(' ')).substring(0, size) ;
    },

    function multiline(f) {
      // Function for returning multi-line strings from commented functions.
      // Ex. var str = multiline(function() { /* multi-line string here */ });
      if ( typeof f === 'string' ) return f;
      var s = f.toString();
      var start = s.indexOf('/*');
      var end   = s.lastIndexOf('*/');
      return s.substring(start+2, end);
    }
  ]
});

(function() {

  function set(X, key, value) {
    /* Update a Context binding. */
    X[key] = value;

    // TODO:
    // if ( GLOBAL.SimpleReadOnlyValue && key !== '$' && key !== '$$' )
    // X[key + '$'] = SimpleReadOnlyValue.create({value: value});
  }

  function setDynamic(X, key, dValue) {
    Object.defineProperty(
      X,
      key,
      {
        get: function() { return dValue.get(); },
        configurable: true
      }
    );

    if ( key !== '$' && key !== '$$' ) X[key + '$'] = dValue;
  }

  var ROOT = global;

  // TODO: more sophisticated caching will be required
  // once we can register models.
  var lookup_ = foam.fn.memoize1(function lookup_(id) {
    var path = id.split('.');
    var root = ROOT;
    for ( var i = 0 ; root && i < path.length ; i++ )
      root = root[path[i]];
    return root;
  });

  var X = {
    lookup: function(id) {
      return id && ( lookup_('foam.core.' + id) || lookup_(id) );
    },

    register: function(cls) {
      var path = cls.id.split('.');
      var root = ROOT;
      for ( var i = 0 ; i < path.length-1 ; i++ ) {
        root = root[path[i]] || ( root[path[i]] = {} );
      }
      root[path[path.length-1]] = cls;
    },

    sub: function sub(opt_args, opt_name) {
      var sub = Object.create(this);

      if ( opt_args ) for ( var key in opt_args ) {
        if ( opt_args.hasOwnProperty(key) ) {
          var asDyn = key !== '$' && key != '$$' && key.charAt(key.length-1) == '$';
          if ( asDyn ) {
            setDynamic(sub, key.substring(0, key.length-1), opt_args[key]);
          } else {
            set(sub, key, opt_args[key]);
          }
        }
      }

      if ( opt_name )
        Object.defineProperty(sub, 'NAME', {value: opt_name, enumerable: false});

      return sub;
    }
  };

  foam.X = X;

  for ( var key in X ) foam[key] = X[key].bind(X);
})();
<commit_msg>Fixed lookup memoization.<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.
 */

/**
 * Top-Level of foam package
 * @exports foam
 */
foam = {
  isServer: typeof process === 'object',
  core:     {},
  Array:    Array.prototype,
  Function: Function.prototype,
  Number:   Number.prototype,
  Object:   Object.prototype,
  String:   String.prototype,
  Date:     Date.prototype,
};

/** Setup nodejs-like 'global' on web */
if ( ! foam.isServer ) global = this;

/**
 * A LIB is a collection of static constants, properties and functions.
 */
foam.LIB = function LIB(model) {
  var proto;

  function defineProperty(proto, key, map) {
    if ( ! map.value || proto === Object.prototype || proto === Array.prototype )
      Object.defineProperty.apply(this, arguments);
    else
      proto[key] = map.value;
  }

  proto = model.name ? foam[model.name] || ( foam[model.name] = {} ) : foam;

  if ( model.properties ) for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var p = model.properties[i];
    defineProperty(
      proto,
      p.name,
      { get: p.getter, enumerable: false });
  }

  for ( key in model.constants )
    defineProperty(
      proto,
      key,
      { value: model.constants[key], writable: true, enumerable: false });

  if ( model.methods ) for ( var i = 0 ; i < model.methods.length ; i++ ) {
    var m = model.methods[i];
    defineProperty(
      proto,
      m.name,
      { value: m.code || m, writable: true, enumerable: false });
  }
};

/*
 * Add Unique Identifiers (UIDs) to all Objects.
 * UID's are created when first accessed.
 */
foam.LIB({
  name: 'Object',

  properties: [
    {
      name: '$UID',
      getter: (function() {
        var id = 1;
        return function() {
          if ( Object.hasOwnProperty.call(this, '$UID__') ) return this.$UID__;
          Object.defineProperty(this, '$UID__', {value: id, enumerable: false});
          id++;
          return this.$UID__;
        };
      })()
    }
  ]
});

foam.LIB({
  name: 'Number',

  methods: [
    function compareTo(o) { return ( o == this ) ? 0 : this < o ? -1 : 1; },
  ]
});

foam.LIB({
  name: 'String',

  methods: [
    function compareTo(o) { return ( o == this ) ? 0 : this < o ? -1 : 1; },

    /** Adds hashCode functionality to all strings. */
    function hashCode() {
      var hash = 0;
      if ( this.length == 0 ) return hash;

      for (i = 0; i < this.length; i++) {
        var code = this.charCodeAt(i);
        hash = ((hash << 5) - hash) + code;
        hash &= hash;
      }

      return hash;
    }
  ]
});

foam.LIB({
  name: 'Array',
  methods: [
    function diff(other) {
      /** Finds elements added (found in other, not in this) and removed
          (found in this, not in other). Repeated values are treated
          as separate elements, but ordering changes are ignored. */
      var added = other.slice(0);
      var removed = [];
      for ( var i = 0 ; i < this.length ; i++ ) {
        for ( var j = 0 ; j < added.length ; j++ ) {
          if ( this[i].compareTo(added[j]) == 0 ) {
            added.splice(j, 1);
            j--;
            break;
          }
        }
        if ( j == added.length ) removed.push(this[i]);
      }
      return { added: added, removed: removed };
    },
    function clone() {
      /** Returns a deep copy of this array and its contents. */
      var ret = new Array(this.length);
      for ( var i = 0; i < this.length; i++ ) {
        ret[i] = ( this[i].clone ) ? this[i].clone() : this[i];
      }
      return ret;
    },
    function shallowClone() {
      /** Returns a new array containing the same elements as this. */
      return this.slice();
    }
  ]
});

foam.LIB({
  name: 'Date',
  methods: [
    function toRelativeDateString(){
      // TODO i18n: make this translatable
      var seconds = Math.floor((Date.now() - this.getTime())/1000);

      if ( seconds < 60 ) return 'moments ago';

      var minutes = Math.floor((seconds)/60);

      if ( minutes == 1 ) return '1 minute ago';

      if ( minutes < 60 ) return minutes + ' minutes ago';

      var hours = Math.floor(minutes/60);
      if ( hours == 1 ) return '1 hour ago';

      if ( hours < 24 ) return hours + ' hours ago';

      var days = Math.floor(hours / 24);
      if ( days == 1 ) return '1 day ago';

      if ( days < 7 ) return days + ' days ago';

      if ( days < 365 ) {
        var year = 1900+this.getYear();
        var noyear = this.toDateString().replace(' ' + year, '');
        return noyear.substring(4);
      }

      return this.toDateString().substring(4);
    },

    function equals(o) {
      if ( ! o ) return false;
      if ( ! o.getTime ) return false;
      return this.getTime() === o.getTime();
    },

    function compareTo(o){
      if ( o === this ) return 0;
      if ( ! o ) return 1;
      var d = this.getTime() - o.getTime();
      return d == 0 ? 0 : d > 0 ? 1 : -1;
    }
  ]
});

foam.LIB({
  name: 'array',

  methods: [
    function argsToArray(args) {
      /** convenience method to turn 'arguments' into a real array */
      return foam.fn.appendArguments([], args, 0);
    }
  ]
});

foam.LIB({
  name: 'events',

  methods: [
    function oneTime(listener) {
      /** Create a "one-time" listener which unsubscribes itself after its first invocation. **/
      return function(subscription) {
        listener.apply(this, foam.array.argsToArray(arguments));
        subscription.destroy();
      };
    },

    function consoleLog(listener) {
      /** Log all listener invocations to console. **/
      return function() {
        var args = foam.array.argsToArray(arguments);
        console.log(args);
        listener && listener.apply(this, args);
      };
    }
  ]
});

foam.LIB({
  name: 'util',
  methods: [
    function equals(a, b) {
      if ( a === b ) return true;
      if ( ! a || ! b ) return false;
      if ( a.equals ) return a.equals(b);
      return a == b;
    }
  ]
});

foam.LIB({
  name: 'fn',

  methods: [
    /** Faster version of memoize() when only dealing with one argument. */
    function memoize1(f) {
      var cache = {};
      var g = function(arg) {
        console.assert(arguments.length == 1, "Memoize1'ed functions must take exactly one argument.");
        var key = arg ? arg.toString() : '';
        if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg);
        return cache[key];
      };
      foam.fn.setName(g, 'memoize1(' + f.name + ')');
      return g;
    },

    function setName(f, name) {
      /** Set a function's name for improved debugging and profiling **/
      Object.defineProperty(f, 'name', {value: name, configurable: true});
    },

    function appendArguments(a, args, start) {
      /** Convenience method to append 'arguments' onto a real array **/
      for ( var i = start ; i < args.length ; i++ ) a.push(args[i]);
      return a;
    },

    function argsStr(f) {
      /** Finds the function(...) declaration arguments part. Strips newlines. */
      return f.toString().replace(/(\r\n|\n|\r)/gm,"").match(/^function(\s+[_$\w]+|\s*)\((.*?)\)/)[2] || '';
    },

    function argsArray(f) {
      /**
       * Return a function's arguments as an array.
       * Ex. args(function(a,b) {...}) == ['a', 'b']
       **/
      var args = foam.fn.argsStr(f);
      if ( ! args ) return [];
      args += ',';

      var ret = [];
      // [ ws /* anything */ ] ws arg_name ws [ /* anything */ ],
      var argMatcher = /(\s*\/\*.*?\*\/)?\s*([\w_$]+)\s*(\/\*.*?\*\/)?\s*\,+/g;
      var typeMatch;
      while ((typeMatch = argMatcher.exec(args)) !== null) {
        ret.push(typeMatch[2]);
      }
      return ret;
    },

    // ???: Is this needed?
    /*
    function passProperties(args, f) {
      return function passProperties() {
        var self = this;
        return f.apply(this, args.map(function(arg) { return self[arg]; }));
      };
    }
    */
  ]
});

(function() {
  // Disable setName if not supported on this platform.
  try {
    foam.fn.setName(function() {}, '');
  } catch (x) {
    /**
      @class fn
      @ignore */
    foam.LIB({
      name: 'fn',
      methods: [ function setName() { /* NOP */ } ]
    });
  }
})();

foam.LIB({
  name: 'string',

  methods: [
    {
      name: 'constantize',
      code: foam.fn.memoize1(function(str) {
        // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT
        return str.replace(/[a-z][^0-9a-z_]/g, function(a) {
          return a.substring(0,1) + '_' + a.substring(1,2);
        }).toUpperCase();
      })
    },

    {
      name: 'labelize',
      code: foam.fn.memoize1(function(str) {
        if ( ! str || str === '' ) return str;
        return this.capitalize(str.replace(/[a-z][A-Z]/g, function (a) { return a.charAt(0) + ' ' + a.charAt(1); }));
      })
    },

    {
      name: 'capitalize',
      code: foam.fn.memoize1(function(str) {
        // switchFromProperyName to //SwitchFromPropertyName
        return str[0].toUpperCase() + str.substring(1);
      })
    },

    function pad(str, size) {
      // Right pads to size if size > 0, Left pads to -size if size < 0
      return size < 0 ?
        (new Array(-size).join(' ') + str).slice(size)       :
        (str + new Array(size).join(' ')).substring(0, size) ;
    },

    function multiline(f) {
      // Function for returning multi-line strings from commented functions.
      // Ex. var str = multiline(function() { /* multi-line string here */ });
      if ( typeof f === 'string' ) return f;
      var s = f.toString();
      var start = s.indexOf('/*');
      var end   = s.lastIndexOf('*/');
      return s.substring(start+2, end);
    }
  ]
});

(function() {

  function set(X, key, value) {
    /* Update a Context binding. */
    X[key] = value;

    // TODO:
    // if ( GLOBAL.SimpleReadOnlyValue && key !== '$' && key !== '$$' )
    // X[key + '$'] = SimpleReadOnlyValue.create({value: value});
  }

  function setDynamic(X, key, dValue) {
    Object.defineProperty(
      X,
      key,
      {
        get: function() { return dValue.get(); },
        configurable: true
      }
    );

    if ( key !== '$' && key !== '$$' ) X[key + '$'] = dValue;
  }

  var ROOT = global;

  var lookup_ = foam.fn.memoize1(function lookup_(id) {
    var path = id.split('.');
    var root = ROOT;
    for ( var i = 0 ; root && i < path.length ; i++ )
      root = root[path[i]];
    return root;
  });

  var X = {
    // TODO: more sophisticated caching will be required
    // once we can register models.
    lookup: function(id) {
      return id && ( foam.core[id] || lookup_(id) );
    },

    register: function(cls) {
      var path = cls.id.split('.');
      var root = ROOT;
      for ( var i = 0 ; i < path.length-1 ; i++ ) {
        root = root[path[i]] || ( root[path[i]] = {} );
      }
      root[path[path.length-1]] = cls;
    },

    sub: function sub(opt_args, opt_name) {
      var sub = Object.create(this);

      if ( opt_args ) for ( var key in opt_args ) {
        if ( opt_args.hasOwnProperty(key) ) {
          var asDyn = key !== '$' && key != '$$' && key.charAt(key.length-1) == '$';
          if ( asDyn ) {
            setDynamic(sub, key.substring(0, key.length-1), opt_args[key]);
          } else {
            set(sub, key, opt_args[key]);
          }
        }
      }

      if ( opt_name )
        Object.defineProperty(sub, 'NAME', {value: opt_name, enumerable: false});

      return sub;
    }
  };

  foam.X = X;

  for ( var key in X ) foam[key] = X[key].bind(X);
})();
<|endoftext|>


<commit_before>import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param {any} [preloadedState] The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 *
 * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 * to enhance the store with third-party capabilities such as middleware,
 * time travel, persistence, etc. The only store enhancer that ships with Redux
 * is `applyMiddleware()`.
 *
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */
export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  function getState() {
    return currentState
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * You may call `dispatch()` from a change listener, with the following
   * caveats:
   *
   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
   * If you subscribe or unsubscribe while the listeners are being invoked, this
   * will not have any effect on the `dispatch()` that is currently in progress.
   * However, the next `dispatch()` call, whether nested or not, will use a more
   * recent snapshot of the subscription list.
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param {Object} action A plain object representing “what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/zenparsing/es-observable
   */
  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
<commit_msg>docs(observable): es-observable GitHub repo moved to tc39 (#2340)<commit_after>import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param {any} [preloadedState] The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 *
 * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 * to enhance the store with third-party capabilities such as middleware,
 * time travel, persistence, etc. The only store enhancer that ships with Redux
 * is `applyMiddleware()`.
 *
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */
export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  function getState() {
    return currentState
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * You may call `dispatch()` from a change listener, with the following
   * caveats:
   *
   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
   * If you subscribe or unsubscribe while the listeners are being invoked, this
   * will not have any effect on the `dispatch()` that is currently in progress.
   * However, the next `dispatch()` call, whether nested or not, will use a more
   * recent snapshot of the subscription list.
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param {Object} action A plain object representing “what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
<|endoftext|>


<commit_before>// <browser>

var Temple = {};

Temple.VERSION = '0.1.0';

/*
Operator table from RFC 6570, Appendix A, Implementation Hints.

.------------------------------------------------------------------.
|          NUL     +      .       /       ;      ?      &      #   |
|------------------------------------------------------------------|
| first |  ""     ""     "."     "/"     ";"    "?"    "&"    "#"  |
| sep   |  ","    ","    "."     "/"     ";"    "&"    "&"    ","  |
| named | false  false  false   false   true   true   true   false |
| ifemp |  ""     ""     ""      ""      ""     "="    "="    ""   |
| allow |   U     U+R     U       U       U      U      U     U+R  |
`------------------------------------------------------------------'

first:  String to append to the result first if any of the expression's
        variables are defined.

sep:    Separator to append to the result before any second (or subsequent)
        defined variable expansion.

named:  A boolean for whether or not the expansion includes the variable or
        key name when no explode modifier is given.

ifemp:  String to append to the name if the corresponding value is empty.

allow:  What characters to allow unencoded within the value expansion.
        'U' means any character not in the unreserved list will be encoded.
        'U+R' means any character not in the unreserved or reserved lists will
        be encoded.
*/

Temple.Operator = {
    /**
    Returns the operator configuration for the given operator name.

    @method get
    @param {String} Operator name.
    @return {Object} Operator configuration.
    **/
    get: function (op) {
        return this[op] || this['default'];
    },

    // Simple string expansion
    'default': {
        first: '',
        sep  : ',',
        named: false,
        ifemp: '',
        allow: 'U'
    },

    // Reserved expansion
    '+': {
        first: '',
        sep  : ',',
        named: false,
        ifemp: '',
        allow: 'U+R'
    },

    // Label expansion with dot-prefix
    '.': {
        first : '.',
        sep   : '.',
        named : false,
        ifemp : '',
        allow : 'U'
    },

    // Path segment expansion
    '/': {
        first: '/',
        sep  : '/',
        named: false,
        ifemp: '',
        allow: 'U'
    },

    // Path-style parameter expansion
    ';': {
        first: ';',
        sep  : ';',
        named: true,
        ifemp: '',
        allow: 'U'
    },

    // Form-style query expansion
    '?': {
        first: '?',
        sep  : '&',
        named: true,
        ifemp: '=',
        allow: 'U'
    },

    // Form-style query continuation
    '&': {
        first: '&',
        sep  : '&',
        named: true,
        ifemp: '=',
        allow: 'U'
    },

    // Fragment expansion
    '#': {
        first : '#',
        sep   : ',',
        named : false,
        ifemp : '',
        allow : 'U+R'
    }
};

(function () {
var Aproto = Array.prototype,

    // Regular expressions used for encoding expansion values.
    re  = {
        // Match characters not in the unreserved list.
        U: /[^\w~.\-]/g,

        // Match characters not in the ureserved or reserved lists.
        UR: /[^\w.~:\/\?#\[\]@!\$&'()*+,;=\-]/g,

        // Match invalid URI characters, and "%" characters not part of a
        // percent-encoded character. Percent-encoding detection can produce
        // false positives.
        L: /[\x01-\x1A]|[ "'<>\\\^`{|}]|%(?![a-fA-F0-9]{2})/g
    };

// Escape additional URI characters per RFC 3986: !, ', (, ), *
function _encodeURIComponent(str) {
    return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27')
        .replace(/\(/g, '%27').replace(/\)/g, '%28').replace(/\*/, '%2A');
}

/**
URL-encode portions of a value that match a regular expression. Optionally
avoid double-encoding of already URL-encoded values. Non-string values are
coerced to string before encoding.

@method encode
@param {Mixed} value The value to encode.
@param {RegExp} re Regular expression used for matching.
@param {Boolean} [decode] Whether or not to decode the string first, to
    prevent double-encoding. Default is false.
@return {String} The URI encoded string.
**/
function encode(value, re, decode) {
    var string = '' + value,
        m;

    if (decode && /\%/.test(string)) {
        try {
            string = decodeURIComponent(string);
        } catch (e) {}
    }

    return (function next(start) {
        if (m = re.exec(string)) {
            return string.slice(start, m.index) + _encodeURIComponent(m[0]) +
                next(m.index + 1);
        }

        return string.substring(start);
    }(0));
}

Temple.Encoder = {
    varname   : encodeURIComponent,
    literal   : function (value) { return encode(value, re.L);        },
    unreserved: function (value) { return encode(value, re.U,  true); },
    reserved  : function (value) { return encode(value, re.UR, true); }
};

// Shorthand aliases.
Temple.Encoder.L      = Temple.Encoder.literal;
Temple.Encoder.U      = Temple.Encoder.unreserved;
Temple.Encoder['U+R'] = Temple.Encoder.reserved;

Temple.Array = {
    each: Aproto.forEach ? function (array, fn, thisObj) {
        array.forEach(fn, thisObj);
    } : function (array, fn, thisObj) {
        var i, len;

        for (i = 0, len = (array && array.length) || 0; i < len; ++i) {
            if (array[i] !== undefined) {
                fn.call(thisObj, array[i], i, array);
            }
        }
    },

    filter: Aproto.filter ? function (array, fn, thisObj) {
        return array.filter(fn, thisObj);
    } : function (array, fn, thisObj) {
        var results = [];

        Temple.Array.each(array, function (item, index) {
            if (fn.call(thisObj, item, index, array)) {
                results.push(item);
            }
        });

        return results;
    },

    isArray: Array.isArray ? Array.isArray : function (o) {
        return Object.prototype.toString.call(o) === '[object Array]';
    },

    map: Aproto.map ? function (array, fn, thisObj) {
        return array.map(fn, thisObj);
    } : function (array, fn, thisObj) {
        var results = array.concat();

        Temple.Array.each(array, function (item, index) {
            results = fn.call(thisObj, item, index, array);
        }, thisObj);

        return results;
    }
};

Temple.Object = {
    each: function (o, fn, thisObj) {
        var key;

        for (key in o) {
            if (o.hasOwnProperty(key)) {
                fn.call(thisObj, o[key], key, o);
            }
        }
    },

    some: function (o, fn, thisObj) {
        var key;

        for (key in o) {
            if (o.hasOwnProperty(key) && fn.call(thisObj, o[key], key, o)) {
                return true;
            }
        }

        return false;
    }
};

}());

// </browser>

module.exports = Temple;
<commit_msg>Fix version<commit_after>// <browser>

var Temple = {};

Temple.VERSION = '0.0.1';

/*
Operator table from RFC 6570, Appendix A, Implementation Hints.

.------------------------------------------------------------------.
|          NUL     +      .       /       ;      ?      &      #   |
|------------------------------------------------------------------|
| first |  ""     ""     "."     "/"     ";"    "?"    "&"    "#"  |
| sep   |  ","    ","    "."     "/"     ";"    "&"    "&"    ","  |
| named | false  false  false   false   true   true   true   false |
| ifemp |  ""     ""     ""      ""      ""     "="    "="    ""   |
| allow |   U     U+R     U       U       U      U      U     U+R  |
`------------------------------------------------------------------'

first:  String to append to the result first if any of the expression's
        variables are defined.

sep:    Separator to append to the result before any second (or subsequent)
        defined variable expansion.

named:  A boolean for whether or not the expansion includes the variable or
        key name when no explode modifier is given.

ifemp:  String to append to the name if the corresponding value is empty.

allow:  What characters to allow unencoded within the value expansion.
        'U' means any character not in the unreserved list will be encoded.
        'U+R' means any character not in the unreserved or reserved lists will
        be encoded.
*/

Temple.Operator = {
    /**
    Returns the operator configuration for the given operator name.

    @method get
    @param {String} Operator name.
    @return {Object} Operator configuration.
    **/
    get: function (op) {
        return this[op] || this['default'];
    },

    // Simple string expansion
    'default': {
        first: '',
        sep  : ',',
        named: false,
        ifemp: '',
        allow: 'U'
    },

    // Reserved expansion
    '+': {
        first: '',
        sep  : ',',
        named: false,
        ifemp: '',
        allow: 'U+R'
    },

    // Label expansion with dot-prefix
    '.': {
        first : '.',
        sep   : '.',
        named : false,
        ifemp : '',
        allow : 'U'
    },

    // Path segment expansion
    '/': {
        first: '/',
        sep  : '/',
        named: false,
        ifemp: '',
        allow: 'U'
    },

    // Path-style parameter expansion
    ';': {
        first: ';',
        sep  : ';',
        named: true,
        ifemp: '',
        allow: 'U'
    },

    // Form-style query expansion
    '?': {
        first: '?',
        sep  : '&',
        named: true,
        ifemp: '=',
        allow: 'U'
    },

    // Form-style query continuation
    '&': {
        first: '&',
        sep  : '&',
        named: true,
        ifemp: '=',
        allow: 'U'
    },

    // Fragment expansion
    '#': {
        first : '#',
        sep   : ',',
        named : false,
        ifemp : '',
        allow : 'U+R'
    }
};

(function () {
var Aproto = Array.prototype,

    // Regular expressions used for encoding expansion values.
    re  = {
        // Match characters not in the unreserved list.
        U: /[^\w~.\-]/g,

        // Match characters not in the ureserved or reserved lists.
        UR: /[^\w.~:\/\?#\[\]@!\$&'()*+,;=\-]/g,

        // Match invalid URI characters, and "%" characters not part of a
        // percent-encoded character. Percent-encoding detection can produce
        // false positives.
        L: /[\x01-\x1A]|[ "'<>\\\^`{|}]|%(?![a-fA-F0-9]{2})/g
    };

// Escape additional URI characters per RFC 3986: !, ', (, ), *
function _encodeURIComponent(str) {
    return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27')
        .replace(/\(/g, '%27').replace(/\)/g, '%28').replace(/\*/, '%2A');
}

/**
URL-encode portions of a value that match a regular expression. Optionally
avoid double-encoding of already URL-encoded values. Non-string values are
coerced to string before encoding.

@method encode
@param {Mixed} value The value to encode.
@param {RegExp} re Regular expression used for matching.
@param {Boolean} [decode] Whether or not to decode the string first, to
    prevent double-encoding. Default is false.
@return {String} The URI encoded string.
**/
function encode(value, re, decode) {
    var string = '' + value,
        m;

    if (decode && /\%/.test(string)) {
        try {
            string = decodeURIComponent(string);
        } catch (e) {}
    }

    return (function next(start) {
        if (m = re.exec(string)) {
            return string.slice(start, m.index) + _encodeURIComponent(m[0]) +
                next(m.index + 1);
        }

        return string.substring(start);
    }(0));
}

Temple.Encoder = {
    varname   : encodeURIComponent,
    literal   : function (value) { return encode(value, re.L);        },
    unreserved: function (value) { return encode(value, re.U,  true); },
    reserved  : function (value) { return encode(value, re.UR, true); }
};

// Shorthand aliases.
Temple.Encoder.L      = Temple.Encoder.literal;
Temple.Encoder.U      = Temple.Encoder.unreserved;
Temple.Encoder['U+R'] = Temple.Encoder.reserved;

Temple.Array = {
    each: Aproto.forEach ? function (array, fn, thisObj) {
        array.forEach(fn, thisObj);
    } : function (array, fn, thisObj) {
        var i, len;

        for (i = 0, len = (array && array.length) || 0; i < len; ++i) {
            if (array[i] !== undefined) {
                fn.call(thisObj, array[i], i, array);
            }
        }
    },

    filter: Aproto.filter ? function (array, fn, thisObj) {
        return array.filter(fn, thisObj);
    } : function (array, fn, thisObj) {
        var results = [];

        Temple.Array.each(array, function (item, index) {
            if (fn.call(thisObj, item, index, array)) {
                results.push(item);
            }
        });

        return results;
    },

    isArray: Array.isArray ? Array.isArray : function (o) {
        return Object.prototype.toString.call(o) === '[object Array]';
    },

    map: Aproto.map ? function (array, fn, thisObj) {
        return array.map(fn, thisObj);
    } : function (array, fn, thisObj) {
        var results = array.concat();

        Temple.Array.each(array, function (item, index) {
            results = fn.call(thisObj, item, index, array);
        }, thisObj);

        return results;
    }
};

Temple.Object = {
    each: function (o, fn, thisObj) {
        var key;

        for (key in o) {
            if (o.hasOwnProperty(key)) {
                fn.call(thisObj, o[key], key, o);
            }
        }
    },

    some: function (o, fn, thisObj) {
        var key;

        for (key in o) {
            if (o.hasOwnProperty(key) && fn.call(thisObj, o[key], key, o)) {
                return true;
            }
        }

        return false;
    }
};

}());

// </browser>

module.exports = Temple;
<|endoftext|>


<commit_before>/*jshint evil:true, unused:false */
/*global jake:true, require:true, module:true, process:true */

var fs = require("fs");
var Handlebars = require("handlebars");
var exec = require("child_process").exec;
var util = require("./utils");

var source = {
	toc: Handlebars.compile([
		'## Table of Contents\n',
		'{{#each classes}}',
			'* [[{{.}}|{{.}}]]\n',
		'{{/each}}',
	''].join('')),

	page: Handlebars.compile([
		'## Table of Contents\n',
		'* [Constructor](#{{class}}-method)\n',
		'{{#if property}}',
			'* [Properties](#properties)\n',
			'{{#each property}}',
			'	* [{{name}}](#{{name}}-property)\n',
			'{{/each}}',
		'{{/if}}',
		'{{#if method}}',
			'* [Methods](#methods)\n',
			'{{#each method}}',
			'	* [{{name}}({{{formatSignature params}}})](#{{name}}-{{formatAnchorSuffix params}}-method)\n',
			'{{/each}}',
		'{{/if}}',
		'{{#if event}}',
			'* [Events](#events)\n',
			'{{#each event}}',
			'	* [{{name}}](#{{name}}-event)\n',
			'{{/each}}',
		'{{/if}}\n',

		'{{{formatConstructor .}}}',

		'{{#if property}}',
			'\n',
			'## Properties\n\n',

			'{{#each property}}',
				'\n',
				'### [{{name}}]({{srcUrl}} "Defined at: {{file}}:{{line}}")\n\n',

				'{{{description}}}\n\n',

				'{{#if example}}',
					'__Example__\n',
					'{{#each example}}',
					'	{{formatExample .}}\n',
					'{{/each}}',
				'{{/if}}',
			'{{/each}}\n',
		'{{/if}}',

		'{{#if method}}',
			'\n',
			'## Methods\n\n',

			'{{#each method}}',
				'{{> method}}',
			'{{/each}}\n',
		'{{/if}}',

		'{{#if event}}',
			'\n',
			'## Events\n',
			'{{#each event}}',
				'\n',
				'### {{name}}\n\n',

				'{{{description}}}\n\n',

				'{{#if example}}',
					'__Example__\n',
					'{{#each example}}',
					'	{{{formatExample .}}}\n',
					'{{/each}}',
				'{{/if}}',
			'{{/each}}\n',
		'{{/if}}',
	''].join('')),

	constructor: Handlebars.compile([
		'## Constructor\n',
		'{{> method}}',
	''].join('')),

	method: Handlebars.compile([
		'\n',
		'### [{{name}}({{{formatSignature params}}})]({{srcUrl}} "Defined at: {{file}}:{{line}}")\n\n',

		'{{{description}}}\n\n',

		'{{#if params}}',
			'__Arguments__\n',
			'{{#each params}}',
				'{{> argument}}',
			'{{/each}}\n',
		'{{/if}}',

		'{{#if example}}',
			'__Example__\n',
			'{{#each example}}',
			'	{{{formatExample .}}}\n',
			'{{/each}}\n',
		'{{/if}}',
	''].join('')),

	argument: Handlebars.compile([
		'{{#if type}}',
			'* **{{#if optional}}',
				'{{#if optdefault}}',
					'[{{name}}={{{optdefault}}}]',
				'{{else}}',
					'[{{name}}]',
				'{{/if}}',
			'{{else}}',
				'{{name}}',
			'{{/if}}** `{{type}}`  \n',
			'{{{formatArgDescription .}}}\n',
			'{{#if props}}',
				'{{#each props}}',
					'{{indent level}}{{> argument}}',
				'{{/each}}',
			'{{/if}}',
		'{{/if}}',
	''].join(''))
};

function generatePages(githubRepo, dir, YUIDocDir) {
	if (!fs.existsSync(dir) || !fs.existsSync(YUIDocDir + "/data.json")) {
		process.exit(1);
	}

	// clear previous versions
	var apiDir = dir + "/API";
	if (fs.existsSync(apiDir)) {
		jake.rmRf(apiDir);
	}
	fs.mkdirSync(apiDir, 0755);

	// load YUIDoc exported data
	var data = eval("("+fs.readFileSync(YUIDocDir + "/data.json").toString()+")");

	// parse the data and generate the pages
	var srcUrl = "/" + githubRepo.replace(/^[\s\S]+?github\.com[:\/]([\s\S]+?)\.wiki[\s\S]+$/, '$1') + "/blob/master/";

	var defineArgIndentation = function(args, level) {
		level = level || 0;
		util.each(args, function(arg) {
			arg.level = level;
			if (arg.props) {
				defineArgIndentation(arg.props, level + 1);
			}
		});
	};

	// prepare class items...
	util.each(data.classitems, function(item) {
		if (!data.classes[item.class]) { // class striped off as not required
			return true;
		}

		// bypass private and protected
		if (data.classes[item.class].access && data.classes[item.class].access != 'public') {
			delete data.classes[item.class];
			return true;
		}

		if (item.access && item.access != 'public') {
			return true;
		}

		if (!~['method', 'property', 'event'].indexOf(item.itemtype)) {
			return true;
		}

		if (util.isArray(data.classes[item.class].classitems)) {
			data.classes[item.class].classitems = {
				property: [],
				method: [],
				event: []
			};
		}

		// define a link to an item's place in a source
		item.srcUrl = srcUrl + item.file + '#L' + item.line;

		// come up with argument indentation level
		if (typeof data.classes[item.class].level == 'undefined' && data.classes[item.class].params) {
			defineArgIndentation(data.classes[item.class].params);
		}

		if (item.itemtype === 'method' && item.params) {
			defineArgIndentation(item.params);
		}

		data.classes[item.class].classitems[item.itemtype].push(item);
	});

	// define Handlebars helpers
	Handlebars.registerHelper('formatConstructor', function(item) {
		var fn = data.classes[item.class];

		if (fn.is_constructor != 1) {
			return '';
		}

		fn.srcUrl = srcUrl + fn.file + '#L' + fn.line;
		return source.constructor(fn);
	});

	Handlebars.registerHelper('formatSignature', function(params) {
		var titleArgs = [];
		util.each(params, function(param) {
			var name;
			if (param.type) {
				if (param.optional) {
					name = param.optdefault ? "[" + param.name + "=" + param.optdefault + "]" : "[" + param.name + "]";
				} else {
					name = param.name;
				}
				titleArgs.push(name);
			}
		});
		return titleArgs.join(", ");
	});

	Handlebars.registerHelper('formatAnchorSuffix', function(params) {
		return Handlebars.helpers.formatSignature(params).replace(/\W+/g, '');
	});

	Handlebars.registerHelper('formatExample', function(example) {
		var type = /<\w+>/.test(example) ? 'html' : 'javascript';
		return "```" + type + "\n" + example.replace(/^\xA0+/, '') + "\n\t```\n";
	});

	Handlebars.registerHelper('formatArgDescription', function(arg) {
		var indent = Handlebars.helpers.indent(arg.level);
		var description = arg.description.replace(/(```[^`]+```)/g, function($0, $1) {
			return $1.replace(/\n/g, '\n' + indent);
		});
		return indent + description.replace(/\n\n/g, '\n\n' + indent);
	});

	Handlebars.registerHelper('formatType', function(type) {
		return type.replace(/\|/g, '\\');
	});

	Handlebars.registerHelper('indent', function(level) {
		var indent = '';
		while (level--) {
			indent += '\t';
		}
		return indent;
	});

	// define partials
	Handlebars.registerPartial('argument', source.argument);
	Handlebars.registerPartial('method', source.method);

	// generate TOC
	fs.writeFileSync(apiDir + "/" + "API.md", source.toc({
		classes: Object.keys(data.classes)
	}));

	// generate pages
	util.each(data.classes, function(item, className) {
		fs.writeFileSync(apiDir + "/" + className + ".md", source.page({
			class: className,
			property: item.classitems.property,
			method: item.classitems.method,
			event: item.classitems.event
		}));
	});
}

module.exports = function(githubRepo, dir, YUIDocDir) {
	// make sure we have the repo
	if (!fs.existsSync(dir)) {
		exec("git clone " + githubRepo + " ./" + dir, function() {
			generatePages.apply(this, arguments);
		});
	} else {
		generatePages.apply(this, arguments);
	}
};
<commit_msg>Build, Wiki: Add markers to static methods and properties.<commit_after>/*jshint evil:true, unused:false */
/*global jake:true, require:true, module:true, process:true */

var fs = require("fs");
var Handlebars = require("handlebars");
var exec = require("child_process").exec;
var util = require("./utils");

var source = {
	toc: Handlebars.compile([
		'## Table of Contents\n',
		'{{#each classes}}',
			'* [[{{.}}|{{.}}]]\n',
		'{{/each}}',
	''].join('')),

	page: Handlebars.compile([
		'## Table of Contents\n',
		'* [Constructor](#{{class}}-method)\n',
		'{{#if property}}',
			'* [Properties](#properties)\n',
			'{{#each property}}',
			'	* [{{name}}](#{{name}}-property) {{#if static}}`static`{{/if}}\n',
			'{{/each}}',
		'{{/if}}',
		'{{#if method}}',
			'* [Methods](#methods)\n',
			'{{#each method}}',
			'	* [{{name}}({{{formatSignature params}}})](#{{name}}-{{formatAnchorSuffix params}}-method) {{#if static}}`static`{{/if}}\n',
			'{{/each}}',
		'{{/if}}',
		'{{#if event}}',
			'* [Events](#events)\n',
			'{{#each event}}',
			'	* [{{name}}](#{{name}}-event)\n',
			'{{/each}}',
		'{{/if}}\n',

		'{{{formatConstructor .}}}',

		'{{#if property}}',
			'\n',
			'## Properties\n\n',

			'{{#each property}}',
				'\n',
				'### [{{name}}]({{srcUrl}} "Defined at: {{file}}:{{line}}") {{#if static}}`static`{{/if}}\n\n',

				'{{{description}}}\n\n',

				'{{#if example}}',
					'__Example__\n',
					'{{#each example}}',
					'	{{formatExample .}}\n',
					'{{/each}}',
				'{{/if}}',
			'{{/each}}\n',
		'{{/if}}',

		'{{#if method}}',
			'\n',
			'## Methods\n\n',

			'{{#each method}}',
				'{{> method}}',
			'{{/each}}\n',
		'{{/if}}',

		'{{#if event}}',
			'\n',
			'## Events\n',
			'{{#each event}}',
				'\n',
				'### {{name}}\n\n',

				'{{{description}}}\n\n',

				'{{#if example}}',
					'__Example__\n',
					'{{#each example}}',
					'	{{{formatExample .}}}\n',
					'{{/each}}',
				'{{/if}}',
			'{{/each}}\n',
		'{{/if}}',
	''].join('')),

	constructor: Handlebars.compile([
		'## Constructor\n',
		'{{> method}}',
	''].join('')),

	method: Handlebars.compile([
		'\n',
		'### [{{name}}({{{formatSignature params}}})]({{srcUrl}} "Defined at: {{file}}:{{line}}") {{#if static}}`static`{{/if}}\n\n',

		'{{{description}}}\n\n',

		'{{#if params}}',
			'__Arguments__\n',
			'{{#each params}}',
				'{{> argument}}',
			'{{/each}}\n',
		'{{/if}}',

		'{{#if example}}',
			'__Example__\n',
			'{{#each example}}',
			'	{{{formatExample .}}}\n',
			'{{/each}}\n',
		'{{/if}}',
	''].join('')),

	argument: Handlebars.compile([
		'{{#if type}}',
			'* **{{#if optional}}',
				'{{#if optdefault}}',
					'[{{name}}={{{optdefault}}}]',
				'{{else}}',
					'[{{name}}]',
				'{{/if}}',
			'{{else}}',
				'{{name}}',
			'{{/if}}** `{{type}}`  \n',
			'{{{formatArgDescription .}}}\n',
			'{{#if props}}',
				'{{#each props}}',
					'{{indent level}}{{> argument}}',
				'{{/each}}',
			'{{/if}}',
		'{{/if}}',
	''].join(''))
};

function generatePages(githubRepo, dir, YUIDocDir) {
	if (!fs.existsSync(dir) || !fs.existsSync(YUIDocDir + "/data.json")) {
		process.exit(1);
	}

	// clear previous versions
	var apiDir = dir + "/API";
	if (fs.existsSync(apiDir)) {
		jake.rmRf(apiDir);
	}
	fs.mkdirSync(apiDir, 0755);

	// load YUIDoc exported data
	var data = eval("("+fs.readFileSync(YUIDocDir + "/data.json").toString()+")");

	// parse the data and generate the pages
	var srcUrl = "/" + githubRepo.replace(/^[\s\S]+?github\.com[:\/]([\s\S]+?)\.wiki[\s\S]+$/, '$1') + "/blob/master/";

	var defineArgIndentation = function(args, level) {
		level = level || 0;
		util.each(args, function(arg) {
			arg.level = level;
			if (arg.props) {
				defineArgIndentation(arg.props, level + 1);
			}
		});
	};

	// prepare class items...
	util.each(data.classitems, function(item) {
		if (!data.classes[item.class]) { // class striped off as not required
			return true;
		}

		// bypass private and protected
		if (data.classes[item.class].access && data.classes[item.class].access != 'public') {
			delete data.classes[item.class];
			return true;
		}

		if (item.access && item.access != 'public') {
			return true;
		}

		if (!~['method', 'property', 'event'].indexOf(item.itemtype)) {
			return true;
		}

		if (util.isArray(data.classes[item.class].classitems)) {
			data.classes[item.class].classitems = {
				property: [],
				method: [],
				event: []
			};
		}

		// define a link to an item's place in a source
		item.srcUrl = srcUrl + item.file + '#L' + item.line;

		// come up with argument indentation level
		if (typeof data.classes[item.class].level == 'undefined' && data.classes[item.class].params) {
			defineArgIndentation(data.classes[item.class].params);
		}

		if (item.itemtype === 'method' && item.params) {
			defineArgIndentation(item.params);
		}

		data.classes[item.class].classitems[item.itemtype].push(item);
	});

	// define Handlebars helpers
	Handlebars.registerHelper('formatConstructor', function(item) {
		var fn = data.classes[item.class];

		if (fn.is_constructor != 1) {
			return '';
		}

		fn.srcUrl = srcUrl + fn.file + '#L' + fn.line;
		return source.constructor(fn);
	});

	Handlebars.registerHelper('formatSignature', function(params) {
		var titleArgs = [];
		util.each(params, function(param) {
			var name;
			if (param.type) {
				if (param.optional) {
					name = param.optdefault ? "[" + param.name + "=" + param.optdefault + "]" : "[" + param.name + "]";
				} else {
					name = param.name;
				}
				titleArgs.push(name);
			}
		});
		return titleArgs.join(", ");
	});

	Handlebars.registerHelper('formatAnchorSuffix', function(params) {
		return Handlebars.helpers.formatSignature(params).replace(/\W+/g, '');
	});

	Handlebars.registerHelper('formatExample', function(example) {
		var type = /<\w+>/.test(example) ? 'html' : 'javascript';
		return "```" + type + "\n" + example.replace(/^\xA0+/, '') + "\n\t```\n";
	});

	Handlebars.registerHelper('formatArgDescription', function(arg) {
		var indent = Handlebars.helpers.indent(arg.level);
		var description = arg.description.replace(/(```[^`]+```)/g, function($0, $1) {
			return $1.replace(/\n/g, '\n' + indent);
		});
		return indent + description.replace(/\n\n/g, '\n\n' + indent);
	});

	Handlebars.registerHelper('formatType', function(type) {
		return type.replace(/\|/g, '\\');
	});

	Handlebars.registerHelper('indent', function(level) {
		var indent = '';
		while (level--) {
			indent += '\t';
		}
		return indent;
	});

	// define partials
	Handlebars.registerPartial('argument', source.argument);
	Handlebars.registerPartial('method', source.method);

	// generate TOC
	fs.writeFileSync(apiDir + "/" + "API.md", source.toc({
		classes: Object.keys(data.classes)
	}));

	// generate pages
	util.each(data.classes, function(item, className) {
		fs.writeFileSync(apiDir + "/" + className + ".md", source.page({
			class: className,
			property: item.classitems.property,
			method: item.classitems.method,
			event: item.classitems.event
		}));
	});
}

module.exports = function(githubRepo, dir, YUIDocDir) {
	// make sure we have the repo
	if (!fs.existsSync(dir)) {
		exec("git clone " + githubRepo + " ./" + dir, function() {
			generatePages.apply(this, arguments);
		});
	} else {
		generatePages.apply(this, arguments);
	}
};
<|endoftext|>


<commit_before>var BlueRidge = BlueRidge || {};

BlueRidge.CommandLine = {
  passed: 0,

  failed: 0,

  messages: [],

  require: function(file, options){
    load(BlueRidge.CommandLine.prepareFilenameForRequireBasedOnSpecDirectory(file));

    options = options || {};
    if(options['onload']) {
      options['onload'].call();
    }
  },

  debug: function(message){
    print(message);
  },

  prepareFilenameForRequireBasedOnSpecDirectory: function(filename){
    if(filename == null || filename[0] == "/") { return filename; }
    return (this.specDirname == null) ? filename : (this.specDirname + "/" + filename);
  },

  get fixtureFile(){
    return "fixtures/" + this.specFile.replace(/^(.*?)_spec\.js$/, "$1.html");
  },

  get specDirname(){
    if(this.specFile == null) { return null; }
    var pathComponents = this.specFile.split("/");
    var filename = pathComponents.pop();
    return (pathComponents.length > 0) ? pathComponents.join("/") : null;
  },

  get specBasename(){
    if(this.specFile == null) { return null; }
    return this.specFile.split("/").pop();
  },

  exampleName: function(element){
    var exampleName = $.trim($(element).children("h2").text());

    var names = this.contextNamesForExample(element);
    names.push(exampleName);

    return names.join(" ");
  },

  contextNamesForExample: function(element){
    var describes = $(element).parents('.describe').children('h1');

    var contextNames = $.map(describes, function(context){
      return $.trim($(context).text());
    });

    return contextNames.reverse();
  }
};

if(BlueRidge.loaded != true) {
  for (var i = 0; i < arguments.length; i++) {
    for (var name in this) {
      if ('BlueRidge' != name) {
        delete this[name];
      }
    }

    BlueRidge.CommandLine.specFile = arguments[i];

    var require = require || BlueRidge.CommandLine.require;
    var debug   = debug   || BlueRidge.CommandLine.debug;
    var console = console || { debug : debug, info : debug, log : debug };

    var BLUE_RIDGE_LIB_PREFIX = (environment["blue.ridge.prefix"] || "../../vendor/plugins/blue-ridge") + "/lib/";
    require(BLUE_RIDGE_LIB_PREFIX + "env.rhino.js");

    Envjs(BlueRidge.CommandLine.fixtureFile, {
      loadInlineScript: function(){},
      log: function(){}
    });

    require(BLUE_RIDGE_LIB_PREFIX + "jquery-1.3.2.js");
    require(BLUE_RIDGE_LIB_PREFIX + "jquery.fn.js");
    require(BLUE_RIDGE_LIB_PREFIX + "jquery.print.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.builder.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.matchers.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.events.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.behaviors.js");
    require(BLUE_RIDGE_LIB_PREFIX + "smoke.core.js");
    require(BLUE_RIDGE_LIB_PREFIX + "smoke.mock.js");
    require(BLUE_RIDGE_LIB_PREFIX + "smoke.stub.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.mocking.js");
    require(BLUE_RIDGE_LIB_PREFIX + "consoleReportForRake.js");

    BlueRidge.loaded = true;
    load(BlueRidge.CommandLine.specFile);
    jQuery(window).trigger("load");
  }

  print("\n");

  jQuery.each(BlueRidge.CommandLine.messages, function(index, message) {
    print(message);
  });

  print((BlueRidge.CommandLine.passed + BlueRidge.CommandLine.failed) + ' tests, ' + BlueRidge.CommandLine.failed + ' failures');
}<commit_msg>Rhino Test Runner gibt bei Failures den korrekten Exit Code zurück.<commit_after>var BlueRidge = BlueRidge || {};

BlueRidge.CommandLine = {
  passed: 0,

  failed: 0,

  messages: [],

  require: function(file, options){
    load(BlueRidge.CommandLine.prepareFilenameForRequireBasedOnSpecDirectory(file));

    options = options || {};
    if(options['onload']) {
      options['onload'].call();
    }
  },

  debug: function(message){
    print(message);
  },

  prepareFilenameForRequireBasedOnSpecDirectory: function(filename){
    if(filename == null || filename[0] == "/") { return filename; }
    return (this.specDirname == null) ? filename : (this.specDirname + "/" + filename);
  },

  get fixtureFile(){
    return "fixtures/" + this.specFile.replace(/^(.*?)_spec\.js$/, "$1.html");
  },

  get specDirname(){
    if(this.specFile == null) { return null; }
    var pathComponents = this.specFile.split("/");
    var filename = pathComponents.pop();
    return (pathComponents.length > 0) ? pathComponents.join("/") : null;
  },

  get specBasename(){
    if(this.specFile == null) { return null; }
    return this.specFile.split("/").pop();
  },

  exampleName: function(element){
    var exampleName = $.trim($(element).children("h2").text());

    var names = this.contextNamesForExample(element);
    names.push(exampleName);

    return names.join(" ");
  },

  contextNamesForExample: function(element){
    var describes = $(element).parents('.describe').children('h1');

    var contextNames = $.map(describes, function(context){
      return $.trim($(context).text());
    });

    return contextNames.reverse();
  }
};

if(BlueRidge.loaded != true) {
  for (var i = 0; i < arguments.length; i++) {
    for (var name in this) {
      if ('BlueRidge' != name) {
        delete this[name];
      }
    }

    BlueRidge.CommandLine.specFile = arguments[i];

    var require = require || BlueRidge.CommandLine.require;
    var debug   = debug   || BlueRidge.CommandLine.debug;
    var console = console || { debug : debug, info : debug, log : debug };

    var BLUE_RIDGE_LIB_PREFIX = (environment["blue.ridge.prefix"] || "../../vendor/plugins/blue-ridge") + "/lib/";
    require(BLUE_RIDGE_LIB_PREFIX + "env.rhino.js");

    Envjs(BlueRidge.CommandLine.fixtureFile, {
      loadInlineScript: function(){},
      log: function(){}
    });

    require(BLUE_RIDGE_LIB_PREFIX + "jquery-1.3.2.js");
    require(BLUE_RIDGE_LIB_PREFIX + "jquery.fn.js");
    require(BLUE_RIDGE_LIB_PREFIX + "jquery.print.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.builder.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.matchers.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.events.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.behaviors.js");
    require(BLUE_RIDGE_LIB_PREFIX + "smoke.core.js");
    require(BLUE_RIDGE_LIB_PREFIX + "smoke.mock.js");
    require(BLUE_RIDGE_LIB_PREFIX + "smoke.stub.js");
    require(BLUE_RIDGE_LIB_PREFIX + "screw.mocking.js");
    require(BLUE_RIDGE_LIB_PREFIX + "consoleReportForRake.js");

    BlueRidge.loaded = true;
    load(BlueRidge.CommandLine.specFile);
    jQuery(window).trigger("load");
  }

  print("\n");

  jQuery.each(BlueRidge.CommandLine.messages, function(index, message) {
    print(message);
  });

  print((BlueRidge.CommandLine.passed + BlueRidge.CommandLine.failed) + ' tests, ' + BlueRidge.CommandLine.failed + ' failures');

  if (BlueRidge.CommandLine.failed > 0) { java.lang.System.exit(1) };
}<|endoftext|>


<commit_before>'use babel';

/* @flow */
import type { TextEditor } from 'atom';
import * as jestRunner from './spawn-runner';

export function activate() {
  require('atom-package-deps').install();
}

export function deactivate() {
  // Fill something here, optional
}

export function provideTester() {
  return {
    name: 'tester-jest',
    options: {},
    scopes: atom.config.get('tester-jest.scopes'),
    test(textEditor :TextEditor) {
      if (!textEditor) {
        return Promise.resolve({ messages: [], output: '' });
      }
      const text = textEditor.getText();
      if (text.length === 0) {
        return Promise.resolve({ messages: [], output: '' });
      }
      // Note, a Promise may be returned as well!
      return jestRunner.run(textEditor);
    },
    stop() {
      jestRunner.stop();
    },
  };
}
<commit_msg>allow empty param in run method<commit_after>'use babel';

/* @flow */
import type { TextEditor } from 'atom';
import * as jestRunner from './spawn-runner';

export function activate() {
  require('atom-package-deps').install();
}

export function deactivate() {
  // Fill something here, optional
}

export function provideTester() {
  return {
    name: 'tester-jest',
    options: {},
    scopes: atom.config.get('tester-jest.scopes'),
    test(textEditor :TextEditor) {
      // Note, a Promise may be returned as well!
      return jestRunner.run(textEditor);
    },
    stop() {
      jestRunner.stop();
    },
  };
}
<|endoftext|>