<commit_before>window.cls || (window.cls = {});

/**
  * @constructor
  */
cls.Breakpoint = function(id, script_id, line_nr, event_type, active_rt_ids)
{
  this.id = id;
  this.script_id = script_id || "";
  this.line_nr = line_nr || 0;
  this.event_type = event_type || "";
  this.type = this.script_id ? "source" : "event";
  this.is_enabled = true;
  this.condition = "";
  this.update_is_active(active_rt_ids);
};

cls.Breakpoint.prototype = new function()
{
  this._is_active = true;

  this.__defineGetter__('is_active', function()
  {
    return this._is_active;
  });

  this.__defineSetter__('is_active', function(){});

  this.update_is_active = function(active_rt_ids)
  {
    if (this.script_id)
    {
      var rt_id = window.runtimes.getScriptsRuntimeId(this.script_id);
      this._is_active = active_rt_ids.indexOf(rt_id) != -1;
    }
  }
}

/**
  * @constructor
  */
cls.Breakpoints = function()
{

  if (cls.Breakpoints.instance)
  {
    return cls.Breakpoints.instance;
  }

  cls.Breakpoints.instance = this;

  /* interface */

  this.get_breakpoints = function(){};
  this.get_active_breakpoints = function(){};
  this.get_breakpoint_with_id = function(bp_id){};
  this.script_has_breakpoint_on_line = function(script_id, line_nr){};
  this.get_breakpoint_on_script_line = function(script_id, line_nr){};
  // bp_id optional
  this.add_breakpoint = function(script_id, line_nr, bp_id){};
  // removes the breakpoint in the host
  // disables the breakpoint in the client
  this.remove_breakpoint = function(script_id, line_nr){};
  this.delete_breakpoint = function(bp_id){};
  this.add_event_breakpoint = function(event_name){};
  this.remove_event_breakpoint = function(bp_id){};
  this.copy_breakpoints = function(new_script, old_script){};
  this.set_condition = function(condition, bp_id){};
  this.get_condition = function(bp_id){};

  /* constants */

  const
  BP_NONE = cls.NewScript.BP_NONE,
  BP_DISABLED = cls.NewScript.BP_DISABLED,
  BP_ENABLED = cls.NewScript.BP_ENABLED,
  BP_DELTA_ENABLE = BP_ENABLED - BP_DISABLED,
  BP_DELTA_CONDITION = cls.NewScript.BP_ENABLED_CONDITION - BP_ENABLED;

  /* private */

  this._get_bp_id = (function()
  {
    var count = 1;
    return function() {return count++;}
  })();

  this._add_bp = function(bp_id, script_id, line_nr, event_type)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    if (bp)
    {
      bp.is_enabled = true;
    }
    else
    {
      this._bps.push(new this._bp_class(bp_id,
                                        script_id,
                                        line_nr,
                                        event_type,
                                        this._active_rt_ids));
    }
    window.messages.post("breakpoint-updated",
                         {id: bp_id, script_id: script_id});
  };

  this._remove_bp = function(bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    if (bp)
    {
      bp.is_enabled = false;
      window.messages.post("breakpoint-updated",
                           {id: bp.id, script_id: bp.script_id});
    }
  };

  this._get_bp_id_with_script_id_and_line_nr = function(script_id, line_nr)
  {
    for (var i = 0, bp; bp = this._bps[i]; i++)
    {
      if (bp.script_id == script_id && bp.line_nr == line_nr)
      {
        return bp.id;
      }
    }
    return 0;
  };

  this._update_bp_state = function(bp, delta, absolute)
  {
    var script = bp && window.runtimes.getScript(bp.script_id);
    if (script)
    {
      if (delta)
      {
        script.breakpoint_states[bp.line_nr] += delta;
      }
      else
      {
        var state = script.breakpoint_states[bp.line_nr] || 0;
        var pointer_state = state % 3;
        script.breakpoint_states[bp.line_nr] = absolute;
        script.breakpoint_states[bp.line_nr] += pointer_state;
      }
    }
    window.messages.post('breakpoint-updated',
                         {id: bp.id,
                          script_id: bp.script_id,
                          event_type: bp.event_type});
  };

  this._get_bp_id_with_event_name = function(event_name)
  {
    for (var i = 0, bp; bp = this._bps[i]; i++)
    {
      if (bp.event_type == event_name)
      {
        return bp.id;
      }
    }
    return 0;
  };

  this._replace_script_id = function(new_script_id, old_script_id)
  {
    for (var i = 0, bp; bp = this._bps[i]; i++)
    {
      if (bp.script_id == old_script_id)
      {
        bp.script_id = new_script_id;
        bp.update_is_active(this._active_rt_ids);
      }
    }
  };

  this._get_bp_index_with_bp_id = function(bp_id)
  {
    for (var i = 0; i < this._bps.length && this._bps[i].id != bp_id; i++);
    return i;
  };

  this._on_active_tab = function(msg)
  {
    this._active_rt_ids = msg.activeTab;
    this._bps.forEach(function(bp)
    {
      bp.update_is_active(this._active_rt_ids);
    }, this);
    window.views.breakpoints.update();
  };

  this._init = function()
  {
    this._bps = [];
    this._bp_class = window.cls.Breakpoint;
    this._esdb = window.services['ecmascript-debugger'];
    this._active_rt_ids = [];
    window.messages.addListener('active-tab', this._on_active_tab.bind(this));
  };

  /* implementation */

  this.get_breakpoints = function()
  {
    return this._bps;
  };

  this.get_active_breakpoints = function()
  {
    return this._bps.filter(function(bp)
    {
      return bp.is_active;
    });
  };

  this.get_breakpoint_with_id = function(bp_id)
  {
    return this._bps[this._get_bp_index_with_bp_id(bp_id)];
  };

  this.add_breakpoint = function(script_id, line_nr, bp_id)
  {
    var script = window.runtimes.getScript(script_id);
    if (script)
    {
      bp_id = bp_id ||
              // if a breakpoint was set on the same script and line before
              this._get_bp_id_with_script_id_and_line_nr(script_id, line_nr) ||
              this._get_bp_id();
      script.breakpoints[line_nr] = bp_id;
      var state = script.breakpoint_states[line_nr] || 0;
      var pointer_state = state % 3;
      state -= pointer_state;
      if (!state)
      {
        script.breakpoint_states[line_nr] = BP_DISABLED;
      }
      if (script.breakpoint_states[line_nr] < BP_ENABLED)
      {
        script.breakpoint_states[line_nr] += BP_DELTA_ENABLE;
      }
      script.breakpoint_states[line_nr] += pointer_state;
      // message signature has changes with version 6.0
      // AddBreakpoint means always to a source line
      // for events it's now AddEventBreakpoint
      if (this._esdb.major_version > 5)
      {
        this._esdb.requestAddBreakpoint(0, [bp_id, script_id, line_nr]);
      }
      else
      {
        this._esdb.requestAddBreakpoint(0, [bp_id, "line", script_id, line_nr]);
      }
      this._add_bp(bp_id, script_id, line_nr);
    }
  };

  this.remove_breakpoint = function(script_id, line_nr)
  {
    var script = window.runtimes.getScript(script_id);
    if (script)
    {
      var bp_id = script.breakpoints[line_nr];
      this._esdb.requestRemoveBreakpoint(0, [bp_id]);
      script.breakpoints[line_nr] = 0;
      var state = script.breakpoint_states[line_nr] || 0;
      var pointer_state = state % 3;
      state -= pointer_state;
      if (state >= BP_ENABLED)
      {
        script.breakpoint_states[line_nr] = state - BP_DELTA_ENABLE;
      }
      script.breakpoint_states[line_nr] += pointer_state;
      this._remove_bp(bp_id);
      return bp_id;
    }
  };

  this.delete_breakpoint = function(bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    this._bps.splice(this._get_bp_index_with_bp_id(bp_id), 1);
    this._update_bp_state(bp, 0, BP_NONE);
  };

  this.add_event_breakpoint = function(event_name)
  {
    var bp_id = this._get_bp_id_with_event_name(event_name) ||
                this._get_bp_id();
    this._esdb.requestAddEventBreakpoint(0, [bp_id, event_name]);
    this._add_bp(bp_id, null, null, event_name);
    return bp_id;
  };

  this.remove_event_breakpoint = function(bp_id)
  {
    this._esdb.requestRemoveBreakpoint(0, [bp_id]);
    this._remove_bp(bp_id);
  };

  this.copy_breakpoints = function(new_script, old_script)
  {
    new_script.breakpoint_states = old_script.breakpoint_states;
    this._replace_script_id(new_script.script_id, old_script.script_id);
    var old_bps = old_script.breakpoints;
    for (line_nr in old_bps)
    {
      if (old_bps[line_nr])
      {
        line_nr = parseInt(line_nr);
        this.remove_breakpoint(old_script.script_id, line_nr);
        this.add_breakpoint(new_script.script_id, line_nr, old_bps[line_nr]);
      }
    }
  };

  this.script_has_breakpoint_on_line = function(script_id, line_nr)
  {
    var script = window.runtimes.getScript(script_id);
    return Boolean(script && script.breakpoints[line_nr]);
  };

  this.get_breakpoint_on_script_line = function(script_id, line_nr)
  {
    var bp_id = this._get_bp_id_with_script_id_and_line_nr(script_id, line_nr);
    return bp_id ? this.get_breakpoint_with_id(bp_id): null;
  };

  this.set_condition = function(condition, bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    if (bp)
    {
      var current_condition = bp.condition;
      bp.condition = condition;
      if (condition && !current_condition)
      {
        this._update_bp_state(bp, BP_DELTA_CONDITION);
      }
      else if(!condition && current_condition)
      {
        this._update_bp_state(bp, -BP_DELTA_CONDITION);
      }
    }
  };

  this.get_condition = function(bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    return bp && bp.condition || "";
  };

  /* instantiation */

  this._init();

};

cls.Breakpoints.get_instance = function()
{
  return this.instance || new cls.Breakpoints();
};
<commit_msg>Fix for DFL-1885: Disabled breakpoints disappear from Breakpoints panel.<commit_after>window.cls || (window.cls = {});

/**
  * @constructor
  */
cls.Breakpoint = function(id, script_id, line_nr, event_type, active_rt_ids)
{
  this.id = id;
  this.script_id = script_id || "";
  this.line_nr = line_nr || 0;
  this.event_type = event_type || "";
  this.type = this.script_id ? "source" : "event";
  this.is_enabled = true;
  this.condition = "";
  this.update_is_active(active_rt_ids);
};

cls.Breakpoint.prototype = new function()
{
  this._is_active = true;

  this.__defineGetter__('is_active', function()
  {
    return this._is_active;
  });

  this.__defineSetter__('is_active', function(){});

  this.update_is_active = function(active_rt_ids)
  {
    if (this.script_id)
    {
      var rt_id = window.runtimes.getScriptsRuntimeId(this.script_id);
      this._is_active = active_rt_ids.indexOf(rt_id) != -1;
    }
  }
}

/**
  * @constructor
  */
cls.Breakpoints = function()
{

  if (cls.Breakpoints.instance)
  {
    return cls.Breakpoints.instance;
  }

  cls.Breakpoints.instance = this;

  /* interface */

  this.get_breakpoints = function(){};
  this.get_active_breakpoints = function(){};
  this.get_breakpoint_with_id = function(bp_id){};
  this.script_has_breakpoint_on_line = function(script_id, line_nr){};
  this.get_breakpoint_on_script_line = function(script_id, line_nr){};
  // bp_id optional
  this.add_breakpoint = function(script_id, line_nr, bp_id){};
  // removes the breakpoint in the host
  // disables the breakpoint in the client
  this.remove_breakpoint = function(script_id, line_nr){};
  this.delete_breakpoint = function(bp_id){};
  this.add_event_breakpoint = function(event_name){};
  this.remove_event_breakpoint = function(bp_id){};
  this.copy_breakpoints = function(new_script, old_script){};
  this.set_condition = function(condition, bp_id){};
  this.get_condition = function(bp_id){};

  /* constants */

  const
  BP_NONE = cls.NewScript.BP_NONE,
  BP_DISABLED = cls.NewScript.BP_DISABLED,
  BP_ENABLED = cls.NewScript.BP_ENABLED,
  BP_DELTA_ENABLE = BP_ENABLED - BP_DISABLED,
  BP_DELTA_CONDITION = cls.NewScript.BP_ENABLED_CONDITION - BP_ENABLED;

  /* private */

  this._get_bp_id = (function()
  {
    var count = 1;
    return function() {return count++;}
  })();

  this._add_bp = function(bp_id, script_id, line_nr, event_type)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    if (bp)
    {
      bp.is_enabled = true;
    }
    else
    {
      this._bps.push(new this._bp_class(bp_id,
                                        script_id,
                                        line_nr,
                                        event_type,
                                        this._active_rt_ids));
    }
    window.messages.post("breakpoint-updated",
                         {id: bp_id, script_id: script_id});
  };

  this._remove_bp = function(bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    if (bp)
    {
      bp.is_enabled = false;
      window.messages.post("breakpoint-updated",
                           {id: bp.id, script_id: bp.script_id});
    }
  };

  this._get_bp_id_with_script_id_and_line_nr = function(script_id, line_nr)
  {
    for (var i = 0, bp; bp = this._bps[i]; i++)
    {
      if (bp.script_id == script_id && bp.line_nr == line_nr)
      {
        return bp.id;
      }
    }
    return 0;
  };

  this._update_bp_state = function(bp, delta, absolute)
  {
    var script = bp && window.runtimes.getScript(bp.script_id);
    if (script)
    {
      if (delta)
      {
        script.breakpoint_states[bp.line_nr] += delta;
      }
      else
      {
        var state = script.breakpoint_states[bp.line_nr] || 0;
        var pointer_state = state % 3;
        script.breakpoint_states[bp.line_nr] = absolute;
        script.breakpoint_states[bp.line_nr] += pointer_state;
      }
    }
    window.messages.post('breakpoint-updated',
                         {id: bp.id,
                          script_id: bp.script_id,
                          event_type: bp.event_type});
  };

  this._get_bp_id_with_event_name = function(event_name)
  {
    for (var i = 0, bp; bp = this._bps[i]; i++)
    {
      if (bp.event_type == event_name)
      {
        return bp.id;
      }
    }
    return 0;
  };

  this._replace_script_id = function(new_script_id, old_script_id)
  {
    for (var i = 0, bp; bp = this._bps[i]; i++)
    {
      if (bp.script_id == old_script_id)
      {
        bp.script_id = new_script_id;
        bp.update_is_active(this._active_rt_ids);
      }
    }
  };

  this._get_bp_index_with_bp_id = function(bp_id)
  {
    for (var i = 0; i < this._bps.length && this._bps[i].id != bp_id; i++);
    return i;
  };

  this._on_active_tab = function(msg)
  {
    this._active_rt_ids = msg.activeTab;
    this._bps.forEach(function(bp)
    {
      bp.update_is_active(this._active_rt_ids);
    }, this);
    window.views.breakpoints.update();
  };

  this._init = function()
  {
    this._bps = [];
    this._bp_class = window.cls.Breakpoint;
    this._esdb = window.services['ecmascript-debugger'];
    this._active_rt_ids = [];
    window.messages.addListener('active-tab', this._on_active_tab.bind(this));
  };

  /* implementation */

  this.get_breakpoints = function()
  {
    return this._bps;
  };

  this.get_active_breakpoints = function()
  {
    return this._bps.filter(function(bp)
    {
      return bp.is_active;
    });
  };

  this.get_breakpoint_with_id = function(bp_id)
  {
    return this._bps[this._get_bp_index_with_bp_id(bp_id)];
  };

  this.add_breakpoint = function(script_id, line_nr, bp_id)
  {
    var script = window.runtimes.getScript(script_id);
    if (script)
    {
      bp_id = bp_id ||
              // if a breakpoint was set on the same script and line before
              this._get_bp_id_with_script_id_and_line_nr(script_id, line_nr) ||
              this._get_bp_id();
      script.breakpoints[line_nr] = bp_id;
      var state = script.breakpoint_states[line_nr] || 0;
      var pointer_state = state % 3;
      state -= pointer_state;
      if (!state)
      {
        script.breakpoint_states[line_nr] = BP_DISABLED;
      }
      if (script.breakpoint_states[line_nr] < BP_ENABLED)
      {
        script.breakpoint_states[line_nr] += BP_DELTA_ENABLE;
      }
      script.breakpoint_states[line_nr] += pointer_state;
      // message signature has changes with version 6.0
      // AddBreakpoint means always to a source line
      // for events it's now AddEventBreakpoint
      if (this._esdb.major_version > 5)
      {
        this._esdb.requestAddBreakpoint(0, [bp_id, script_id, line_nr]);
      }
      else
      {
        this._esdb.requestAddBreakpoint(0, [bp_id, "line", script_id, line_nr]);
      }
      this._add_bp(bp_id, script_id, line_nr);
    }
  };

  this.remove_breakpoint = function(script_id, line_nr)
  {
    var script = window.runtimes.getScript(script_id);
    if (script)
    {
      var bp_id = script.breakpoints[line_nr];
      this._esdb.requestRemoveBreakpoint(0, [bp_id]);
      script.breakpoints[line_nr] = 0;
      var state = script.breakpoint_states[line_nr] || 0;
      var pointer_state = state % 3;
      state -= pointer_state;
      if (state >= BP_ENABLED)
      {
        script.breakpoint_states[line_nr] = state - BP_DELTA_ENABLE;
      }
      script.breakpoint_states[line_nr] += pointer_state;
      this._remove_bp(bp_id);
      return bp_id;
    }
  };

  this.delete_breakpoint = function(bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    this._bps.splice(this._get_bp_index_with_bp_id(bp_id), 1);
    this._update_bp_state(bp, 0, BP_NONE);
  };

  this.add_event_breakpoint = function(event_name)
  {
    var bp_id = this._get_bp_id_with_event_name(event_name) ||
                this._get_bp_id();
    this._esdb.requestAddEventBreakpoint(0, [bp_id, event_name]);
    this._add_bp(bp_id, null, null, event_name);
    return bp_id;
  };

  this.remove_event_breakpoint = function(bp_id)
  {
    this._esdb.requestRemoveBreakpoint(0, [bp_id]);
    this._remove_bp(bp_id);
  };

  this.copy_breakpoints = function(new_script, old_script)
  {
    new_script.breakpoint_states = old_script.breakpoint_states;
    this._replace_script_id(new_script.script_id, old_script.script_id);
    var old_bps = old_script.breakpoints;
    for (line_nr in old_bps)
    {
      if (old_bps[line_nr])
      {
        line_nr = parseInt(line_nr);
        this.remove_breakpoint(old_script.script_id, line_nr);
        this.add_breakpoint(new_script.script_id, line_nr, old_bps[line_nr]);
      }
    }
    window.views.breakpoints.update();
  };

  this.script_has_breakpoint_on_line = function(script_id, line_nr)
  {
    var script = window.runtimes.getScript(script_id);
    return Boolean(script && script.breakpoints[line_nr]);
  };

  this.get_breakpoint_on_script_line = function(script_id, line_nr)
  {
    var bp_id = this._get_bp_id_with_script_id_and_line_nr(script_id, line_nr);
    return bp_id ? this.get_breakpoint_with_id(bp_id): null;
  };

  this.set_condition = function(condition, bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    if (bp)
    {
      var current_condition = bp.condition;
      bp.condition = condition;
      if (condition && !current_condition)
      {
        this._update_bp_state(bp, BP_DELTA_CONDITION);
      }
      else if(!condition && current_condition)
      {
        this._update_bp_state(bp, -BP_DELTA_CONDITION);
      }
    }
  };

  this.get_condition = function(bp_id)
  {
    var bp = this.get_breakpoint_with_id(bp_id);
    return bp && bp.condition || "";
  };

  /* instantiation */

  this._init();

};

cls.Breakpoints.get_instance = function()
{
  return this.instance || new cls.Breakpoints();
};
<|endoftext|>


<commit_before>import R from 'ramda';
import realMakeCard from '../makeCard/makeCard';

const ankifySenseData = R.curry((makeCard, { headword, pronunciation }, senseData) => {
	const { definition, situation, synonym, antonym, examples, exampleGroups, subsenses } = senseData;

	const commonData = {
		definition,
		situation,
		synonym,
		antonym,
		pronunciation
	};

	const ankifyExample = (form = headword) => example =>
		makeCard({ example, form, ...commonData})

	const ankifyExampleGroup = exampleGroup => {
		const { form, examples: exampleGroupExamples } = exampleGroup;
		const cards = exampleGroupExamples.map(ankifyExample(form));
		return cards;
	}

	const cardsFromExamples = R.map(
		ankifyExample()
	)(examples);

	const cardsFromExampleGroups = R.map(
		ankifyExampleGroup
	)(exampleGroups);

	const cardsFromSubsenses = R.map(
		ankifySenseData(makeCard, { headword, pronunciation })
	)(subsenses);

	const cardFromDefinition =
		R.all(R.isEmpty, [examples, exampleGroups, subsenses]) && definition
			? makeCard({ form: headword, ...commonData })
			: '';

	const cards = R.pipe(
		R.flatten,
		R.reject(R.isEmpty),
		R.join('\n')
	)([
		cardsFromExamples,
		cardsFromExampleGroups,
		cardsFromSubsenses,
		cardFromDefinition
	]);

	return cards;
});

export {ankifySenseData};
export default ankifySenseData(realMakeCard);
<commit_msg>refactor: add comments to ankifySenseData<commit_after>import R from 'ramda';
import realMakeCard from '../makeCard/makeCard';

const ankifySenseData = R.curry((makeCard, { headword, pronunciation }, senseData) => {

	// data

	const { definition, situation, synonym, antonym, examples, exampleGroups, subsenses } = senseData;

	const commonData = {
		definition,
		situation,
		synonym,
		antonym,
		pronunciation
	};

	// ankify... functions

	const ankifyExample = (form = headword) => example =>
		makeCard({ example, form, ...commonData})

	const ankifyExampleGroup = exampleGroup => {
		const { form, examples: exampleGroupExamples } = exampleGroup;
		const cards = exampleGroupExamples.map(ankifyExample(form));
		return cards;
	}

	// different card types

	const cardsFromExamples = R.map(
		ankifyExample()
	)(examples);

	const cardsFromExampleGroups = R.map(
		ankifyExampleGroup
	)(exampleGroups);

	const cardsFromSubsenses = R.map(
		ankifySenseData(makeCard, { headword, pronunciation })
	)(subsenses);

	const cardFromDefinition =
		R.all(R.isEmpty, [examples, exampleGroups, subsenses]) && definition
			? makeCard({ form: headword, ...commonData })
			: '';

	// all cards

	const cards = R.pipe(
		R.flatten,
		R.reject(R.isEmpty),
		R.join('\n')
	)([
		cardsFromExamples,
		cardsFromExampleGroups,
		cardsFromSubsenses,
		cardFromDefinition
	]);

	return cards;
});

export {ankifySenseData};
export default ankifySenseData(realMakeCard);
<|endoftext|>


<commit_before>var fs = require('fs')
	, async = require('async')
	, slave = require('../slave-driver')
	, npm = require('npm')
	, path = require('path')

	// iffy means it might not work on all platforms, but works on most. So sad.
	// If it doesn't say iffy, it *should* be passing.
	// TODO test certain modules and make sure they FAIL.
	, modules = [
		// expresso
			'diff' // iffy
		, 'dnode-protocol' // iffy

		// vows
		, 'date-utils'

		// tap
		, 'semver'

		// node *.js
		, 'Journaling-Hash'
		, 'abbrev'
		, 'argsparser'

		// nodeunit
		, 'json-streamify' // iffy

		// make test
		, 'jsontool'

		// whiskey
		// none really that don't do silly things in their test command.

		// jasmine
		, 'jquery-tmpl-jst'
		]

	// for faster testing
	// , modules = [ 'date-utils' ]
	, tasks = [];

require('colors');

console.log('Running with NodeJS: ' + process.version);
slave.ready(function(run) {
	modules.forEach(function(module) {
		tasks.push(function(cb) {
			var s = run(module, { reportResults: false })
				, out = '', err = '';

			s.on('out', function(data) {
				out += data;
			});

			s.on('err', function(err, data) {
				err += data;
			});

			s.on('complete', function(code, sig) {
				fs.writeFile('./logs/' + module + '.out.log', out);
				fs.writeFile('./logs/' + module + '.err.log', err);
				// console.log('[test.js:out]:\n%s'.green, out);
				// console.log('[test.js:err]:\n%s'.red, err);
				// console.log('test completed with code:', code, 'sig:', sig);
				cb(null, { name: module, passed: code, err: err, out: out });
			});
		});
	});

	async.parallel(tasks, function(err, results) {
		if (err) throw err;

		testViaNpm(printResults);
		function printResults(err) {
			console.log();
			console.log('===pants runner===');
			var win = true;
			results.forEach(function(r) {
				// only print if it failed!
				if (r.passed === false) {
					console.log(r.name, 'passed?', r.passed);
					console.log('stdout:', r.out);
					console.log('stderr:', r.err);
					win = false;
				}
			});
			if (win) {
				console.log('Success! All modules passed. Full list:');
				console.log(modules.join(', '));
			}
			process.exit(0);
		}
	});
});

function testViaNpm(callback) {
	var npmConfig = {
			loglevel: 'silent',
			cwd: path.join(__dirname, '..', 'test_modules', 'node_modules')
		}
	, cliTasks = [];

	console.log(npmConfig.cwd);

	npm.load(npmConfig, function (err) {
		if (err) throw err;

		modules.forEach(function(module) {
			cliTasks.push(function(cb) {
				npm.commands.test([path.join(npmConfig.cwd, module)], function(err, d) {
					cb(null, { err: err, data: d, name: module });
				});
			});
		});

		async.parallel(cliTasks, function(err, results) {
			if (err) throw err;
			console.log();
			console.log('===npm test===');
			results.forEach(function(r) {
				console.log('passed?', !r.err, ':', r.name);
				if (r.err) {
					console.log(r.err);
				}
			});
			callback(null, results);
		});
	});
};
<commit_msg>test: make self-test an executable<commit_after>#!/usr/bin/env node
var fs = require('fs')
	, async = require('async')
	, slave = require('../slave-driver')
	, npm = require('npm')
	, path = require('path')

	// iffy means it might not work on all platforms, but works on most. So sad.
	// If it doesn't say iffy, it *should* be passing.
	// TODO test certain modules and make sure they FAIL.
	, modules = [
		// expresso
			'diff' // iffy
		, 'dnode-protocol' // iffy

		// vows
		, 'date-utils'

		// tap
		, 'semver'

		// node *.js
		, 'Journaling-Hash'
		, 'abbrev'
		, 'argsparser'

		// nodeunit
		, 'json-streamify' // iffy

		// make test
		, 'jsontool'

		// whiskey
		// none really that don't do silly things in their test command.

		// jasmine
		, 'jquery-tmpl-jst'
		]

	// for faster testing
	// , modules = [ 'date-utils' ]
	, tasks = [];

require('colors');

console.log('Running with NodeJS: ' + process.version);
slave.ready(function(run) {
	modules.forEach(function(module) {
		tasks.push(function(cb) {
			var s = run(module, { reportResults: false })
				, out = '', err = '';

			s.on('out', function(data) {
				out += data;
			});

			s.on('err', function(err, data) {
				err += data;
			});

			s.on('complete', function(code, sig) {
				fs.writeFile('./logs/' + module + '.out.log', out);
				fs.writeFile('./logs/' + module + '.err.log', err);
				// console.log('[test.js:out]:\n%s'.green, out);
				// console.log('[test.js:err]:\n%s'.red, err);
				// console.log('test completed with code:', code, 'sig:', sig);
				cb(null, { name: module, passed: code, err: err, out: out });
			});
		});
	});

	async.parallel(tasks, function(err, results) {
		if (err) throw err;

		testViaNpm(printResults);
		function printResults(err) {
			console.log();
			console.log('===pants runner===');
			var win = true;
			results.forEach(function(r) {
				// only print if it failed!
				if (r.passed === false) {
					console.log(r.name, 'passed?', r.passed);
					console.log('stdout:', r.out);
					console.log('stderr:', r.err);
					win = false;
				}
			});
			if (win) {
				console.log('Success! All modules passed. Full list:');
				console.log(modules.join(', '));
			}
			process.exit(0);
		}
	});
});

function testViaNpm(callback) {
	var npmConfig = {
			loglevel: 'silent',
			cwd: path.join(__dirname, '..', 'test_modules', 'node_modules')
		}
	, cliTasks = [];

	console.log(npmConfig.cwd);

	npm.load(npmConfig, function (err) {
		if (err) throw err;

		modules.forEach(function(module) {
			cliTasks.push(function(cb) {
				npm.commands.test([path.join(npmConfig.cwd, module)], function(err, d) {
					cb(null, { err: err, data: d, name: module });
				});
			});
		});

		async.parallel(cliTasks, function(err, results) {
			if (err) throw err;
			console.log();
			console.log('===npm test===');
			results.forEach(function(r) {
				console.log('passed?', !r.err, ':', r.name);
				if (r.err) {
					console.log(r.err);
				}
			});
			callback(null, results);
		});
	});
};
<|endoftext|>


<commit_before>angular.module('pie')
.directive('piemarkdown', function() {
	var converter = new Showdown.converter();

	return {
		restrict: 'E',
		scope: { content: '=' },
		link: function(scope, element, attrs) {
			scope.$watch('content', function(newVal) {
				element.html(converter.makeHtml(newVal));
			});
		}
	};
});<commit_msg>Fix markdown behavior when content doesn't exist yet<commit_after>angular.module('pie')
.directive('piemarkdown', function() {
	var converter = new Showdown.converter();

	return {
		restrict: 'E',
		scope: { content: '=' },
		link: function(scope, element, attrs) {
			scope.$watch('content', function(newVal) {
				if (newVal) {
					element.html(converter.makeHtml(newVal));
				}
			});
		}
	};
});<|endoftext|>


<commit_before>/*
 * The main application module
 */
define([
        'jquery',
        'underscore',
        'backbone',
        'handlebars',
        'synapse',
        'synapse/hooks/object',
        'synapse/hooks/jquery',
        'synapse/hooks/backbone-model',
        'twitter/collections/tweets',
        'twitter/models/searcher',
        'twitter/views/tweetsview',
        'twitter/views/searcherview',
        'timer/models/timer',
        'timer/views/timerview'
    ], function($, _, Backbone, Handlebars, Synapse, ObjectHook, jQueryHook, BackboneModelHook,
        Tweets, Searcher, TweetsView, SearcherView, Timer, TimerView) {

    // For data-binding support between Backbone and jQuery objects
    Synapse.addHooks(jQueryHook, BackboneModelHook, ObjectHook);

    // Application namespace object to be returned
    var App = {};

    // Tweets for this application
    App.Tweets = new Tweets();

    // Model for performing searches
    App.Searcher = new Searcher();

    // Timer for auto-refreshing support
    App.Timer = new Timer();

    // Main application view
    var AppView = Backbone.View.extend({

        // Use existing DOM element instead of creating a new one
        el: $('#twitter-app'),

        initialize: function() {
            // Make sure that 'this' is pointed to this AppView instance for the following functions
            _.bindAll(this, 'displayResults', 'showLoader', 'updateTimer', 'toggleTimer', 'clearResults');

            // Setup handlers for events of interest
            App.Searcher.bind('ajax:before', this.showLoader);
            App.Searcher.bind('ajax:after', this.clearResults);
            App.Searcher.bind('change:query', this.updateTimer);
            App.Searcher.bind('change:results', this.displayResults);
            App.Timer.bind('change:started', this.toggleTimer);
            App.Timer.bind('alarm', App.Searcher.refresh);

            // Timer view for the countdown between refreshes
            this.timerView = new TimerView({
                // Use existing DOM element
                el: this.$('.timer'),
                model: App.Timer
            });

            // Main tweets view for results
            this.tweetsView = new TweetsView({
                // Use existing DOM element
                el: this.$('.tweets'),
                collection: App.Tweets
            });

            // Input box for the live search
            this.searcherView = new SearcherView({
                // Use existing DOM element
                el: this.$('#search-form'),
                model: App.Searcher
            });

            // Data-binding between the query <span> in the countdown and the Seacher's query attribute
            Synapse(this.timerView.$('span[name=query]')).observe(App.Searcher);
        },

        showLoader: function() {
            $(this.tweetsView.el).addClass('loading');
        },

        clearResults: function() {
            // Remove previous tweets
            App.Tweets.reset([]);
        },

        updateTimer: function() {
            // Reset timer
            App.Timer.resetTime();

            // If query is empty/null then stop timer, else start timer
            if (App.Searcher.get('query'))
                App.Timer.start();
            else
                App.Timer.stop();
        },

        // debounce for some delay before displaying results
        displayResults: _.debounce(function(searcher, data) {
            // Refresh tweets collection with source data
            var results = data ? data.results : [];
            // Add to beginning of collection
            App.Tweets.add(results, { at: 0 });
            // Remove loading indicator
            $(this.tweetsView.el).removeClass('loading');
        }, 300),

        // When timer starts or stops, we want to show or hide the timer view respectively
        toggleTimer: function() {
            if (App.Timer.get('started'))
                $(this.timerView.el).show();
            else
                $(this.timerView.el).hide();
        }
    });

    // Create AppView and return the App namespace
    App.View = new AppView();
    return App;
});
<commit_msg>clear results handler right before display results handler<commit_after>/*
 * The main application module
 */
define([
        'jquery',
        'underscore',
        'backbone',
        'handlebars',
        'synapse',
        'synapse/hooks/object',
        'synapse/hooks/jquery',
        'synapse/hooks/backbone-model',
        'twitter/collections/tweets',
        'twitter/models/searcher',
        'twitter/views/tweetsview',
        'twitter/views/searcherview',
        'timer/models/timer',
        'timer/views/timerview'
    ], function($, _, Backbone, Handlebars, Synapse, ObjectHook, jQueryHook, BackboneModelHook,
        Tweets, Searcher, TweetsView, SearcherView, Timer, TimerView) {

    // For data-binding support between Backbone and jQuery objects
    Synapse.addHooks(jQueryHook, BackboneModelHook, ObjectHook);

    // Application namespace object to be returned
    var App = {};

    // Tweets for this application
    App.Tweets = new Tweets();

    // Model for performing searches
    App.Searcher = new Searcher();

    // Timer for auto-refreshing support
    App.Timer = new Timer();

    // Main application view
    var AppView = Backbone.View.extend({

        // Use existing DOM element instead of creating a new one
        el: $('#twitter-app'),

        initialize: function() {
            // Make sure that 'this' is pointed to this AppView instance for the following functions
            _.bindAll(this, 'displayResults', 'showLoader', 'updateTimer', 'toggleTimer', 'clearResults');

            // Setup handlers for events of interest
            App.Searcher.bind('ajax:before', this.showLoader);
            App.Searcher.bind('change:query', this.updateTimer);
            App.Searcher.bind('change:results', this.clearResults);
            App.Searcher.bind('change:results', this.displayResults);
            App.Timer.bind('change:started', this.toggleTimer);
            App.Timer.bind('alarm', App.Searcher.refresh);

            // Timer view for the countdown between refreshes
            this.timerView = new TimerView({
                // Use existing DOM element
                el: this.$('.timer'),
                model: App.Timer
            });

            // Main tweets view for results
            this.tweetsView = new TweetsView({
                // Use existing DOM element
                el: this.$('.tweets'),
                collection: App.Tweets
            });

            // Input box for the live search
            this.searcherView = new SearcherView({
                // Use existing DOM element
                el: this.$('#search-form'),
                model: App.Searcher
            });

            // Data-binding between the query <span> in the countdown and the Seacher's query attribute
            Synapse(this.timerView.$('span[name=query]')).observe(App.Searcher);
        },

        showLoader: function() {
            $(this.tweetsView.el).addClass('loading');
        },

        clearResults: function() {
            // Remove previous tweets
            App.Tweets.reset([]);
        },

        updateTimer: function() {
            // Reset timer
            App.Timer.resetTime();

            // If query is empty/null then stop timer, else start timer
            if (App.Searcher.get('query')) {
                App.Timer.start();
            } else {
                App.Timer.stop();
            }
        },

        // debounce for some delay before displaying results
        displayResults: _.debounce(function(searcher, data) {
            // Refresh tweets collection with source data
            var results = data ? data.results : [];
            // Add to beginning of collection
            App.Tweets.add(results, { at: 0 });
            // Remove loading indicator
            $(this.tweetsView.el).removeClass('loading');
        }, 300),

        // When timer starts or stops, we want to show or hide the timer view respectively
        toggleTimer: function() {
            if (App.Timer.get('started'))
                $(this.timerView.el).show();
            else
                $(this.timerView.el).hide();
        }
    });

    // Create AppView and return the App namespace
    App.View = new AppView();
    return App;
});
<|endoftext|>


<commit_before>var _ = require('lodash');

var utils = require('../utils');
var resources = require('../dummy/resources');

var DummyResource = resources.DummyResource;
var DummyResourceError = resources.DummyResourceError;

var DummyKvResource = DummyResource.extend(function(self, name, opts) {
    /**class:DummyKvResource(name)

    Handles api requests to the kv resource from :class:`DummyApi`.

    :param string name:
        The name of the resource. Should match the name given in api requests.
    */
    DummyResource.call(self, name);

    /**attribute:DummyKvResource.store
    An object mapping all the keys in the store to their corresponding values.
    */
    self.store = {};

    self.incr = function(key, amount) {
        /**:DummyKvResource.incr(key[, amount])

        Increment the value of an integer key. The current value of the key
        must be an integer. If the key does not exist, it is set to zero.
        Returns the result.

        :param string key:
            The key corresponding to the value to increment
        :param integer amount:
            The amount to increment by. Defaults to 1.
        */
        amount = utils.exists(amount)
            ? amount
            : 1;

        if (!utils.is_integer(amount)) {
            throw new DummyResourceError([
                "Non-integer increment amount given for key",
                "'" + key + "': " + amount
            ].join(' '));
        }

        var value = self.store[key];
        if (!utils.is_integer(value)) {
            throw new DummyResourceError([
                "Cannot increment non-integer value for key",
                "'" + key + "': " + value
            ].join(' '));
        }

        value += amount;
        self.store[key] = value;
        return value;
    };

    self.handlers.get = function(cmd) {
        var value = self.store[cmd.key];

        if (_.isUndefined(value)) {
            value = null;
        }

        return {
            success: true,
            value: value
        };
    };

    self.handlers.set = function(cmd) {
        self.store[cmd.key] = cmd.value;
        return {success: true};
    };

    self.handlers.incr = function(cmd) {
        return {
            success: true,
            value: self.incr(cmd.key, cmd.amount)
        };
    };

    self.handlers.delete = function(cmd) {
        var existed = cmd.key in self.store;
        delete self.store[cmd.key];
        return {
            success: true,
            existed: existed
        };
    };
});

this.DummyKvResource = DummyKvResource;
<commit_msg>Allow DummyKvStore to be initialised with data<commit_after>var _ = require('lodash');

var utils = require('../utils');
var resources = require('../dummy/resources');

var DummyResource = resources.DummyResource;
var DummyResourceError = resources.DummyResourceError;

var DummyKvResource = DummyResource.extend(function(self, name, store) {
    /**class:DummyKvResource(name[, store])

    Handles api requests to the kv resource from :class:`DummyApi`.

    :param string name:
        The name of the resource. Should match the name given in api requests.
    :param object store:
        The data to initialise the store with.
    */
    DummyResource.call(self, name);

    /**attribute:DummyKvResource.store
    An object mapping all the keys in the store to their corresponding values.
    */
    self.store = store || {};

    self.incr = function(key, amount) {
        /**:DummyKvResource.incr(key[, amount])

        Increment the value of an integer key. The current value of the key
        must be an integer. If the key does not exist, it is set to zero.
        Returns the result.

        :param string key:
            The key corresponding to the value to increment
        :param integer amount:
            The amount to increment by. Defaults to 1.
        */
        amount = utils.exists(amount)
            ? amount
            : 1;

        if (!utils.is_integer(amount)) {
            throw new DummyResourceError([
                "Non-integer increment amount given for key",
                "'" + key + "': " + amount
            ].join(' '));
        }

        var value = self.store[key];
        if (!utils.is_integer(value)) {
            throw new DummyResourceError([
                "Cannot increment non-integer value for key",
                "'" + key + "': " + value
            ].join(' '));
        }

        value += amount;
        self.store[key] = value;
        return value;
    };

    self.handlers.get = function(cmd) {
        var value = self.store[cmd.key];

        if (_.isUndefined(value)) {
            value = null;
        }

        return {
            success: true,
            value: value
        };
    };

    self.handlers.set = function(cmd) {
        self.store[cmd.key] = cmd.value;
        return {success: true};
    };

    self.handlers.incr = function(cmd) {
        return {
            success: true,
            value: self.incr(cmd.key, cmd.amount)
        };
    };

    self.handlers.delete = function(cmd) {
        var existed = cmd.key in self.store;
        delete self.store[cmd.key];
        return {
            success: true,
            existed: existed
        };
    };
});

this.DummyKvResource = DummyKvResource;
<|endoftext|>


<commit_before>var Radio = require('../../radio');

var program = require('commander');

var fs = require('fs');
var fsextra = require('fs-extra');
var moment = require('moment');
var path = require('path');

var dot = require('dot');

/* WEB APP REQUIREMENTS */
// Expressjs for devices to register for push notifications
var express = require('express');
var request = require('request');
var sqlite3 = require('sqlite3').verbose();
/*----*/

var Events = require('../../../events');
var Utils = require('../../../utils');
var RSSFeedGenerator = require('../../rss-feed-generator');

const { URL } = require('url');

var lm = require('../../lineup-manager');

var Raa1 = function(program) {
    this.configFilePath = path.resolve(program.args[0]);
    this.cwd = path.resolve(path.dirname(program.args[0]) + '/..');
    this.config = null;

    this.events = null;

    this.deploymentMode = program.args[1];
    this.options = program;

    this.dataProvider = {};

    Radio.call(this, "Raa1", "Radio Auto-asaad");
}
Utils.inheritsFrom(Raa1, Radio);

Raa1.prototype.initialize = function() {
    this.config = JSON.parse(fs.readFileSync(this.configFilePath, 'utf8'));

    if (this.config == null) {
        console.log("FATAL ERROR: Error reading config file");
        process.exit(1);
    }

    this.registerWebApp();

    this.events = new Events(this.config.Events);

    this.lineupManager = lm.build(this.deploymentMode, this.cwd, this);
    this.lineupManager.init(this.options);
}

Raa1.prototype.getRadioConfig = function() {
    this.config = JSON.parse(fs.readFileSync(this.configFilePath, 'utf8'));

    if (this.config == null) {
        console.log("FATAL ERROR: Error reading config file");
        process.exit(1);
    }

    return this.config.Radio;
}

Raa1.prototype.reset = function(currentDate, callback_fn) {
    var self = this;
    var d = moment(this.lineupManager.options.currentDayMoment);

    this.dataProvider = {};

    var onAllEventsCollection = function() {
        callback_fn();
    }

    for (var i = 0; i <= this.lineupManager.options.futureLineupsCount; i++) {
        this.events.readEvent(d.toDate(), function(d, eventsDict) {
            dateString = moment(d).format("YYYYMMDD");
            self.dataProvider[dateString] = {};
            self.dataProvider[dateString].fajrTime = eventsDict[self.events.EventType.FAJR];
            self.dataProvider[dateString].dhuhrTime = eventsDict[self.events.EventType.DHUHR];
            self.dataProvider[dateString].maghribTime = eventsDict[self.events.EventType.MAGHRIB];

            if (Object.keys(self.dataProvider).length > self.lineupManager.options.futureLineupsCount) {
                onAllEventsCollection();
            }
        });
        d.add(1, 'days');
    }
}

Raa1.prototype.calculateProgramStartTime = function(targetDateMoment, id) {
    if (id == 'اذان صبح') {
        return this.dataProvider[targetDateMoment.format("YYYYMMDD")].fajrTime;
    } else if (id == 'اذان ظهر') {
        return this.dataProvider[targetDateMoment.format("YYYYMMDD")].dhuhrTime;
    } else if (id == 'اذان مغرب') {
        return this.dataProvider[targetDateMoment.format("YYYYMMDD")].maghribTime;
    }
}

// For raa1, we should generate the basic lineup HTML
Radio.prototype.onLineupPlanned = function(targetDateMoment, lineup) {
    // TODO
}

// For raa1, we should generate the lineup HTML
Radio.prototype.onLineupCompiled = function(targetDateMoment, compiledLineup) {

    var lineupTemplateText = fs.readFileSync('lineup-view.jst', 'utf8');
    dot.templateSettings.strip = false;

    var lineupTemplateFn = dot.template(lineupTemplateText);

    var data = {};
    data.array = [];

    var feedGen = this.createFeedGenerator();

    for (var i = 0; i < compiledLineup.Programs.length; i++) {
        var entry = {};

        if (compiledLineup.Programs[i].Title) {
                    entry.title = compiledLineup.Programs[i].Title;
                } else {
                    entry.title = "بی نام";
                }
                entry.description = "";

        if (compiledLineup.Programs[i].PreShow) {
            entry.startTime = moment(compiledLineup.Programs[i].PreShow.Meta.TentativeStartTime).format('HH:mm');

            for (var j = 0; j < compiledLineup.Programs[i].PreShow.Clips.length; j++) {
                if (compiledLineup.Programs[i].PreShow.Clips[j].Description) {
                    entry.description += compiledLineup.Programs[i].PreShow.Clips[j].Description;
                    entry.description += "؛ ";
                }
            }
        } else {
            entry.startTime = moment(compiledLineup.Programs[i].Show.Meta.TentativeStartTime).format('HH:mm');
        }
        entry.endTime = moment(compiledLineup.Programs[i].Show.Meta.TentativeEndTime).format('HH:mm');

        for (var j = 0; j < compiledLineup.Programs[i].Show.Clips.length; j++) {
            if (compiledLineup.Programs[i].Show.Clips[j].Description && compiledLineup.Programs[i].Show.Clips[j].Description != "") {
                if (compiledLineup.Programs[i].Show.Clips[j].HasVOD) {
                    // reconstruct the relative path to the media from the absolute playback path
                    vodRelativeURI = compiledLineup.Programs[i].Show.Clips[j].Path.replace(this.config.Radio.Media.BaseDir,"");
                    vodUrl = new URL(vodRelativeURI, 'http://vod.raa.media/');
                    entry.description += '' + compiledLineup.Programs[i].Show.Clips[j].Description + "";

                    /* Now update the RSS xml feed (If the clip should be published) */
                    // An example of an item which should not be published (although having VOD),
                    // is the re-broadcast of a program.
                    if (compiledLineup.Programs[i].PublishPodcast) {
                        // Create a new feed gen item
                        rssFeedItem = this.createRSSItemTemplate();
                        rssFeedItem.title = compiledLineup.Programs[i].Title;
                        rssFeedItem.description = compiledLineup.Programs[i].Show.Clips[j].Description;
                        rssFeedItem.url = vodUrl.toString();
                        rssFeedItem.date = Date().toString();

                        rssFeedItem.enclosure = {};
                        var podcastEncodedUrl = 'https://api.raa.media/linkgenerator/podcast/' + Buffer.from(vodUrl.toString()).toString('base64');
                        // This is to convince podcast players that this is an audio file!! It will be ignored by base64 decoder later
                        podcastEncodedUrl = podcastEncodedUrl + ".mp3";
                        rssFeedItem.enclosure.url = podcastEncodedUrl;

                        itunesSubtitleElement = {};
                        itunesSubtitleElement["itunes:subtitle"] = compiledLineup.Programs[i].Show.Clips[j].Description;;
                        rssFeedItem.custom_elements.push(itunesSubtitleElement);

                        // Push the new item
                        feedGen.addItem(rssFeedItem);
                    }

                } else {
                    entry.description += compiledLineup.Programs[i].Show.Clips[j].Description;
                }

                if (j != compiledLineup.Programs[i].Show.Clips.length - 1) { // Except for the last item
                    entry.description += "؛ ";
                }
            }
        }

        entry.description = entry.description.trim();
        // Hack to remove extra semicolon if it is remained from before
        if (entry.description.charAt(entry.description.length - 1) == "؛") {
            entry.description = entry.description.slice(0, -1);
        }

        data.array.push(entry);
    }

    var resultText = lineupTemplateFn(data);
    // A little bit of hacking here to recreate the current date
    today = moment(compiledLineup.Programs[0].Show.StartTime).format('YYYY-MM-DD');
    fs.writeFileSync(this.cwd + "/web/lineup-" + today + ".html", resultText);
    fs.writeFileSync(this.cwd + "/web/lineup-" + today + ".json", JSON.stringify(data, null, 4));

    // Publish the RSS feed (but publish yesterday items not today's)
    feedGen.publishFeed(targetDateMoment, this.cwd + "/rss/rss.xml", true);
}

Radio.prototype.createRSSItemTemplate = function() {
    return {
        author: 'رادیو اتو-اسعد',
        custom_elements: [
          {'itunes:author': 'رادیو اتو-اسعد'},
          {'itunes:image': {
            _attr: {
              href: 'http://raa.media/img/raa-cover-itunes.png'
            }
          }}
        ]
    }
}

Radio.prototype.createFeedGenerator = function() {
    return new RSSFeedGenerator({
        title: 'رادیو اتو-اسعد',
        custom_namespaces: {
            'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'
        },
        description: 'پادکست‌های رادیو اتو-اسعد شامل برنامه‌هایی است که امکان پخش عمومی آن‌ها برای ما وجود داشته است.',
        feed_url: 'http://raa.media/rss.xml',
        site_url: 'http://raa.media',
        image_url: 'http://raa.media/img/raa-cover-itunes.png',
        copyright: '2017 Radio Auto-asaad',
        language: 'fa',
        pubDate: 'Aug 01, 2017 04:00:00 GMT',
        ttl: '60',
        custom_elements: [
          {'itunes:subtitle': 'پادکست‌های رادیو اتو-اسعد'},
          {'itunes:author': 'اتو-اسعد'},
          {'itunes:explicit': false},
          {'itunes:owner': [
            {'itunes:name': 'Radio Auto-asaad'},
            {'itunes:email': 'admin@raa.media'}
          ]},
          {'itunes:image': {
            _attr: {
              href: 'http://raa.media/img/raa-cover-itunes.png'
            }
          }},
          {'itunes:category': [
            {_attr: {
              text: 'Arts'
            }},
            {'itunes:category': {
              _attr: {
                text: 'Literature'
              }
            }}
          ]}
        ]
    });
}

Raa1.prototype.registerWebApp = function() {
    var self = this;

    self.iosDB = new sqlite3.Database(this.cwd + '/run/ios-device-list.db', sqlite3.OPEN_READWRITE, function(err) {
      if (err) {
        console.error('Error connecting to iOS database: ' + err.message);
      }
      console.log('Connected to the iOS database.');
    });

    self.fcmDB = new sqlite3.Database(this.cwd + '/run/fcm-device-list.db', sqlite3.OPEN_READWRITE, function(err) {
      if (err) {
        console.error('Error connecting to FCM database: ' + err.message);
      }
      console.log('Connected to the FCM database.');
    });

    self.iosDB.serialize(function() {
        self.iosDB.run("CREATE TABLE if not exists devices (deviceId TEXT PRIMARY_KEY, unique(deviceId))");
    });

    self.fcmDB.serialize(function() {
        self.fcmDB.run("CREATE TABLE if not exists devices (deviceId TEXT PRIMARY_KEY, unique(deviceId))");
    });

    self.webApp = express()
    self.webApp.get('/registerDevice/ios/:deviceId', function(req, res) {
        var deviceId = req.params['deviceId'];
        if (deviceId) {
            // Persist deviceId in our local database
            self.iosDB.serialize(function() {
                var stmt = self.iosDB.prepare("INSERT INTO devices VALUES (?)");
                stmt.run(deviceId, function(err) {
                    // Insertion can fail because of duplicate rows coming in. Ignore them
                    // TODO better exception handling
                });
            });
        }
        res.send("Success");
    });

    self.webApp.get('/registerDevice/fcm/:deviceId', function(req, res) {
        var deviceId = req.params['deviceId'];
        if (deviceId) {
            // Persist deviceId in our local database
            self.fcmDB.serialize(function() {
                var stmt = self.fcmDB.prepare("INSERT INTO devices VALUES (?)");
                stmt.run(deviceId, function(err) {
                    // Insertion can fail because of duplicate rows coming in. Ignore them
                    // TODO better exception handling
                });
            });
        }
        res.send("Success");
    });

    self.webApp.get('/linkgenerator/:medium/:urlEncoded', function(req, res) {
        var medium = req.params['medium'];
        var urlEncoded = req.params['urlEncoded'];
        var reqUrl = Buffer.from(urlEncoded, 'base64').toString('ascii');

        if (reqUrl) {
            var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
            var userAgent = req.headers['user-agent'];

            // Report to GA server
            var gaParams = {
                'v': '1',
                'tid': 'UA-103579661-1',
                'cid': '555',
                't': 'event',
                'ea': 'play',
            };
            gaParams.el = reqUrl;
            gaParams.uip = ip;
            gaParams.ua = userAgent;
            gaParams.ec = medium;
            gaParams.ca = medium;
            gaParams.cn = medium;
            gaParams.cm = medium;

            var requestOptions = {
                url: "https://www.google-analytics.com/collect",
                method: "POST",
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                form: gaParams
            }

            request.post(requestOptions,
                function(error, response, body) {
                    if (error) {
                        console.log("Error while sending GA request: " + error);
                    }
                });

            return res.redirect(reqUrl);
        }

        return res.status(400).send("The query url is missing or not valid");
    });

    self.webApp.listen(7799, function () {
        console.log('Raa device registration endpoint activated on port 7799');
    });
}

/* === Entry Point === */
program
    .version('1.0.0')
    .option('-v, --verbose', 'Detailed logs')
    .option('-t, --test', 'Test mode (no side effects)')
    .option('-n --no-scheduling', 'Persist all steps except scheduling (For advacned testing only)')
    .option('-d --target-date [date]', "Target date in YYYY-MM-DD format", moment)
    .parse(process.argv)

if (program.args.length < 2) {
    console.log("Usage: node raa1.js OPTIONS {config-file} {deployment-mode}");
    process.exit(1);
}

var raa1 = new Raa1(program);

raa1.initialize();
<commit_msg>encoded podcast url generator<commit_after>var Radio = require('../../radio');

var program = require('commander');

var fs = require('fs');
var fsextra = require('fs-extra');
var moment = require('moment');
var path = require('path');

var dot = require('dot');

/* WEB APP REQUIREMENTS */
// Expressjs for devices to register for push notifications
var express = require('express');
var request = require('request');
var sqlite3 = require('sqlite3').verbose();
/*----*/

var Events = require('../../../events');
var Utils = require('../../../utils');
var RSSFeedGenerator = require('../../rss-feed-generator');

const { URL } = require('url');

var lm = require('../../lineup-manager');

var Raa1 = function(program) {
    this.configFilePath = path.resolve(program.args[0]);
    this.cwd = path.resolve(path.dirname(program.args[0]) + '/..');
    this.config = null;

    this.events = null;

    this.deploymentMode = program.args[1];
    this.options = program;

    this.dataProvider = {};

    Radio.call(this, "Raa1", "Radio Auto-asaad");
}
Utils.inheritsFrom(Raa1, Radio);

Raa1.prototype.initialize = function() {
    this.config = JSON.parse(fs.readFileSync(this.configFilePath, 'utf8'));

    if (this.config == null) {
        console.log("FATAL ERROR: Error reading config file");
        process.exit(1);
    }

    this.registerWebApp();

    this.events = new Events(this.config.Events);

    this.lineupManager = lm.build(this.deploymentMode, this.cwd, this);
    this.lineupManager.init(this.options);
}

Raa1.prototype.getRadioConfig = function() {
    this.config = JSON.parse(fs.readFileSync(this.configFilePath, 'utf8'));

    if (this.config == null) {
        console.log("FATAL ERROR: Error reading config file");
        process.exit(1);
    }

    return this.config.Radio;
}

Raa1.prototype.reset = function(currentDate, callback_fn) {
    var self = this;
    var d = moment(this.lineupManager.options.currentDayMoment);

    this.dataProvider = {};

    var onAllEventsCollection = function() {
        callback_fn();
    }

    for (var i = 0; i <= this.lineupManager.options.futureLineupsCount; i++) {
        this.events.readEvent(d.toDate(), function(d, eventsDict) {
            dateString = moment(d).format("YYYYMMDD");
            self.dataProvider[dateString] = {};
            self.dataProvider[dateString].fajrTime = eventsDict[self.events.EventType.FAJR];
            self.dataProvider[dateString].dhuhrTime = eventsDict[self.events.EventType.DHUHR];
            self.dataProvider[dateString].maghribTime = eventsDict[self.events.EventType.MAGHRIB];

            if (Object.keys(self.dataProvider).length > self.lineupManager.options.futureLineupsCount) {
                onAllEventsCollection();
            }
        });
        d.add(1, 'days');
    }
}

Raa1.prototype.calculateProgramStartTime = function(targetDateMoment, id) {
    if (id == 'اذان صبح') {
        return this.dataProvider[targetDateMoment.format("YYYYMMDD")].fajrTime;
    } else if (id == 'اذان ظهر') {
        return this.dataProvider[targetDateMoment.format("YYYYMMDD")].dhuhrTime;
    } else if (id == 'اذان مغرب') {
        return this.dataProvider[targetDateMoment.format("YYYYMMDD")].maghribTime;
    }
}

// For raa1, we should generate the basic lineup HTML
Radio.prototype.onLineupPlanned = function(targetDateMoment, lineup) {
    // TODO
}

// For raa1, we should generate the lineup HTML
Radio.prototype.onLineupCompiled = function(targetDateMoment, compiledLineup) {

    var lineupTemplateText = fs.readFileSync('lineup-view.jst', 'utf8');
    dot.templateSettings.strip = false;

    var lineupTemplateFn = dot.template(lineupTemplateText);

    var data = {};
    data.array = [];

    var feedGen = this.createFeedGenerator();

    for (var i = 0; i < compiledLineup.Programs.length; i++) {
        var entry = {};

        if (compiledLineup.Programs[i].Title) {
                    entry.title = compiledLineup.Programs[i].Title;
                } else {
                    entry.title = "بی نام";
                }
                entry.description = "";

        if (compiledLineup.Programs[i].PreShow) {
            entry.startTime = moment(compiledLineup.Programs[i].PreShow.Meta.TentativeStartTime).format('HH:mm');

            for (var j = 0; j < compiledLineup.Programs[i].PreShow.Clips.length; j++) {
                if (compiledLineup.Programs[i].PreShow.Clips[j].Description) {
                    entry.description += compiledLineup.Programs[i].PreShow.Clips[j].Description;
                    entry.description += "؛ ";
                }
            }
        } else {
            entry.startTime = moment(compiledLineup.Programs[i].Show.Meta.TentativeStartTime).format('HH:mm');
        }
        entry.endTime = moment(compiledLineup.Programs[i].Show.Meta.TentativeEndTime).format('HH:mm');

        for (var j = 0; j < compiledLineup.Programs[i].Show.Clips.length; j++) {
            if (compiledLineup.Programs[i].Show.Clips[j].Description && compiledLineup.Programs[i].Show.Clips[j].Description != "") {
                if (compiledLineup.Programs[i].Show.Clips[j].HasVOD) {
                    // reconstruct the relative path to the media from the absolute playback path
                    vodRelativeURI = compiledLineup.Programs[i].Show.Clips[j].Path.replace(this.config.Radio.Media.BaseDir,"");
                    vodUrl = new URL(vodRelativeURI, 'http://vod.raa.media/');
                    entry.description += '' + compiledLineup.Programs[i].Show.Clips[j].Description + "";

                    /* Now update the RSS xml feed (If the clip should be published) */
                    // An example of an item which should not be published (although having VOD),
                    // is the re-broadcast of a program.
                    if (compiledLineup.Programs[i].PublishPodcast) {
                        // Create a new feed gen item
                        rssFeedItem = this.createRSSItemTemplate();
                        rssFeedItem.title = compiledLineup.Programs[i].Title;
                        rssFeedItem.description = compiledLineup.Programs[i].Show.Clips[j].Description;
                        rssFeedItem.url = vodUrl.toString();
                        rssFeedItem.date = Date().toString();

                        rssFeedItem.enclosure = {};
                        var podcastEncodedUrl = 'https://api.raa.media/linkgenerator/podcast/' + Buffer.from(vodUrl.toString()).toString('base64');
                        // This is to convince podcast players that this is an audio file!! It will be ignored by base64 decoder later
                        podcastEncodedUrl = podcastEncodedUrl + ".mp3";
                        rssFeedItem.enclosure.url = podcastEncodedUrl;

                        itunesSubtitleElement = {};
                        itunesSubtitleElement["itunes:subtitle"] = compiledLineup.Programs[i].Show.Clips[j].Description;;
                        rssFeedItem.custom_elements.push(itunesSubtitleElement);

                        // Push the new item
                        feedGen.addItem(rssFeedItem);
                    }

                } else {
                    entry.description += compiledLineup.Programs[i].Show.Clips[j].Description;
                }

                if (j != compiledLineup.Programs[i].Show.Clips.length - 1) { // Except for the last item
                    entry.description += "؛ ";
                }
            }
        }

        entry.description = entry.description.trim();
        // Hack to remove extra semicolon if it is remained from before
        if (entry.description.charAt(entry.description.length - 1) == "؛") {
            entry.description = entry.description.slice(0, -1);
        }

        data.array.push(entry);
    }

    var resultText = lineupTemplateFn(data);
    // A little bit of hacking here to recreate the current date
    today = moment(compiledLineup.Programs[0].Show.StartTime).format('YYYY-MM-DD');
    fs.writeFileSync(this.cwd + "/web/lineup-" + today + ".html", resultText);
    fs.writeFileSync(this.cwd + "/web/lineup-" + today + ".json", JSON.stringify(data, null, 4));

    // Publish the RSS feed (but publish yesterday items not today's)
    feedGen.publishFeed(targetDateMoment, this.cwd + "/rss/rss.xml", false);
}

Radio.prototype.createRSSItemTemplate = function() {
    return {
        author: 'رادیو اتو-اسعد',
        custom_elements: [
          {'itunes:author': 'رادیو اتو-اسعد'},
          {'itunes:image': {
            _attr: {
              href: 'http://raa.media/img/raa-cover-itunes.png'
            }
          }}
        ]
    }
}

Radio.prototype.createFeedGenerator = function() {
    return new RSSFeedGenerator({
        title: 'رادیو اتو-اسعد',
        custom_namespaces: {
            'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'
        },
        description: 'پادکست‌های رادیو اتو-اسعد شامل برنامه‌هایی است که امکان پخش عمومی آن‌ها برای ما وجود داشته است.',
        feed_url: 'http://raa.media/rss.xml',
        site_url: 'http://raa.media',
        image_url: 'http://raa.media/img/raa-cover-itunes.png',
        copyright: '2017 Radio Auto-asaad',
        language: 'fa',
        pubDate: 'Aug 01, 2017 04:00:00 GMT',
        ttl: '60',
        custom_elements: [
          {'itunes:subtitle': 'پادکست‌های رادیو اتو-اسعد'},
          {'itunes:author': 'اتو-اسعد'},
          {'itunes:explicit': false},
          {'itunes:owner': [
            {'itunes:name': 'Radio Auto-asaad'},
            {'itunes:email': 'admin@raa.media'}
          ]},
          {'itunes:image': {
            _attr: {
              href: 'http://raa.media/img/raa-cover-itunes.png'
            }
          }},
          {'itunes:category': [
            {_attr: {
              text: 'Arts'
            }},
            {'itunes:category': {
              _attr: {
                text: 'Literature'
              }
            }}
          ]}
        ]
    });
}

Raa1.prototype.registerWebApp = function() {
    var self = this;

    self.iosDB = new sqlite3.Database(this.cwd + '/run/ios-device-list.db', sqlite3.OPEN_READWRITE, function(err) {
      if (err) {
        console.error('Error connecting to iOS database: ' + err.message);
      }
      console.log('Connected to the iOS database.');
    });

    self.fcmDB = new sqlite3.Database(this.cwd + '/run/fcm-device-list.db', sqlite3.OPEN_READWRITE, function(err) {
      if (err) {
        console.error('Error connecting to FCM database: ' + err.message);
      }
      console.log('Connected to the FCM database.');
    });

    self.iosDB.serialize(function() {
        self.iosDB.run("CREATE TABLE if not exists devices (deviceId TEXT PRIMARY_KEY, unique(deviceId))");
    });

    self.fcmDB.serialize(function() {
        self.fcmDB.run("CREATE TABLE if not exists devices (deviceId TEXT PRIMARY_KEY, unique(deviceId))");
    });

    self.webApp = express()
    self.webApp.get('/registerDevice/ios/:deviceId', function(req, res) {
        var deviceId = req.params['deviceId'];
        if (deviceId) {
            // Persist deviceId in our local database
            self.iosDB.serialize(function() {
                var stmt = self.iosDB.prepare("INSERT INTO devices VALUES (?)");
                stmt.run(deviceId, function(err) {
                    // Insertion can fail because of duplicate rows coming in. Ignore them
                    // TODO better exception handling
                });
            });
        }
        res.send("Success");
    });

    self.webApp.get('/registerDevice/fcm/:deviceId', function(req, res) {
        var deviceId = req.params['deviceId'];
        if (deviceId) {
            // Persist deviceId in our local database
            self.fcmDB.serialize(function() {
                var stmt = self.fcmDB.prepare("INSERT INTO devices VALUES (?)");
                stmt.run(deviceId, function(err) {
                    // Insertion can fail because of duplicate rows coming in. Ignore them
                    // TODO better exception handling
                });
            });
        }
        res.send("Success");
    });

    self.webApp.get('/linkgenerator/:medium/:urlEncoded', function(req, res) {
        var medium = req.params['medium'];
        var urlEncoded = req.params['urlEncoded'];
        var reqUrl = Buffer.from(urlEncoded, 'base64').toString('ascii');

        if (reqUrl) {
            var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
            var userAgent = req.headers['user-agent'];

            // Report to GA server
            var gaParams = {
                'v': '1',
                'tid': 'UA-103579661-1',
                'cid': '555',
                't': 'event',
                'ea': 'play',
            };
            gaParams.el = reqUrl;
            gaParams.uip = ip;
            gaParams.ua = userAgent;
            gaParams.ec = medium;
            gaParams.ca = medium;
            gaParams.cn = medium;
            gaParams.cm = medium;

            var requestOptions = {
                url: "https://www.google-analytics.com/collect",
                method: "POST",
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                form: gaParams
            }

            request.post(requestOptions,
                function(error, response, body) {
                    if (error) {
                        console.log("Error while sending GA request: " + error);
                    }
                });

            return res.redirect(reqUrl);
        }

        return res.status(400).send("The query url is missing or not valid");
    });

    self.webApp.listen(7799, function () {
        console.log('Raa device registration endpoint activated on port 7799');
    });
}

/* === Entry Point === */
program
    .version('1.0.0')
    .option('-v, --verbose', 'Detailed logs')
    .option('-t, --test', 'Test mode (no side effects)')
    .option('-n --no-scheduling', 'Persist all steps except scheduling (For advacned testing only)')
    .option('-d --target-date [date]', "Target date in YYYY-MM-DD format", moment)
    .parse(process.argv)

if (program.args.length < 2) {
    console.log("Usage: node raa1.js OPTIONS {config-file} {deployment-mode}");
    process.exit(1);
}

var raa1 = new Raa1(program);

raa1.initialize();
<|endoftext|>


<commit_before>var path = require('path')
var fs = require('fs')
var api = require('zenircbot-api')
var Set = require('Set')

var zen = new api.ZenIRCBot()
var channels = ['#readthedocs']
var oncall = new Set()
var confFile = path.join(__dirname, 'rtfd.json')

if (fs.existsSync(confFile)) {
    var conf = api.load_config(confFile)
    if (conf.channels) {
        channels = channels.concat(conf.channels)
    }
    if (conf.oncall) {
        oncall = oncall.addAll(conf.oncall)
    }
}

var info = [
    ('Welcome to #readthedocs, if you have a question please prefix it with ' +
     '!help in order to notify the volunteer support people.'),
    ('Please allow some time for a response as we may be asleep or ' +
     'otherwise busy.'),
    ("If you don't get a response please file an issue here: " +
     "https://github.com/rtfd/readthedocs.org/issues")
]

zen.register_commands(
    "rtfd.js",
    [
        {
            name: "oncall",
            description: "Adds caller to on call list."
        }, {
            name: "offcall",
            description: "Removes call from on call list."
        }, {
            name: "help",
            description: "Pings on call people and records question."
        }, {
            name: "info",
            description: "Gives basic information about using the commands."
        }
    ]
)

var filtered = zen.filter({version: 1, type: 'directed_privmsg'})
filtered.on('data', function(msg) {
    if (channels.indexOf(msg.data.channel) !== -1) {
        var help = /^help(\s.*)?$/.exec(msg.data.message)
        if (/^info$/.test(msg.data.message)) {
            info.forEach(function (line) {
                zen.send_privmsg(msg.data.channel, line)
            })
        } else if (help) {
            zen.send_privmsg(msg.data.channel,
                             oncall.toArray().join(', ') + ': ^')
            var question = help[1]
            if (question) {
                fs.appendFile('./question.log', question.trim() + '\n')
            }
        } else if (/^oncall$/.test(msg.data.message)) {
            oncall.add(msg.data.sender)
        } else if (/^offcall$/) {
            oncall.remove(msg.data.sender)
        }
    }
})
<commit_msg>add depth command<commit_after>var path = require('path')
var fs = require('fs')
var http = require('http')
var api = require('zenircbot-api')
var Set = require('Set')

var zen = new api.ZenIRCBot()
var channels = ['#readthedocs']
var oncall = new Set()
var confFile = path.join(__dirname, 'rtfd.json')

if (fs.existsSync(confFile)) {
    var conf = api.load_config(confFile)
    if (conf.channels) {
        channels = channels.concat(conf.channels)
    }
    if (conf.oncall) {
        oncall = oncall.addAll(conf.oncall)
    }
}

var info = [
    ('Welcome to #readthedocs, if you have a question please prefix it with ' +
     '!help in order to notify the volunteer support people.'),
    ('Please allow some time for a response as we may be asleep or ' +
     'otherwise busy.'),
    ("If you don't get a response please file an issue here: " +
     "https://github.com/rtfd/readthedocs.org/issues")
]

zen.register_commands(
    "rtfd.js",
    [
        {
            name: "oncall",
            description: "Adds caller to on call list."
        }, {
            name: "offcall",
            description: "Removes call from on call list."
        }, {
            name: "help",
            description: "Pings on call people and records question."
        }, {
            name: "info",
            description: "Gives basic information about using the commands."
        }, {
            name: "depth",
            description: "Gets the current queue depth."
        }
    ]
)

var filtered = zen.filter({version: 1, type: 'directed_privmsg'})
filtered.on('data', function(msg) {
    if (channels.indexOf(msg.data.channel) !== -1) {
        var help = /^help(\s.*)?$/.exec(msg.data.message)
        if (/^info$/.test(msg.data.message)) {
            info.forEach(function (line) {
                zen.send_privmsg(msg.data.channel, line)
            })
        } else if (help) {
            zen.send_privmsg(msg.data.channel,
                             oncall.toArray().join(', ') + ': ^')
            var question = help[1]
            if (question) {
                fs.appendFile('./question.log', question.trim() + '\n')
            }
        } else if (/^oncall$/.test(msg.data.message)) {
            oncall.add(msg.data.sender)
            zen.send_privmsg(msg.data.channel,
                             'Added ' + msg.data.sender +
                             'to the oncall list.')
        } else if (/^offcall$/) {
            oncall.remove(msg.data.sender)
            zen.send_privmsg(msg.data.channel,
                             'Removed ' + msg.data.sender +
                             'from the oncall list.')
        } else if (/^depth$/) {
            http.get('https://readthedocs.org/depth/', function(res) {
                res.on('data', function(chunk) {
                    zen.send_privmsg(msg.data.channel,
                                     'Queue depth is currently: ' + chunk)
                })
            })
        }
    }
})
<|endoftext|>