<commit_before>/*
 * Copyright 2017, Maha Farhat
 *
 * This file is part of the software gentb, consisting of custom
 * code for the GenTB's django-based website.
 *
 * gentb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * gentb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with gentb.  If not, see <http://www.gnu.org/licenses/>.
 */

var mutations = [];

set_tooltip = function(elem, title) {
    return elem.attr('data-original-title', title)
               .tooltip('fixTitle')
               .tooltip('show');
};

function addOption(self, name, value) {
    var opt = $("<option></option>");
    $(self).append(opt);
    opt.attr("value", name.value || value || name);
    opt.text(name.name || name);
    return opt;
}
function replaceOptions(self, options) {
    self.empty();
    if($(self).is('select')) {
      addOption($(self), "---");
    }
    $.each(options, function(index, option) {
      addOption($(self), option).data('children', option.children);
    });
}
/*
 * This format function allows us to format our tooltips using a basic
 * templating. Basically each tooltip will be a full text field with html
 * and the values that go into the various parts of the text will be in the
 * format {field_name:format} and will come from the point data source.
 */
String.prototype.format = function (values) {
  return this.replace(/{(([^}:]+):)?([^}]*?)?}/g, function () {
    var name = (arguments[2] || "value").split('.');
    var format = arguments[3] || "s";

    var datum = values;
    for(var i = 0; i < name.length; i++) {
      datum = datum[name[i]];
    }
    var value = datum;
    if(format != 's') {
      value = d3.format(format)(datum);
    }
    if(value == 'NaN' || typeof value == "NaN") {
      value = datum;
    }
    return typeof value != 'undefined' ? value : '';
  });
};
function unique(array) {
    return $.grep(array, function(el, index) {
        return index === $.inArray(el, array);
    });
}

var mutations_chart;

$(document).ready(function() {
    $('body').tooltip({
        selector: 'input#snp'
    });

    var svg = 'svg.mutations';
    mutations_chart = initialiseMutationChart(svg);

    $('#mutation-store').data('url-signal', function(url, args) {

        // Setup mutations table first
        var t_table = $('#mutation_map table');

        if(t_table.data('loaded')) {
            return
        }
        t_table.data('loaded', 1);

        var table = t_table.DataTable({
          "processing": true,
          "serverSide": true,
          "ajax": {
              "url": url,
              "data": function ( data ) {
                  // Sent json, store for future use in selecting
                  return data;
              },
              "dataSrc": function ( json ) {
                  // Returned json, filter and etc here.
                  return json.data;
              },
          },
          "language": {
            "processing": "Loading...",
          },
          "columns": [
            {
              "data": "name",
              "title": "Name",
              "description": "Name of the Gene Locus",
              // "render": $.fn.dataTable.render.number(',', '.', 3, ''),
            },
            {
              "data": "gene_locus__name",
              "title": "Gene Locus",
            },
            {
              "data": "nucleotide_position",
              "title": "Position",
            },
            {
              "data": "nucleotide_reference",
              "title": "Reference",
            },
            {
              "data": "nucleotide_varient",
              "title": "Varient",
            },
            {
              "data": "codon_reference",
              "title": "Codon",
            },
            {
              "data": "codon_varient",
              "title": "Varient",
            },
          ],
          'order': [[2, 'asc']],
        })

        table.on('draw', function(a, b, c) {
          $('tbody tr', t_table).click(function() {
            var data = table.row( this.rowIndex - 1 ).data();
            var snp = $('#snp');

            $(this).toggleClass('selected');
            toggleTabData('mutation', data.name, data.name, ' icon-mutation');

            mutations = getTabData('mutation');
            refresh_mutations_graph();

          }).each(function(key, value) {
            // Keep highlights as paging happens.
            var data = table.row( value.rowIndex - 1 ).data();
            if (data && mutations.includes(data.name)) {
              $(value).addClass('selected');
            }
          });
        });

      if($('#level-0').length == 0) {
        initialiseMutationList(url, args);
      }
    });
});

function refresh_mutations_graph() {
    $.getJSON($('svg.mutations').data('json-url'), getAllTabData())
      .done(function(json) {
          if(json.data.length > 0) {
            $('#mutations').show();
            $('#mutation_explainer').hide();
          } else {
            $('#mutations').hide();
            $('#mutation_explainer').show();
          }
          chartData('svg.mutations', mutations_chart, json.data);
      })
      .fail(function(jqxhr, textStatus, error) {
        var err = textStatus + ", " + error;
        console.log("Request Failed: " + err);
      });
}

function refreshMutation(svg, chart, data) {
  // The goal here is to list all mutations within the
  // selected drug, lineage or country
  $('#mselect').each(function() {
      // replaceOptions($('#level-0'), data.children);
      // $('#level-0').show();
      // $('#level-0').change();
  });
  $('#clear-mutation').click();

}

function initialiseMutationList(url, args, refresh_function) {
    var locus = $('#locus');
    var locus_datalist = $('#locus-list');
    var snp = $('#snp');
    var snp_datalist = $('#mutation-list');
    var button_del = $('#clear-mutation');

    var lookup_url = $('#gene_map').data('locus-lookup');
    var range_url = $('#gene_map').data('locus-range');

    var locus_select = function(val) {
      snp.val('');
      if(val && val != '---') {
        snp_datalist.empty();
        snp_datalist.data('set', '--- ASK AGAIN ---');
        snp.keyup();
        set_tooltip(locus, "Gene " + val + " Selected");
        // Refresh range
        reset_args();
        $('#mutation_selector').addClass('loading');
        $('#locus').attr("disabled", "disabled");
        $.getJSON(range_url, args).done(function(json) {
          $('#mutation_selector').removeClass('loading');
          $('#locus').removeAttr("disabled");
          $('#gene_map').show();
          $('#gene_start').text(json['start']);
          $('#gene_end').text(json['end']);
          // XXX $('#gene_label').text(json['title']);
          var max = parseFloat(json['max']);
          for(var i=1;i<=50;i++) {
              var items = json['values'][i - 1];
              if(items == undefined) { items = []; }
              var count = items.length;
              var height = (parseFloat(count) / max) * 50;

              // Back-calc the start of this range (for confirmation)
              var girth = (json['end'] - json['start']) / 50;
              var start = parseInt(((i - 1) * girth) + json['start']);
              var end = parseInt((i * girth) + json['start']);

              $('#ms-'+i).data('items', items)
                  .attr('data-original-title', count + " mutations, " + start + "-" + end)
                  .attr('data-container', 'body')
                  .tooltip();

              $('#ms-'+i+' span').css('line-height', height + 'px');
          }
        });
      }
    }

    $('#mutation_selector > span').click(function() {
        $('#mutation_selector > span.selected').removeClass('selected');
        $(this).addClass('selected');
        replaceOptions(snp_datalist, $(this).data('items'));
        snp.focus();
    });

    locus.select(function(){locus_select($(this).val())});
    locus.data('previous', locus.val());

    function reset_args() {
      args = getAllTabData();
      args.locus = locus.val();
      args.synonymous = $('input[name="synon"]').is(':checked');
      delete args.snp;
      delete args.ecoli;
      delete args.range;
    }

    locus.on('keyup', function(e){
      var selected = $(this).val();
      if($(this).data('previous') == selected) { return; }
      $(this).data('previous', selected);
      reset_args();

      $.getJSON(lookup_url, args).done(function(json) {
        set_tooltip(locus, json.msg || "Not updated");

        if(json.values) {
            replaceOptions(locus_datalist, json.values);
            if(json.values.length == 1 && json.values[0].toLowerCase() == selected.toLowerCase()) {
                locus.val(json.values[0]);
                locus_datalist.empty()
                locus_select(json.values[0]);
            } else if(selected.toLowerCase() == 'all') {
                locus.val('All');
                locus_datalist.empty()
                locus_select('All');
            }
        } else {
          locus_datalist.empty();
        }
        // Trigger workaround to update datalist.
        locus.focus();
      }).fail(function(jqxhr, textStatus, error) {
        var err = textStatus + ", " + error;
        console.log("Request Failed: " + err);
      });
    });

    snp.select(function() {
      var value = $(this).val();
      var mutations = $('#mutation-store').data('value');
      mutations.push(value);
      mutations = unique(mutations);
      $('#mutation-store').data('value', mutations);
      refresh_function(mutations);
      $(this).val('');
      $(this).blur();
      button_del.show();
    });

    snp.on('keyup', function(e){
      var selected = $(this).val();
      if($(this).data('previous') == selected || selected == '') { return; }
      $(this).data('previous', selected);
      reset_args();
      if(selected.toLowerCase().startsWith('e:')) {
          if(isNaN(selected.substr(2)) || selected.length <= 2) {
              return;
          }
          args.ecoli = selected.substr(2);
      } else {
          args.snp = selected;
      }
      $.getJSON(url, args).done(function(json) {
        snp.attr('data-original-title', json.msg || "Not updated")
          .tooltip('fixTitle')
          .tooltip('show');

        if(json.values) {
          replaceOptions(snp_datalist, json.values);
        } else {
          snp_datalist.empty();
        }
        // Trigger workaround to update datalist.
        snp.focus();
      }).fail(function(jqxhr, textStatus, error) {
        var err = textStatus + ", " + error;
        console.log("Request Failed: " + err);
      });
    });

    button_del.click(function() {
      $('#mutation-store').data('value', new Array());
      refresh_function(new Array());
      button_del.hide();
    });
}

function initialiseMutationChart() {
    var chart = nv.models.multiBarChart()
      .stacked(false)
      .reduceXTicks(false);

    var width = 1000;
    var height = 600;

    chart.margin({top: 40, right: 20, bottom: 120, left: 80});
    chart.height(height);
    chart.width(width);
    chart.yAxis.scale(100).orient("left")
        .axisLabel('Percent of strains')
        .tickFormat(d3.format(".2%"));

    chart.tooltip.contentGenerator(function (data) {
      data.extra = {
         'head': chart.tooltip.headerFormatter()(data.value),
         'y': chart.tooltip.valueFormatter()(data.data.y),
      };
      template = '

' +
        '
{extra.head:s}
' +
        '
' +
          '

	' +
          '{data.key:s}	' +
        '{extra.y:s}	{data.value:d} of {data.total:d}

';
      return template.format(data);
    });

    chart.xAxis
        .axisLabel("Mutation Name")
        .rotateLabels(-20);

    chart.showLegend(true);

    var data = [{'values': [], key: 'Resistant'}];

    var svg = d3.select('svg.mutations')
          .attr('perserveAspectRatio', 'xMinYMid')
          .attr('width', width)
          .attr('height', height)
          .attr('viewBox', '0 0 ' + width + ' ' + height)
          .datum(data)
          .transition().duration(500)
          .call(chart);

    nv.utils.windowResize(chart.update);
    return chart;
}
<commit_msg>Fixed indentation pt. 2<commit_after>/*
 * Copyright 2017, Maha Farhat
 *
 * This file is part of the software gentb, consisting of custom
 * code for the GenTB's django-based website.
 *
 * gentb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * gentb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with gentb.  If not, see <http://www.gnu.org/licenses/>.
 */

var mutations = [];

set_tooltip = function(elem, title) {
    return elem.attr('data-original-title', title)
               .tooltip('fixTitle')
               .tooltip('show');
};

function addOption(self, name, value) {
    var opt = $("<option></option>");
    $(self).append(opt);
    opt.attr("value", name.value || value || name);
    opt.text(name.name || name);
    return opt;
}
function replaceOptions(self, options) {
    self.empty();
    if($(self).is('select')) {
      addOption($(self), "---");
    }
    $.each(options, function(index, option) {
      addOption($(self), option).data('children', option.children);
    });
}
/*
 * This format function allows us to format our tooltips using a basic
 * templating. Basically each tooltip will be a full text field with html
 * and the values that go into the various parts of the text will be in the
 * format {field_name:format} and will come from the point data source.
 */
String.prototype.format = function (values) {
  return this.replace(/{(([^}:]+):)?([^}]*?)?}/g, function () {
    var name = (arguments[2] || "value").split('.');
    var format = arguments[3] || "s";

    var datum = values;
    for(var i = 0; i < name.length; i++) {
      datum = datum[name[i]];
    }
    var value = datum;
    if(format != 's') {
      value = d3.format(format)(datum);
    }
    if(value == 'NaN' || typeof value == "NaN") {
      value = datum;
    }
    return typeof value != 'undefined' ? value : '';
  });
};
function unique(array) {
    return $.grep(array, function(el, index) {
        return index === $.inArray(el, array);
    });
}

var mutations_chart;

$(document).ready(function() {
    $('body').tooltip({
        selector: 'input#snp'
    });

    var svg = 'svg.mutations';
    mutations_chart = initialiseMutationChart(svg);

    $('#mutation-store').data('url-signal', function(url, args) {

        // Setup mutations table first
        var t_table = $('#mutation_map table');

        if(t_table.data('loaded')) {
            return
        }
        t_table.data('loaded', 1);

        var table = t_table.DataTable({
          "processing": true,
          "serverSide": true,
          "ajax": {
              "url": url,
              "data": function ( data ) {
                  // Sent json, store for future use in selecting
                  return data;
              },
              "dataSrc": function ( json ) {
                  // Returned json, filter and etc here.
                  return json.data;
              },
          },
          "language": {
            "processing": "Loading...",
          },
          "columns": [
            {
              "data": "name",
              "title": "Name",
              "description": "Name of the Gene Locus",
              // "render": $.fn.dataTable.render.number(',', '.', 3, ''),
            },
            {
              "data": "gene_locus__name",
              "title": "Gene Locus",
            },
            {
              "data": "nucleotide_position",
              "title": "Position",
            },
            {
              "data": "nucleotide_reference",
              "title": "Reference",
            },
            {
              "data": "nucleotide_varient",
              "title": "Varient",
            },
            {
              "data": "codon_reference",
              "title": "Codon",
            },
            {
              "data": "codon_varient",
              "title": "Varient",
            },
          ],
          'order': [[2, 'asc']],
        })

        table.on('draw', function(a, b, c) {
          $('tbody tr', t_table).click(function() {
            var data = table.row( this.rowIndex - 1 ).data();
            var snp = $('#snp');

            $(this).toggleClass('selected');
            toggleTabData('mutation', data.name, data.name, ' icon-mutation');

            mutations = getTabData('mutation');
            refresh_mutations_graph();

          }).each(function(key, value) {
            // Keep highlights as paging happens.
            var data = table.row( value.rowIndex - 1 ).data();
            if (data && mutations.includes(data.name)) {
              $(value).addClass('selected');
            }
          });
        });

      if($('#level-0').length == 0) {
        initialiseMutationList(url, args);
      }
    });
});

function refresh_mutations_graph() {
    $.getJSON($('svg.mutations').data('json-url'), getAllTabData())
      .done(function(json) {
          if(json.data.length > 0) {
            $('#mutations').show();
            $('#mutation_explainer').hide();
          } else {
            $('#mutations').hide();
            $('#mutation_explainer').show();
          }
          chartData('svg.mutations', mutations_chart, json.data);
      })
      .fail(function(jqxhr, textStatus, error) {
        var err = textStatus + ", " + error;
        console.log("Request Failed: " + err);
      });
}

function refreshMutation(svg, chart, data) {
  // The goal here is to list all mutations within the
  // selected drug, lineage or country
  $('#mselect').each(function() {
      // replaceOptions($('#level-0'), data.children);
      // $('#level-0').show();
      // $('#level-0').change();
  });
  $('#clear-mutation').click();

}

function initialiseMutationList(url, args, refresh_function) {
    var locus = $('#locus');
    var locus_datalist = $('#locus-list');
    var snp = $('#snp');
    var snp_datalist = $('#mutation-list');
    var button_del = $('#clear-mutation');

    var lookup_url = $('#gene_map').data('locus-lookup');
    var range_url = $('#gene_map').data('locus-range');

    var locus_select = function(val) {
      snp.val('');
      if(val && val != '---') {
        snp_datalist.empty();
        snp_datalist.data('set', '--- ASK AGAIN ---');
        snp.keyup();
        set_tooltip(locus, "Gene " + val + " Selected");
        // Refresh range
        reset_args();
        $('#mutation_selector').addClass('loading');
        $('#locus').attr("disabled", "disabled");
        $.getJSON(range_url, args).done(function(json) {
          $('#mutation_selector').removeClass('loading');
          $('#locus').removeAttr("disabled");
          $('#gene_map').show();
          $('#gene_start').text(json['start']);
          $('#gene_end').text(json['end']);
          // XXX $('#gene_label').text(json['title']);
          var max = parseFloat(json['max']);
          for(var i=1;i<=50;i++) {
              var items = json['values'][i - 1];
              if(items == undefined) { items = []; }
              var count = items.length;
              var height = (parseFloat(count) / max) * 50;

              // Back-calc the start of this range (for confirmation)
              var girth = (json['end'] - json['start']) / 50;
              var start = parseInt(((i - 1) * girth) + json['start']);
              var end = parseInt((i * girth) + json['start']);

              $('#ms-'+i).data('items', items)
                  .attr('data-original-title', count + " mutations, " + start + "-" + end)
                  .attr('data-container', 'body')
                  .tooltip();

              $('#ms-'+i+' span').css('line-height', height + 'px');
          }
        });
      }
    }

    $('#mutation_selector > span').click(function() {
        $('#mutation_selector > span.selected').removeClass('selected');
        $(this).addClass('selected');
        replaceOptions(snp_datalist, $(this).data('items'));
        snp.focus();
    });

    locus.select(function(){locus_select($(this).val())});
    locus.data('previous', locus.val());

    function reset_args() {
      args = getAllTabData();
      args.locus = locus.val();
      args.synonymous = $('input[name="synon"]').is(':checked');
      delete args.snp;
      delete args.ecoli;
      delete args.range;
    }

    locus.on('keyup', function(e){
      var selected = $(this).val();
      if($(this).data('previous') == selected) { return; }
      $(this).data('previous', selected);
      reset_args();

      $.getJSON(lookup_url, args).done(function(json) {
        set_tooltip(locus, json.msg || "Not updated");

        if(json.values) {
            replaceOptions(locus_datalist, json.values);
            if(json.values.length == 1 && json.values[0].toLowerCase() == selected.toLowerCase()) {
                locus.val(json.values[0]);
                locus_datalist.empty()
                locus_select(json.values[0]);
            } else if(selected.toLowerCase() == 'all') {
                locus.val('All');
                locus_datalist.empty()
                locus_select('All');
            }
        } else {
          locus_datalist.empty();
        }
        // Trigger workaround to update datalist.
        locus.focus();
      }).fail(function(jqxhr, textStatus, error) {
        var err = textStatus + ", " + error;
        console.log("Request Failed: " + err);
      });
    });

    snp.select(function() {
      var value = $(this).val();
      var mutations = $('#mutation-store').data('value');
      mutations.push(value);
      mutations = unique(mutations);
      $('#mutation-store').data('value', mutations);
      refresh_function(mutations);
      $(this).val('');
      $(this).blur();
      button_del.show();
    });

    snp.on('keyup', function(e){
      var selected = $(this).val();
      if($(this).data('previous') == selected || selected == '') { return; }
      $(this).data('previous', selected);
      reset_args();
      if(selected.toLowerCase().startsWith('e:')) {
          if(isNaN(selected.substr(2)) || selected.length <= 2) {
              return;
          }
          args.ecoli = selected.substr(2);
      } else {
          args.snp = selected;
      }
      $.getJSON(url, args).done(function(json) {
        snp.attr('data-original-title', json.msg || "Not updated")
          .tooltip('fixTitle')
          .tooltip('show');

        if(json.values) {
          replaceOptions(snp_datalist, json.values);
        } else {
          snp_datalist.empty();
        }
        // Trigger workaround to update datalist.
        snp.focus();
      }).fail(function(jqxhr, textStatus, error) {
        var err = textStatus + ", " + error;
        console.log("Request Failed: " + err);
      });
    });

    button_del.click(function() {
      $('#mutation-store').data('value', new Array());
      refresh_function(new Array());
      button_del.hide();
    });
}

function initialiseMutationChart() {
    var chart = nv.models.multiBarChart()
      .stacked(false)
      .reduceXTicks(false);

    var width = 1000;
    var height = 600;

    chart.margin({top: 40, right: 20, bottom: 120, left: 80});
    chart.height(height);
    chart.width(width);
    chart.yAxis.scale(100).orient("left")
        .axisLabel('Percent of strains')
        .tickFormat(d3.format(".2%"));

    chart.tooltip.contentGenerator(function (data) {
        data.extra = {
          'head': chart.tooltip.headerFormatter()(data.value),
          'y': chart.tooltip.valueFormatter()(data.data.y),
        };
        template = '

' +
          '
{extra.head:s}
' +
          '
' +
            '

	' +
            '{data.key:s}	' +
          '{extra.y:s}	{data.value:d} of {data.total:d}

';
        return template.format(data);
    });

    chart.xAxis
        .axisLabel("Mutation Name")
        .rotateLabels(-20);

    chart.showLegend(true);

    var data = [{'values': [], key: 'Resistant'}];

    var svg = d3.select('svg.mutations')
          .attr('perserveAspectRatio', 'xMinYMid')
          .attr('width', width)
          .attr('height', height)
          .attr('viewBox', '0 0 ' + width + ' ' + height)
          .datum(data)
          .transition().duration(500)
          .call(chart);

    nv.utils.windowResize(chart.update);
    return chart;
}
<|endoftext|>


<commit_before>"use strict";

var base = require("xbase"),
	rimraf = require("rimraf"),
	fs = require("fs"),
	moment = require("moment"),
	glob = require("glob"),
	path = require("path"),
	runUtil = require("xutil").run,
	fileUtil = require("xutil").file,
	dustUtil = require("xutil").dust,
	printUtil = require("xutil").print,
	tiptoe = require("tiptoe");

if(process.argv.length<3 || !fs.existsSync(process.argv[2]))
{
	base.error("Usage: node extractImages.js /path/to/hots");
	process.exit(1);
}

var HOTS_PATH = process.argv[2];
var HOTS_DATA_PATH = path.join(HOTS_PATH, "HeroesData");

if(!fs.existsSync(HOTS_DATA_PATH))
{
	base.error("HeroesData dir not found: %s", HOTS_DATA_PATH);
	process.exit(1);
}
var dustData =
{
};

var WEB_OUT_PATH = path.join(__dirname, "dist");

var IMAGES_FULL_PATH = path.join(__dirname, "..", "images", "mods", "heroes.stormmod", "base.stormassets", "Assets", "Textures");

var ZIP_PATH = path.join(__dirname, "images.zip");

var CASCEXTRATOR_PATH = path.join(__dirname, "..", "build", "bin", "CASCExtractor");

var IMAGE_OUT_PATH = path.join(__dirname, "..", "images");

var HEROES_JSON_PATH = path.join(__dirname, "..", "out", "heroes.json");

var IMAGE_ASSETS_PATH = "mods/heroes.stormmod/base.stormassets/Assets/Textures/";

tiptoe(
	function removeJSONDirectory()
	{
		base.info("Clearing and creating out path...");
		rimraf(path.join(WEB_OUT_PATH), this);
	},
	function createJSONDirectory()
	{
		fs.mkdir(path.join(WEB_OUT_PATH), this);
	},
	function extractingBuildVersion()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Extracting build version...");
		runUtil.run(CASCEXTRATOR_PATH, [HOTS_DATA_PATH, "-o", WEB_OUT_PATH, "-f", "mods\\core.stormmod\\base.stormdata\\DataBuildId.txt"], {silent:true}, this);
	},
	function readAndCleanupBuildVersion()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Data Build Version: %s", fs.readFileSync(path.join(WEB_OUT_PATH, "mods", "core.stormmod", "base.stormdata", "DataBuildId.txt"), {encoding:"utf8"}).trim("B").trim());
		rimraf(path.join(WEB_OUT_PATH, "mods"), this);
	},
	function findStaticContent()
	{
		base.info("Finding static content...");
		glob(path.join(__dirname, "static", "*"), this);
	},
	function processStaticContent(content)
	{
		base.info("Copying static content...");
		content.serialForEach(function(staticFile, subcb)
		{
			fileUtil.copy(staticFile, path.join(WEB_OUT_PATH, path.basename(staticFile)), subcb);
		}, this);
	},
	function findJSON()
	{
		base.info("Finding JSON files...");
		glob(path.join(__dirname, "..", "out", "*.json"), this);
	},
	function processJSON(jsonFiles)
	{
		base.info("Copying JSON files...");
		jsonFiles.serialForEach(function(jsonFile, subcb)
		{
			fileUtil.copy(jsonFile, path.join(WEB_OUT_PATH, path.basename(jsonFile)), subcb);
		}, this);
	},
	function render()
	{
		base.info("Rendering index...");

		dustData.heroesSize = printUtil.toSize(fs.statSync(path.join(WEB_OUT_PATH, "heroes.json")).size, 1);
		dustData.mountsSize = printUtil.toSize(fs.statSync(path.join(WEB_OUT_PATH, "mounts.json")).size, 1);
		dustData.changeLog = JSON.parse(fs.readFileSync(path.join(__dirname, "changelog.json"), {encoding : "utf8"})).map(function(o) { o.when = moment(o.when, "YYYY-MM-DD").format("MMM D, YYYY"); return o; });
		dustData.lastUpdated = dustData.changeLog[0].when;
		dustData.version = dustData.changeLog[0].version;
		dustData.patchVersion = dustData.changeLog[0].patchVersion;

		dustUtil.render(__dirname, "index", dustData, { keepWhitespace : true }, this);
	},
	function save(html)
	{
		fs.writeFile(path.join(WEB_OUT_PATH, "index.html"), html, {encoding:"utf8"}, this);
	},
	function extractImages()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Extracting images...");
		extractAllImages(this);
	},
	function getImagesToZip()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Finding extracted images...");
		glob(path.join(IMAGES_FULL_PATH, "*.png"), this);
	},
	function zipImages(images)
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Zipping images...");
		runUtil.run("zip", ["-r", ZIP_PATH].concat(images.map(function(image) { return path.basename(image); })), {silent:true, cwd : IMAGES_FULL_PATH}, this);
	},
	function finish(err)
	{
		if(err)
		{
			base.error(err.stack);
			base.error(err);
			process.exit(1);
		}

		process.exit(0);
	}
);

function extractAllImages(cb)
{
	tiptoe(
		function clearOut()
		{
			base.info("\tClearing 'images' directory...");
			rimraf(IMAGE_OUT_PATH, this);
		},
		function createOut()
		{
			fs.mkdir(IMAGE_OUT_PATH, this);
		},
		function copyBuildInfo()
		{
			base.info("\tCopying latest .build.info file...");
			fileUtil.copy(path.join(HOTS_PATH, ".build.info"), path.join(HOTS_DATA_PATH, ".build.info"), this);
		},
		function loadJSON()
		{
			base.info("\tLoading JSON...");
			fs.readFile(HEROES_JSON_PATH, {encoding:"utf8"}, this);
		},
		function extractFiles(heroesRaw)
		{
			var imageFiles = JSON.parse(heroesRaw).map(function(hero) { return Object.values(hero.talents).flatten().concat(Object.values(hero.abilities).flatten()).concat([hero]).map(function(item) { return item.icon; }); }).flatten().unique();
			base.info("\tExtracting %d image files...", imageFiles.length);
			imageFiles.parallelForEach(extractImage, this, 1);
		},
		cb
	);
}

function extractImage(imageFile, cb)
{
	tiptoe(
		function extractImage()
		{
			runUtil.run(CASCEXTRATOR_PATH, [HOTS_DATA_PATH, "-o", IMAGE_OUT_PATH, "-f", IMAGE_ASSETS_PATH + imageFile], {silent:true}, this);
		},
		function convertImage()
		{
			runUtil.run("convert", [path.join(IMAGE_OUT_PATH, IMAGE_ASSETS_PATH.replaceAll("\\\\", "/"), imageFile), path.join(IMAGE_OUT_PATH, IMAGE_ASSETS_PATH.replaceAll("\\\\", "/"), imageFile + ".png")], {silent:true}, this);
		},
		function removeOldImage()
		{
			fs.unlink(path.join(IMAGE_OUT_PATH, IMAGE_ASSETS_PATH.replaceAll("\\\\", "/"), imageFile), this);
		},
		cb
	);
}
<commit_msg>additional logging<commit_after>"use strict";

var base = require("xbase"),
	rimraf = require("rimraf"),
	fs = require("fs"),
	moment = require("moment"),
	glob = require("glob"),
	path = require("path"),
	runUtil = require("xutil").run,
	fileUtil = require("xutil").file,
	dustUtil = require("xutil").dust,
	printUtil = require("xutil").print,
	tiptoe = require("tiptoe");

if(process.argv.length<3 || !fs.existsSync(process.argv[2]))
{
	base.error("Usage: node extractImages.js /path/to/hots");
	process.exit(1);
}

var HOTS_PATH = process.argv[2];
var HOTS_DATA_PATH = path.join(HOTS_PATH, "HeroesData");

if(!fs.existsSync(HOTS_DATA_PATH))
{
	base.error("HeroesData dir not found: %s", HOTS_DATA_PATH);
	process.exit(1);
}
var dustData =
{
};

var WEB_OUT_PATH = path.join(__dirname, "dist");

var IMAGES_FULL_PATH = path.join(__dirname, "..", "images", "mods", "heroes.stormmod", "base.stormassets", "Assets", "Textures");

var ZIP_PATH = path.join(__dirname, "images.zip");

var CASCEXTRATOR_PATH = path.join(__dirname, "..", "build", "bin", "CASCExtractor");

var IMAGE_OUT_PATH = path.join(__dirname, "..", "images");

var HEROES_JSON_PATH = path.join(__dirname, "..", "out", "heroes.json");

var IMAGE_ASSETS_PATH = "mods/heroes.stormmod/base.stormassets/Assets/Textures/";

tiptoe(
	function removeJSONDirectory()
	{
		base.info("Clearing and creating out path...");
		rimraf(path.join(WEB_OUT_PATH), this);
	},
	function createJSONDirectory()
	{
		fs.mkdir(path.join(WEB_OUT_PATH), this);
	},
	function extractingBuildVersion()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Extracting build version...");
		runUtil.run(CASCEXTRATOR_PATH, [HOTS_DATA_PATH, "-o", WEB_OUT_PATH, "-f", "mods\\core.stormmod\\base.stormdata\\DataBuildId.txt"], {silent:true}, this);
	},
	function readAndCleanupBuildVersion()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Data Build Version: %s", fs.readFileSync(path.join(WEB_OUT_PATH, "mods", "core.stormmod", "base.stormdata", "DataBuildId.txt"), {encoding:"utf8"}).trim("B").trim());
		rimraf(path.join(WEB_OUT_PATH, "mods"), this);
	},
	function findStaticContent()
	{
		base.info("Finding static content...");
		glob(path.join(__dirname, "static", "*"), this);
	},
	function processStaticContent(content)
	{
		base.info("Copying static content...");
		content.serialForEach(function(staticFile, subcb)
		{
			fileUtil.copy(staticFile, path.join(WEB_OUT_PATH, path.basename(staticFile)), subcb);
		}, this);
	},
	function findJSON()
	{
		base.info("Finding JSON files...");
		glob(path.join(__dirname, "..", "out", "*.json"), this);
	},
	function processJSON(jsonFiles)
	{
		base.info("Copying JSON files...");
		jsonFiles.serialForEach(function(jsonFile, subcb)
		{
			fileUtil.copy(jsonFile, path.join(WEB_OUT_PATH, path.basename(jsonFile)), subcb);
		}, this);
	},
	function render()
	{
		base.info("Rendering index...");

		dustData.heroesSize = printUtil.toSize(fs.statSync(path.join(WEB_OUT_PATH, "heroes.json")).size, 1);
		dustData.mountsSize = printUtil.toSize(fs.statSync(path.join(WEB_OUT_PATH, "mounts.json")).size, 1);
		dustData.changeLog = JSON.parse(fs.readFileSync(path.join(__dirname, "changelog.json"), {encoding : "utf8"})).map(function(o) { o.when = moment(o.when, "YYYY-MM-DD").format("MMM D, YYYY"); return o; });
		dustData.lastUpdated = dustData.changeLog[0].when;
		dustData.version = dustData.changeLog[0].version;
		dustData.patchVersion = dustData.changeLog[0].patchVersion;

		dustUtil.render(__dirname, "index", dustData, { keepWhitespace : true }, this);
	},
	function save(html)
	{
		fs.writeFile(path.join(WEB_OUT_PATH, "index.html"), html, {encoding:"utf8"}, this);
	},
	function extractImages()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Extracting images...");
		extractAllImages(this);
	},
	function getImagesToZip()
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Finding extracted images...");
		glob(path.join(IMAGES_FULL_PATH, "*.png"), this);
	},
	function zipImages(images)
	{
		if(process.argv[3]==="dev")
			return this();

		base.info("Zipping images...");
		runUtil.run("zip", ["-r", ZIP_PATH].concat(images.map(function(image) { return path.basename(image); })), {silent:true, cwd : IMAGES_FULL_PATH}, this);
	},
	function finish(err)
	{
		if(err)
		{
			base.error(err.stack);
			base.error(err);
			process.exit(1);
		}

		process.exit(0);
	}
);

function extractAllImages(cb)
{
	tiptoe(
		function clearOut()
		{
			base.info("\tClearing 'images' directory...");
			rimraf(IMAGE_OUT_PATH, this);
		},
		function createOut()
		{
			fs.mkdir(IMAGE_OUT_PATH, this);
		},
		function copyBuildInfo()
		{
			base.info("\tCopying latest .build.info file...");
			fileUtil.copy(path.join(HOTS_PATH, ".build.info"), path.join(HOTS_DATA_PATH, ".build.info"), this);
		},
		function loadJSON()
		{
			base.info("\tLoading JSON...");
			fs.readFile(HEROES_JSON_PATH, {encoding:"utf8"}, this);
		},
		function extractFiles(heroesRaw)
		{
			var imageFiles = JSON.parse(heroesRaw).map(function(hero) { return Object.values(hero.talents).flatten().concat(Object.values(hero.abilities).flatten()).concat([hero]).map(function(item) { return item.icon; }); }).flatten().unique();
			base.info("\tExtracting %d image files...", imageFiles.length);
			imageFiles.parallelForEach(extractImage, this, 1);
		},
		cb
	);
}

function extractImage(imageFile, cb)
{
	tiptoe(
		function extractImage()
		{
      console.log('.');
			runUtil.run(CASCEXTRATOR_PATH, [HOTS_DATA_PATH, "-o", IMAGE_OUT_PATH, "-f", IMAGE_ASSETS_PATH + imageFile], {silent:true}, this);
		},
		function convertImage()
		{
			runUtil.run("convert", [path.join(IMAGE_OUT_PATH, IMAGE_ASSETS_PATH.replaceAll("\\\\", "/"), imageFile), path.join(IMAGE_OUT_PATH, IMAGE_ASSETS_PATH.replaceAll("\\\\", "/"), imageFile + ".png")], {silent:true}, this);
		},
		function removeOldImage()
		{
			fs.unlink(path.join(IMAGE_OUT_PATH, IMAGE_ASSETS_PATH.replaceAll("\\\\", "/"), imageFile), this);
		},
		cb
	);
}
<|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.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'Slot', // ???: Rename AbstractSlot or make an Interface

  requires: [
    'foam.core.internal.SubSlot'
  ],

  documentation: `
    Slots are observable values which can change over time.

    Slots are simple single-value Model-View-Controller Models, but since
    another meaning of 'Model' is already heavily used in FOAM, Slot is
    used to avoid overloading the term.

Types of Slots include:

PropertySlot

ConstantSlot

ExpressionSlot

  `,

  methods: [
    /**
      Subscribe to the Slot's value, if it has one. If the Slot's
      value changes, then unsubscribe from the previous value and
      resubscribe to the new one.
    */
    function valueSub() {
      var self = this;
      var args = Array.from(arguments);
      var s;
      var l = function() {
        var v = self.get();
        if ( s ) s.detach();
        if ( v ) s = v.sub.apply(v, args);
      };
      l();
      this.sub(l);
    },

    /**
      Create a sub-Slot for this Slot's value. If this Slot's
      value changes, then the sub-Slot becomes the Slot for
      the new value's sub-Slot instead. Useful for creating
      Slot paths without having to rebuild whenever a value
      along the chain changes.
    */
    function dot(name) {
      return this.SubSlot.create({
        parent: this,
        name:   name
      });
    },

    // TODO: remove when all code ported
    function link(other) {
      console.warn('deprecated use of link(), use linkFrom() instead');
      return this.linkFrom(other);
    },

    /**
      Link two Slots together, setting both to other's value.
      Returns a Detachable which can be used to break the link.
      After copying a value from one slot to the other, this implementation
      then copies the value back in case the target slot rejected the value.
    */
    function linkFrom(s2) {
      var s1        = this;
      var feedback1 = false;
      var feedback2 = false;

      // TODO: once all slot types property set 'src', these
      // two listeneners can be merged.
      var l1 = function(e) {
        if ( feedback1 ) return;

        if ( ! foam.util.is(s1.get(), s2.get()) ) {
          feedback1 = true;
          try {
            s2.set(s1.get());
            if ( ! foam.util.is(s1.get(), s2.get()) )
              s1.set(s2.get());
          } finally {
            feedback1 = false;
          }
        }
      };

      var l2 = function(e) {
        if ( feedback2 ) return;

        if ( ! foam.util.is(s1.get(), s2.get()) ) {
          feedback2 = true;
          try {
            s1.set(s2.get());
            if ( ! foam.util.is(s1.get(), s2.get()) )
              s2.set(s1.get());
          } finally {
            feedback2 = false;
          }
        }
      };

      var sub1 = s1.sub(l1);
      var sub2 = s2.sub(l2);

      l2();

      return {
        detach: function() {
          sub1 && sub1.detach();
          sub2 && sub2.detach();
          sub1 = sub2 = null;
        }
      };
    },

    function linkTo(other) {
      return other.linkFrom(this);
    },

    /**
      Have this Slot dynamically follow other's value.
      Returns a Detachable which can be used to cancel the binding.
    */
    function follow(other) {
      foam.assert(other, 'Slot.follow requires Slot argument.');
      var self = this;
      var l = function() {
        if ( ! foam.util.is(self.get(), other.get()) ) {
          self.set(other.get());
        }
      };
      l();
      return other.sub(l);
    },

    /**
     * Maps values from one model to another.
     * @param f maps values from srcValue to dstValue
     */
    function mapFrom(other, f) {
      var self = this;
      var l = function() { self.set(f(other.get())); };
      l();
      return other.sub(l);
    },

    function mapTo(other, f) {
      return other.mapFrom(this, f);
    },

    function map(f) {
      return foam.core.ExpressionSlot.create({code: f, args: [this]});
    },

    /**
     * Relate to another Slot.
     * @param f maps from this to other
     * @param fprime maps other to this
     */
    function relateTo(other, f, fPrime) {
      var self     = this;
      var feedback = false;
      var sub      = foam.core.FObject.create();
      var l1 = function() {
        if ( feedback ) return;
        feedback = true;
        other.set(f(self.get()));
        feedback = false;
      };
      var l2 = function() {
        if ( feedback ) return;
        feedback = true;
        self.set(fPrime(other.get()));
        feedback = false;
      };

      sub.onDetach(this.sub(l1));
      sub.onDetach(other.sub(l2));

      l1();

      return sub;
    },

    function relateFrom(other, f, fPrime) {
      return other.relateTo(this, fPrime, f);
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'PropertySlot',
  extends: 'foam.core.Slot',

  documentation: `
    Represents object properties as Slots.
    Created with calling obj.prop$ or obj.slot('prop').
    For internal use only.
  `,

  methods: [
    function initArgs() { },
    function init() { },

    function get() {
      return this.prop.get(this.obj);
    },

    function set(value) {
      return this.prop.set(this.obj, value);
    },

    function getPrev() {
      return this.oldValue;
    },

    function setPrev(value) {
      return this.oldValue = value;
    },

    function sub(l) {
      var s = this.obj.sub('propertyChange', this.prop.name, l);
      s.src = this;
      return s;
    },

    function isDefined() {
      return this.obj.hasOwnProperty(this.prop.name);
    },

    function clear() {
      this.obj.clearProperty(this.prop.name);
    },

    function toString() {
      return 'PropertySlot(' + this.obj.$UID + ',' + this.obj.cls_.id + '.' + this.prop.name + ')';
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'SubSlot',
  extends: 'foam.core.Slot',

  documentation:
      'For internal use only. Is used to implement the Slot.dot() method.',

  properties: [
    'parent', // parent slot, not parent object
    'name',
    'value',
    'prevSub'
  ],

  methods: [
    function init() {
      this.parent.sub(this.parentChange);
      this.parentChange();
    },

    function get() {
      var o = this.parent.get();

      return o && o[this.name];
    },

    function set(value) {
      var o = this.parent.get();

      if ( o ) o[this.name] = value;
    },

    /** Needed? **/
    function getPrev() {
      return this.oldValue;
    },

    /** Needed? **/
    function setPrev(value) {
      return this.oldValue = value;
    },

    function sub(l) {
      return this.SUPER('propertyChange', 'value', l);
    },

    function isDefined() {
      return this.parent.get().hasOwnProperty(this.name);
    },

    function clear() {
      this.parent.get().clearProperty(this.name);
    },

    function toString() {
      return 'SubSlot(' + this.parent + ',' + this.name + ')';
    }
  ],

  listeners: [
    function parentChange(s) {
      this.prevSub && this.prevSub.detach();
      var o = this.parent.get();

      // If the new class has the same axiom as the old class, then we keep this
      // SubSlot attached instead of detaching it.
      if ( o && o.cls_.getAxiomByName(this.name) == null ) {
        this.prevSub = null;
        this.detach();
        return;
      }

      this.prevSub = o && o.slot && o.slot(this.name).sub(this.valueChange);
      this.valueChange();
    },

    function valueChange() {
      var parentValue = this.parent.get();
      this.value = parentValue ? parentValue[this.name] : undefined;
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ConstantSlot',

  extends: 'foam.core.Slot',

  documentation: 'An immutable constant valued Slot.',

  properties: [
    {
      name: 'value',
      getter: function() { return this.value_; },
      setter: function() {}
    }
  ],

  methods: [
    function initArgs(args) { this.value_ = args && args.value; },

    function get() { return this.value; },

    function set() {
      throw new Error('Tried to mutate immutable ConstantSlot.');
    },

    function sub(l) { /* nop */ },

    function follow(other) { /* nop */ },

    function linkFrom(s2) { /* nop */ },

    function linkTo(s2) { s2.set(this.get()); },

    function mapFrom(other, f) { /* nop */ }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ExpressionSlot',
  extends: 'foam.core.PromiseSlot',
  documentation: `
    Tracks dependencies for a dynamic function and invalidates if they change.

      foam.CLASS({name: 'Person', properties: ['fname', 'lname']});
      var p = Person.create({fname: 'John', lname: 'Smith'});
      var e = foam.core.ExpressionSlot.create({
        args: [ p.fname$, p.lname$ ],
        code: function(f, l) { return f + ' ' + l; }
      });
      log(e.get());
      e.sub(log);
      p.fname = 'Steve';
      p.lname = 'Jones';
      log(e.get());

      Output:
       > John Smith
       > [object Object] propertyChange value [object Object]
       > [object Object] propertyChange value [object Object]
       > Steve Jones

      var p = foam.CLASS({name: 'Person', properties: [ 'f', 'l' ]}).create({f:'John', l: 'Doe'});
      var e = foam.core.ExpressionSlot.create({
        obj: p,
        code: function(f, l) { return f + ' ' + l; }
      });

  `,

  properties: [
    'obj',
    'code',
    {
      name: 'args',
      expression: function(obj) {
        foam.assert(obj, 'ExpressionSlot: "obj" or "args" required.');

        var args = foam.Function.argNames(this.code);
        for ( var i = 0 ; i < args.length ; i++ ) {
          args[i] = obj.slot(args[i]);
        }

        // this.invalidate(); // ???: Is this needed?
        this.subToArgs_(args);

        return args;
      },
      postSet: function(_, args) {
        this.subToArgs_(args);
      }
    },
    {
      name: 'value',
      preSet: function(o, n) {
        if ( n && n.then ) {
          this.promise = n;
          n = foam.Undefined.isInstance(o) ? null : o;
        } else {
          // Ensure an old promise doesn't fire and clobber the value.
          this.promise = null;
        }
        return n;
      },
      factory: function() {
        return this.code.apply(this.obj || this, this.args.map(function(a) {
          return a.get();
        }));
      }
    },
    'cleanup_', // detachable to cleanup old subs when obj changes
  ],

  methods: [
    function init() { this.onDetach(this.cleanup); },
    function set() { /* nop */ },
    function subToArgs_(args) {
      this.cleanup();

      var cleanup = foam.core.FObject.create();

      for ( var i = 0 ; i < args.length ; i++ ) {
        cleanup.onDetach(args[i].sub(this.invalidate));
      }

      this.cleanup_ = cleanup;
    }
  ],

  listeners: [
    function cleanup() { this.cleanup_ && this.cleanup_.detach(); },
    function invalidate() { this.clearProperty('value'); }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PromiseSlot',
  extends: 'foam.core.SimpleSlot',

  documentation: `
    A slot that takes a promise and sets its value to its value when it
    resolves.
  `,

  properties: [
    {
      name: 'promise',
      postSet: function(_, n) {
        n && n.then(function(v) {
          if ( n === this.promise ) this.value = v;
        }.bind(this));
      }
    }
  ],

  methods: [
    function set() {
      throw new Error(this.cls_.id + ' does not support setting.');
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ArraySlot',

  extends: 'foam.core.Slot',

  documentation: `
    A slot that takes an array of Slots and notifies when either changes.
  `,

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.Slot',
      name: 'slots'
    }
  ],

  methods: [
    function get() {
      return this.slots.map(s => s.get());
    },

    function set(arr) {
      if ( ! foam.Array.isInstance(arr) )
        throw new Error('ArraySlot can only set an array.');
      arr.forEach((v, i) => this.slots[i].set(v));
    },

    function sub(l) {
      if ( arguments.length != 1 ) return this.SUPER.apply(this, arguments);
      var subs = this.slots.map(s => s.sub(l));
      return {
        detach: function() { subs.forEach(s => s.detach()); }
      };
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'SimpleSlot',
  extends: 'foam.core.Slot',

  properties: [
    {
      name: 'value'
    }
  ],

  methods: [
    function get() { return this.value; },
    function set(v) { this.value = v; },
    function sub(l) {
      return arguments.length === 1 ?
        this.SUPER('propertyChange', 'value', l) :
        this.SUPER.apply(this, arguments);
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ProxySlot',
  extends: 'foam.core.SimpleSlot',
  properties: [
    {
      name: 'sub_'
    },
    {
      name: 'delegate',
      postSet: function(_, n) {
        if ( this.sub_ ) this.sub_.detach();

        if ( foam.core.Slot.isInstance(n) ) {
          this.sub_ = this.linkFrom(n);
        }
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ProxyExpressionSlot',
  extends: 'foam.core.ProxySlot',

  requires: [
    'foam.core.ExpressionSlot'
  ],

  properties: [
    {
      name: 'obj',
      postSet: function () {
        this.update();
      }
    },
    {
      name: 'code',
      postSet: function () {
        this.update();
      }
    },
    {
      name: 'args',
      postSet: function () {
        this.update();
      }
    }
  ],

  methods: [
    function update() {
      if ( ! this.code || ! this.obj ) return;
      this.delegate = this.ExpressionSlot.create({
        args: this.args,
        code: this.code,
        obj: this.obj
      })
    }
  ]
});
<commit_msg>Add docs.<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.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'Slot', // ???: Rename AbstractSlot or make an Interface

  requires: [
    'foam.core.internal.SubSlot'
  ],

  documentation: `
    Slots are observable values which can change over time.

    Slots are simple single-value Model-View-Controller Models, but since
    another meaning of 'Model' is already heavily used in FOAM, Slot is
    used to avoid overloading the term.

Types of Slots include:

PropertySlot

ConstantSlot

ExpressionSlot

  `,

  methods: [
    /**
      Subscribe to the Slot's value, if it has one. If the Slot's
      value changes, then unsubscribe from the previous value and
      resubscribe to the new one.
    */
    function valueSub() {
      var self = this;
      var args = Array.from(arguments);
      var s;
      var l = function() {
        var v = self.get();
        if ( s ) s.detach();
        if ( v ) s = v.sub.apply(v, args);
      };
      l();
      this.sub(l);
    },

    /**
      Create a sub-Slot for this Slot's value. If this Slot's
      value changes, then the sub-Slot becomes the Slot for
      the new value's sub-Slot instead. Useful for creating
      Slot paths without having to rebuild whenever a value
      along the chain changes.
    */
    function dot(name) {
      return this.SubSlot.create({
        parent: this,
        name:   name
      });
    },

    // TODO: remove when all code ported
    function link(other) {
      console.warn('deprecated use of link(), use linkFrom() instead');
      return this.linkFrom(other);
    },

    /**
      Link two Slots together, setting both to other's value.
      Returns a Detachable which can be used to break the link.
      After copying a value from one slot to the other, this implementation
      then copies the value back in case the target slot rejected the value.
    */
    function linkFrom(s2) {
      var s1        = this;
      var feedback1 = false;
      var feedback2 = false;

      // TODO: once all slot types property set 'src', these
      // two listeneners can be merged.
      var l1 = function(e) {
        if ( feedback1 ) return;

        if ( ! foam.util.is(s1.get(), s2.get()) ) {
          feedback1 = true;
          try {
            s2.set(s1.get());
            if ( ! foam.util.is(s1.get(), s2.get()) )
              s1.set(s2.get());
          } finally {
            feedback1 = false;
          }
        }
      };

      var l2 = function(e) {
        if ( feedback2 ) return;

        if ( ! foam.util.is(s1.get(), s2.get()) ) {
          feedback2 = true;
          try {
            s1.set(s2.get());
            if ( ! foam.util.is(s1.get(), s2.get()) )
              s2.set(s1.get());
          } finally {
            feedback2 = false;
          }
        }
      };

      var sub1 = s1.sub(l1);
      var sub2 = s2.sub(l2);

      l2();

      return {
        detach: function() {
          sub1 && sub1.detach();
          sub2 && sub2.detach();
          sub1 = sub2 = null;
        }
      };
    },

    function linkTo(other) {
      return other.linkFrom(this);
    },

    /**
      Have this Slot dynamically follow other's value.
      Returns a Detachable which can be used to cancel the binding.
    */
    function follow(other) {
      foam.assert(other, 'Slot.follow requires Slot argument.');
      var self = this;
      var l = function() {
        if ( ! foam.util.is(self.get(), other.get()) ) {
          self.set(other.get());
        }
      };
      l();
      return other.sub(l);
    },

    /**
     * Maps values from one model to another.
     * @param f maps values from srcValue to dstValue
     */
    function mapFrom(other, f) {
      var self = this;
      var l = function() { self.set(f(other.get())); };
      l();
      return other.sub(l);
    },

    function mapTo(other, f) {
      return other.mapFrom(this, f);
    },

    function map(f) {
      return foam.core.ExpressionSlot.create({code: f, args: [this]});
    },

    /**
     * Relate to another Slot.
     * @param f maps from this to other
     * @param fprime maps other to this
     */
    function relateTo(other, f, fPrime) {
      var self     = this;
      var feedback = false;
      var sub      = foam.core.FObject.create();
      var l1 = function() {
        if ( feedback ) return;
        feedback = true;
        other.set(f(self.get()));
        feedback = false;
      };
      var l2 = function() {
        if ( feedback ) return;
        feedback = true;
        self.set(fPrime(other.get()));
        feedback = false;
      };

      sub.onDetach(this.sub(l1));
      sub.onDetach(other.sub(l2));

      l1();

      return sub;
    },

    function relateFrom(other, f, fPrime) {
      return other.relateTo(this, fPrime, f);
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'PropertySlot',
  extends: 'foam.core.Slot',

  documentation: `
    Represents object properties as Slots.
    Created with calling obj.prop$ or obj.slot('prop').
    For internal use only.
  `,

  methods: [
    function initArgs() { },
    function init() { },

    function get() {
      return this.prop.get(this.obj);
    },

    function set(value) {
      return this.prop.set(this.obj, value);
    },

    function getPrev() {
      return this.oldValue;
    },

    function setPrev(value) {
      return this.oldValue = value;
    },

    function sub(l) {
      var s = this.obj.sub('propertyChange', this.prop.name, l);
      s.src = this;
      return s;
    },

    function isDefined() {
      return this.obj.hasOwnProperty(this.prop.name);
    },

    function clear() {
      this.obj.clearProperty(this.prop.name);
    },

    function toString() {
      return 'PropertySlot(' + this.obj.$UID + ',' + this.obj.cls_.id + '.' + this.prop.name + ')';
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'SubSlot',
  extends: 'foam.core.Slot',

  documentation:
      'For internal use only. Is used to implement the Slot.dot() method.',

  properties: [
    'parent', // parent slot, not parent object
    'name',
    'value',
    'prevSub'
  ],

  methods: [
    function init() {
      this.parent.sub(this.parentChange);
      this.parentChange();
    },

    function get() {
      var o = this.parent.get();

      return o && o[this.name];
    },

    function set(value) {
      var o = this.parent.get();

      if ( o ) o[this.name] = value;
    },

    /** Needed? **/
    function getPrev() {
      return this.oldValue;
    },

    /** Needed? **/
    function setPrev(value) {
      return this.oldValue = value;
    },

    function sub(l) {
      return this.SUPER('propertyChange', 'value', l);
    },

    function isDefined() {
      return this.parent.get().hasOwnProperty(this.name);
    },

    function clear() {
      this.parent.get().clearProperty(this.name);
    },

    function toString() {
      return 'SubSlot(' + this.parent + ',' + this.name + ')';
    }
  ],

  listeners: [
    function parentChange(s) {
      this.prevSub && this.prevSub.detach();
      var o = this.parent.get();

      // If the new class has the same axiom as the old class, then we keep this
      // SubSlot attached instead of detaching it.
      if ( o && o.cls_.getAxiomByName(this.name) == null ) {
        this.prevSub = null;
        this.detach();
        return;
      }

      this.prevSub = o && o.slot && o.slot(this.name).sub(this.valueChange);
      this.valueChange();
    },

    function valueChange() {
      var parentValue = this.parent.get();
      this.value = parentValue ? parentValue[this.name] : undefined;
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ConstantSlot',

  extends: 'foam.core.Slot',

  documentation: 'An immutable constant valued Slot.',

  properties: [
    {
      name: 'value',
      getter: function() { return this.value_; },
      setter: function() {}
    }
  ],

  methods: [
    function initArgs(args) { this.value_ = args && args.value; },

    function get() { return this.value; },

    function set() {
      throw new Error('Tried to mutate immutable ConstantSlot.');
    },

    function sub(l) { /* nop */ },

    function follow(other) { /* nop */ },

    function linkFrom(s2) { /* nop */ },

    function linkTo(s2) { s2.set(this.get()); },

    function mapFrom(other, f) { /* nop */ }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ExpressionSlot',
  extends: 'foam.core.PromiseSlot',
  documentation: `
    Tracks dependencies for a dynamic function and invalidates if they change.

      foam.CLASS({name: 'Person', properties: ['fname', 'lname']});
      var p = Person.create({fname: 'John', lname: 'Smith'});
      var e = foam.core.ExpressionSlot.create({
        args: [ p.fname$, p.lname$ ],
        code: function(f, l) { return f + ' ' + l; }
      });
      log(e.get());
      e.sub(log);
      p.fname = 'Steve';
      p.lname = 'Jones';
      log(e.get());

      Output:
       > John Smith
       > [object Object] propertyChange value [object Object]
       > [object Object] propertyChange value [object Object]
       > Steve Jones

      var p = foam.CLASS({name: 'Person', properties: [ 'f', 'l' ]}).create({f:'John', l: 'Doe'});
      var e = foam.core.ExpressionSlot.create({
        obj: p,
        code: function(f, l) { return f + ' ' + l; }
      });

  `,

  properties: [
    'obj',
    'code',
    {
      name: 'args',
      expression: function(obj) {
        foam.assert(obj, 'ExpressionSlot: "obj" or "args" required.');

        var args = foam.Function.argNames(this.code);
        for ( var i = 0 ; i < args.length ; i++ ) {
          args[i] = obj.slot(args[i]);
        }

        // this.invalidate(); // ???: Is this needed?
        this.subToArgs_(args);

        return args;
      },
      postSet: function(_, args) {
        this.subToArgs_(args);
      }
    },
    {
      name: 'value',
      preSet: function(o, n) {
        if ( n && n.then ) {
          this.promise = n;
          n = foam.Undefined.isInstance(o) ? null : o;
        } else {
          // Ensure an old promise doesn't fire and clobber the value.
          this.promise = null;
        }
        return n;
      },
      factory: function() {
        return this.code.apply(this.obj || this, this.args.map(function(a) {
          return a.get();
        }));
      }
    },
    'cleanup_', // detachable to cleanup old subs when obj changes
  ],

  methods: [
    function init() { this.onDetach(this.cleanup); },
    function set() { /* nop */ },
    function subToArgs_(args) {
      this.cleanup();

      var cleanup = foam.core.FObject.create();

      for ( var i = 0 ; i < args.length ; i++ ) {
        cleanup.onDetach(args[i].sub(this.invalidate));
      }

      this.cleanup_ = cleanup;
    }
  ],

  listeners: [
    function cleanup() { this.cleanup_ && this.cleanup_.detach(); },
    function invalidate() { this.clearProperty('value'); }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PromiseSlot',
  extends: 'foam.core.SimpleSlot',

  documentation: `
    A slot that takes a promise and sets its value to its value when it
    resolves.
  `,

  properties: [
    {
      name: 'promise',
      postSet: function(_, n) {
        n && n.then(function(v) {
          if ( n === this.promise ) this.value = v;
        }.bind(this));
      }
    }
  ],

  methods: [
    function set() {
      throw new Error(this.cls_.id + ' does not support setting.');
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ArraySlot',

  extends: 'foam.core.Slot',

  documentation: `
    A slot that takes an array of Slots and notifies when either changes.
  `,

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.Slot',
      name: 'slots'
    }
  ],

  methods: [
    function get() {
      return this.slots.map(s => s.get());
    },

    function set(arr) {
      if ( ! foam.Array.isInstance(arr) )
        throw new Error('ArraySlot can only set an array.');
      arr.forEach((v, i) => this.slots[i].set(v));
    },

    function sub(l) {
      if ( arguments.length != 1 ) return this.SUPER.apply(this, arguments);
      var subs = this.slots.map(s => s.sub(l));
      return {
        detach: function() { subs.forEach(s => s.detach()); }
      };
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'SimpleSlot',
  extends: 'foam.core.Slot',

  properties: [
    {
      name: 'value'
    }
  ],

  methods: [
    function get() { return this.value; },
    function set(v) { this.value = v; },
    function sub(l) {
      return arguments.length === 1 ?
        this.SUPER('propertyChange', 'value', l) :
        this.SUPER.apply(this, arguments);
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ProxySlot',
  extends: 'foam.core.SimpleSlot',
  properties: [
    {
      name: 'sub_'
    },
    {
      name: 'delegate',
      postSet: function(_, n) {
        if ( this.sub_ ) this.sub_.detach();

        if ( foam.core.Slot.isInstance(n) ) {
          this.sub_ = this.linkFrom(n);
        }
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ProxyExpressionSlot',
  extends: 'foam.core.ProxySlot',

  requires: [
    'foam.core.ExpressionSlot'
  ],

  documentation: "An expression slot which supports 'obj' changing.",

  properties: [
    {
      name: 'obj',
      postSet: function () {
        this.update();
      }
    },
    {
      name: 'code',
      postSet: function () {
        this.update();
      }
    },
    {
      name: 'args',
      postSet: function () {
        this.update();
      }
    }
  ],

  methods: [
    function update() {
      if ( ! this.code || ! this.obj ) return;

      this.delegate = this.ExpressionSlot.create({
        args: this.args,
        code: this.code,
        obj:  this.obj
      })
    }
  ]
});
<|endoftext|>


<commit_before>// module generate object code from ast
var Interpreter = function() {
  this.name = 'Interpreter';
  this.objects = [];
  this.define = [];
  this.rules = {};
  this.computed = [];
  this.maxdepth = 1000;
  this.depth = 0;
  this.maxobjects = 1000;
  this.objectnum = 0;
  this.minsize = .2;
  this.maxsize = 1.0;
  this.seed = 'initial'; // integer or 'initial'
  this.stack = [];
  this.curr = {};
  this.curr.matrix = new Matrix4();
  this.curr.hex = Color('#ff0000');
  this.curr.hsv = _.extend(Color({ hue: 0, saturation: 1, value: 1 }), { computed: false });
  this.curr.blend = { color: null, strength: 0, computed: false };
  this.curr.alpha = 1;
  this.mt = new MersenneTwister();
}

// termination criteria
Interpreter.prototype.terminated = function() {
  if (this.objectnum > this.maxobjects) return true;
  if (this.depth > this.maxdepth) return true;
  return false;
}

// stack current transformation state
Interpreter.prototype.pushState = function() {
  this.depth++;
  this.stack.push({
    matrix: this.curr.matrix.clone(),
    hex: this.curr.hex.clone(),
    hsv: this.curr.hsv.clone(),
    blend: _.extend({}, this.curr.blend),
    alpha: this.curr.alpha
  });
  return this;
}

// pull the parent transformation state
Interpreter.prototype.popState = function() {
  if (this.stack.length > 0) {
    this.curr = this.stack.pop();
    this.depth--;
  }
  return this;
}

Interpreter.prototype.translate = function(x, y, z) {
  this.curr.matrix.translate({
    x: x,
    y: y,
    z: z
  });
  return this;
}

Interpreter.prototype.rotateX = function(angle) {
  this.curr.matrix.rotateX(angle);
  return this;
}

Interpreter.prototype.rotateY = function(angle) {
  this.curr.matrix.rotateY(angle);
  return this;
}

Interpreter.prototype.rotateZ = function(angle) {
  this.curr.matrix.rotateZ(angle);
  return this;
}

Interpreter.prototype.scale = function(x, y, z) {
  this.curr.matrix.scale({
    x: x,
    y: y,
    z: z
  });
  return this;
}

// make 3x3 rotation matrix to 4x4 matrix
// test: { m 1 0 0 0 .53 -.85 0 .85 .53 } box
Interpreter.prototype.matrix = function(v) {
  this.curr.matrix.set(
    v[0], v[1], v[2], 0,
    v[3], v[4], v[5], 0,
    v[6], v[7], v[8], 0,
       0,    0,    0, 1
  );
  return this;
}

Interpreter.prototype.random16 = function() {
  var rand = this.mt.next() * 0xffffff;
  return Math.floor(rand).toString(16);
}

Interpreter.prototype.randomColor = function() {
  return sprintf('#%s', this.random16());
}

Interpreter.prototype.setColor = function(color) {
  if (color === 'random') color = this.randomColor();
  this.curr.hex = Color(color);
  return this;
}

Interpreter.prototype.setHue = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.hue += v % 360;
  return this;
}

Interpreter.prototype.setSaturation = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.saturation = clamp(this.curr.hsv.saturation * v, 0, 1);
  return this;
}

Interpreter.prototype.setBrightness = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.value = clamp(this.curr.hsv.value * v, 0, 1);
  return this;
}

Interpreter.prototype.setBlend = function(color, strength) {
  this.curr.blend.computed = true;
  this.curr.blend.color = color;
  this.curr.blend.strength = this.curr.blend.strength + clamp(strength, 0, 1);
  return this;
}

// execute eisenscript
Interpreter.prototype.generate = function(ast) {
  // rewriting ast
  var that = this;
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Rule: that.rewriteRule(statement); break;
    }
  });

  // pull the defines
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Define: that.define.push(statement); break;
      case Symbol.Set: that.define.push(statement); break;
      case Symbol.Statement: if (statement.computed) that.computed.push(statement); break;
    }
  });

  // creating intermediate code...
  // promise
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        switch (statement.key) {
          case Symbol.Maxdepth: that.maxdepth = statement.value; break;
          case Symbol.Maxobjects: that.maxobjects = statement.value; break;
          case Symbol.Minsize: that.minsize = statement.value; break;
          case Symbol.Maxsize: that.maxsize = statement.value; break;
          case Symbol.Seed: that.seed = statement.value; break;
        }
        break;
      case Symbol.Define:
        // not implemented, but I don't think the definition statement is need.
        break;
    }
  });

  // initial value is randomised chosen integer
  this.mt.setSeed(this.seed === 'initial' ? randInt(0, 65535) : this.seed);

  // pull the statement of system environment
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        if (statement.key === Symbol.Background) that.generateBackground(statement);
        break;
    }
  });

  // execute main
  this.parseStatements(this.computed);

  // return the intermediate code
  return {
    maxdepth: this.maxdepth,
    maxobjects: this.maxobjects,
    minsize: this.minsize,
    maxsize: this.maxsize,
    seed: this.seed,
    objects: this.objects,
    num: this.objects.length
  }
}

// rewrite subtree related to rule statement
Interpreter.prototype.rewriteRule = function(rule) {
  rule.params.forEach(function(param) {
    if (param.type === Symbol.Modifier) {
      switch (param.key) {
        case Symbol.Weight: rule.weight = param.value; break;
        case Symbol.Maxdepth: rule.maxdepth = param.value; rule.alternate = param.alternate; break;
      }
    }
  });
  if (!this.rules[rule.id]) this.rules[rule.id] = [];
  this.rules[rule.id].push(rule);
  return this;
}

// execute statements
Interpreter.prototype.parseStatements = function(statements) {
  var i = 0, len = statements.length;
  while (i < len) {
    if (this.terminated()) break;
    this.parseStatement(statements[i], 0);
    i++;
  }
  return this;
}

// execute a statement
Interpreter.prototype.parseStatement = function(statement, index) {
  // parse transformation expression
  var expr = statement.exprs[index];
  if (expr) {
    this.pushState();
    for (var i = 0; i < expr.left; i++) {
      if (this.terminated()) break;
      this.parseTransformStatement(expr.right);
      // if statement.exprs[index + 1] is undefined, it would break the transformation loops.
      this.parseStatement(statement, index + 1);
    }
    this.popState();
    return this;
  }

  // if not primitive, call rule and parse next transformation loops
  if (_.values(Primitive).indexOf(statement.id) === -1) {
    this.rules[statement.id].depth = (this.rules[statement.id].depth || 0) + 1;
    var rule = this.sampling(statement.id);
    if (rule) this.parseStatements(rule.body);
    this.rules[statement.id].depth--;
    return this;
  }

  // achieve the end of nested transformation loops
  this.generatePrimitive(statement);
  return this;
}

// break down transformation set
Interpreter.prototype.parseTransformStatement = function(transform) {
  var i = 0, len = transform.properties.length;
  while (i < len) {
    this.parseTransform(transform.properties[i]);
    i++;
  }
  return this;
}

// parse transformation property
Interpreter.prototype.parseTransform = function(property) {
  var v = property.value;
  switch (property.key) {
    case Symbol.XShift: this.translate(v, 0, 0); break;
    case Symbol.YShift: this.translate(0, v, 0); break;
    case Symbol.ZShift: this.translate(0, 0, v); break;
    case Symbol.RotateX: this.rotateX(degToRad(v)); break;
    case Symbol.RotateY: this.rotateY(degToRad(v)); break;
    case Symbol.RotateZ: this.rotateZ(degToRad(v)); break;
    case Symbol.Size: this.scale(v.x, v.y, v.z); break;
    case Symbol.Matrix: this.matrix(v); break;
    case Symbol.Color: this.setColor(v); break;
    case Symbol.Hue: this.setHue(v); break;
    case Symbol.Saturation: this.setSaturation(v); break;
    case Symbol.Brightness: this.setBrightness(v); break;
    case Symbol.Blend: this.setBlend(property.color, property.strength); break;
    case Symbol.Alpha: this.curr.alpha *= v; break;
  }
  return this;
}

// create primitive object and stack it as intermediate code for renderer
Interpreter.prototype.generatePrimitive = function(statement) {
  // if achieved maxobjects
  this.objectnum++;
  if (this.terminated()) return;

  // blend the current color with the specified color
  if (this.curr.blend.computed) {
    this.curr.hex = this.curr.hex.toHSV();
    var blend = Color(this.curr.blend.color).toHSV();
    this.curr.hex.hue += (blend.hue - this.curr.hex.hue) * this.curr.blend.strength / 6;
    this.curr.hex.hue %= 360;
  }

  // primitive object
  this.objects.push({
    type: Type.Primitive,
    name: statement.id,
    matrix: this.curr.matrix.clone(),
    color: this.curr.hsv.computed ? this.curr.hex.blend(this.curr.hsv, 1).toCSS() : this.curr.hex.toCSS(),
    opacity: this.curr.alpha,
    depth: this.depth
  });
}

// create background object code and stack it as intermediate code for renderer
Interpreter.prototype.generateBackground = function(statement) {
  this.objects.push({
    type: Type.Background,
    color: statement.value
  });
}

// randomly choose one of the rules according to their weights
Interpreter.prototype.sampling = function(name, retry) {
  if (!this.rules[name]) {
    throw new Error(
      sprintf("ReferenceError: '%s' is not defined. As reported by eisenscript interpreter.", name),
      sprintf("%s.js", this.name)
    );
  }

  // sum weights of each rules
  var sum = 0;
  this.rules[name].forEach(function(rule) {
    rule.weight = rule.weight || 1;
    sum += rule.weight;
  });

  // choosing...
  var rand = this.mt.next() * sum;
  var chosen;
  for (var i = 0; i < this.rules[name].length; i++) {
    var rule = this.rules[name][i];
    if (rule.weight - rand < 0) {
      rand -= rule.weight
      continue;
    }
    chosen = rule;
    break;
  }

  // if rule could not be selected, interpreter tries to choose until 3 times
  if (!chosen) {
    retry = retry || 0;
    if (retry < 3) return this.sampling(name, ++retry);
    // if achieve max retry count
    return false;
  }

  // if achieved maxdepth
  if (chosen.maxdepth && chosen.maxdepth < this.rules[name].depth) {
    if (this.rules[name].depth >= chosen.maxdepth) return false;
    if (chosen.alternate) return this.sampling(chosen.alternate);
    if (this.depth < chosen.maxdepth) return chosen;
    return false;
  }

  // the rule randomly chosen
  return chosen;
}
<commit_msg>fix bug related to executing alternative rule<commit_after>// module generate object code from ast
var Interpreter = function() {
  this.name = 'Interpreter';
  this.objects = [];
  this.define = [];
  this.rules = {};
  this.computed = [];
  this.maxdepth = 1000;
  this.depth = 0;
  this.maxobjects = 1000;
  this.objectnum = 0;
  this.minsize = .2;
  this.maxsize = 1.0;
  this.seed = 'initial'; // integer or 'initial'
  this.stack = [];
  this.curr = {};
  this.curr.matrix = new Matrix4();
  this.curr.hex = Color('#ff0000');
  this.curr.hsv = _.extend(Color({ hue: 0, saturation: 1, value: 1 }), { computed: false });
  this.curr.blend = { color: null, strength: 0, computed: false };
  this.curr.alpha = 1;
  this.mt = new MersenneTwister();
}

// termination criteria
Interpreter.prototype.terminated = function() {
  if (this.objectnum > this.maxobjects) return true;
  if (this.depth > this.maxdepth) return true;
  return false;
}

// stack current transformation state
Interpreter.prototype.pushState = function() {
  this.depth++;
  this.stack.push({
    matrix: this.curr.matrix.clone(),
    hex: this.curr.hex.clone(),
    hsv: this.curr.hsv.clone(),
    blend: _.extend({}, this.curr.blend),
    alpha: this.curr.alpha
  });
  return this;
}

// pull the parent transformation state
Interpreter.prototype.popState = function() {
  if (this.stack.length > 0) {
    this.curr = this.stack.pop();
    this.depth--;
  }
  return this;
}

Interpreter.prototype.translate = function(x, y, z) {
  this.curr.matrix.translate({
    x: x,
    y: y,
    z: z
  });
  return this;
}

Interpreter.prototype.rotateX = function(angle) {
  this.curr.matrix.rotateX(angle);
  return this;
}

Interpreter.prototype.rotateY = function(angle) {
  this.curr.matrix.rotateY(angle);
  return this;
}

Interpreter.prototype.rotateZ = function(angle) {
  this.curr.matrix.rotateZ(angle);
  return this;
}

Interpreter.prototype.scale = function(x, y, z) {
  this.curr.matrix.scale({
    x: x,
    y: y,
    z: z
  });
  return this;
}

// make 3x3 rotation matrix to 4x4 matrix
// test: { m 1 0 0 0 .53 -.85 0 .85 .53 } box
Interpreter.prototype.matrix = function(v) {
  this.curr.matrix.set(
    v[0], v[1], v[2], 0,
    v[3], v[4], v[5], 0,
    v[6], v[7], v[8], 0,
       0,    0,    0, 1
  );
  return this;
}

Interpreter.prototype.random16 = function() {
  var rand = this.mt.next() * 0xffffff;
  return Math.floor(rand).toString(16);
}

Interpreter.prototype.randomColor = function() {
  return sprintf('#%s', this.random16());
}

Interpreter.prototype.setColor = function(color) {
  if (color === 'random') color = this.randomColor();
  this.curr.hex = Color(color);
  return this;
}

Interpreter.prototype.setHue = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.hue += v % 360;
  return this;
}

Interpreter.prototype.setSaturation = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.saturation = clamp(this.curr.hsv.saturation * v, 0, 1);
  return this;
}

Interpreter.prototype.setBrightness = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.value = clamp(this.curr.hsv.value * v, 0, 1);
  return this;
}

Interpreter.prototype.setBlend = function(color, strength) {
  this.curr.blend.computed = true;
  this.curr.blend.color = color;
  this.curr.blend.strength = this.curr.blend.strength + clamp(strength, 0, 1);
  return this;
}

// execute eisenscript
Interpreter.prototype.generate = function(ast) {
  // rewriting ast
  var that = this;
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Rule: that.rewriteRule(statement); break;
    }
  });

  // pull the defines
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Define: that.define.push(statement); break;
      case Symbol.Set: that.define.push(statement); break;
      case Symbol.Statement: if (statement.computed) that.computed.push(statement); break;
    }
  });

  // creating intermediate code...
  // promise
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        switch (statement.key) {
          case Symbol.Maxdepth: that.maxdepth = statement.value; break;
          case Symbol.Maxobjects: that.maxobjects = statement.value; break;
          case Symbol.Minsize: that.minsize = statement.value; break;
          case Symbol.Maxsize: that.maxsize = statement.value; break;
          case Symbol.Seed: that.seed = statement.value; break;
        }
        break;
      case Symbol.Define:
        // not implemented, but I don't think the definition statement is need.
        break;
    }
  });

  // initial value is randomised chosen integer
  this.mt.setSeed(this.seed === 'initial' ? randInt(0, 65535) : this.seed);

  // pull the statement of system environment
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        if (statement.key === Symbol.Background) that.generateBackground(statement);
        break;
    }
  });

  // execute main
  this.parseStatements(this.computed);

  // return the intermediate code
  return {
    maxdepth: this.maxdepth,
    maxobjects: this.maxobjects,
    minsize: this.minsize,
    maxsize: this.maxsize,
    seed: this.seed,
    objects: this.objects,
    num: this.objects.length
  }
}

// rewrite subtree related to rule statement
Interpreter.prototype.rewriteRule = function(rule) {
  rule.params.forEach(function(param) {
    if (param.type === Symbol.Modifier) {
      switch (param.key) {
        case Symbol.Weight: rule.weight = param.value; break;
        case Symbol.Maxdepth: rule.maxdepth = param.value; rule.alternate = param.alternate; break;
      }
    }
  });
  if (!this.rules[rule.id]) this.rules[rule.id] = [];
  this.rules[rule.id].push(rule);
  return this;
}

// execute statements
Interpreter.prototype.parseStatements = function(statements) {
  var i = 0, len = statements.length;
  while (i < len) {
    if (this.terminated()) break;
    this.parseStatement(statements[i], 0);
    i++;
  }
  return this;
}

// execute a statement
Interpreter.prototype.parseStatement = function(statement, index) {
  // parse transformation expression
  var expr = statement.exprs[index];
  if (expr) {
    this.pushState();
    for (var i = 0; i < expr.left; i++) {
      if (this.terminated()) break;
      this.parseTransformStatement(expr.right);
      // if statement.exprs[index + 1] is undefined, it would break the transformation loops.
      this.parseStatement(statement, index + 1);
    }
    this.popState();
    return this;
  }

  // if not primitive, call rule and parse next transformation loops
  if (_.values(Primitive).indexOf(statement.id) === -1) {
    this.rules[statement.id].depth = (this.rules[statement.id].depth || 0) + 1;
    var rule = this.sampling(statement.id);
    if (rule) this.parseStatements(rule.body);
    this.rules[statement.id].depth--;
    return this;
  }

  // achieve the end of nested transformation loops
  this.generatePrimitive(statement);
  return this;
}

// break down transformation set
Interpreter.prototype.parseTransformStatement = function(transform) {
  var i = 0, len = transform.properties.length;
  while (i < len) {
    this.parseTransform(transform.properties[i]);
    i++;
  }
  return this;
}

// parse transformation property
Interpreter.prototype.parseTransform = function(property) {
  var v = property.value;
  switch (property.key) {
    case Symbol.XShift: this.translate(v, 0, 0); break;
    case Symbol.YShift: this.translate(0, v, 0); break;
    case Symbol.ZShift: this.translate(0, 0, v); break;
    case Symbol.RotateX: this.rotateX(degToRad(v)); break;
    case Symbol.RotateY: this.rotateY(degToRad(v)); break;
    case Symbol.RotateZ: this.rotateZ(degToRad(v)); break;
    case Symbol.Size: this.scale(v.x, v.y, v.z); break;
    case Symbol.Matrix: this.matrix(v); break;
    case Symbol.Color: this.setColor(v); break;
    case Symbol.Hue: this.setHue(v); break;
    case Symbol.Saturation: this.setSaturation(v); break;
    case Symbol.Brightness: this.setBrightness(v); break;
    case Symbol.Blend: this.setBlend(property.color, property.strength); break;
    case Symbol.Alpha: this.curr.alpha *= v; break;
  }
  return this;
}

// create primitive object and stack it as intermediate code for renderer
Interpreter.prototype.generatePrimitive = function(statement) {
  // if achieved maxobjects
  this.objectnum++;
  if (this.terminated()) return;

  // blend the current color with the specified color
  if (this.curr.blend.computed) {
    this.curr.hex = this.curr.hex.toHSV();
    var blend = Color(this.curr.blend.color).toHSV();
    this.curr.hex.hue += (blend.hue - this.curr.hex.hue) * this.curr.blend.strength / 6;
    this.curr.hex.hue %= 360;
  }

  // primitive object
  this.objects.push({
    type: Type.Primitive,
    name: statement.id,
    matrix: this.curr.matrix.clone(),
    color: this.curr.hsv.computed ? this.curr.hex.blend(this.curr.hsv, 1).toCSS() : this.curr.hex.toCSS(),
    opacity: this.curr.alpha,
    depth: this.depth
  });
}

// create background object code and stack it as intermediate code for renderer
Interpreter.prototype.generateBackground = function(statement) {
  this.objects.push({
    type: Type.Background,
    color: statement.value
  });
}

// randomly choose one of the rules according to their weights
Interpreter.prototype.sampling = function(name, retry) {
  if (!this.rules[name]) {
    throw new Error(
      sprintf("ReferenceError: '%s' is not defined. As reported by eisenscript interpreter.", name),
      sprintf("%s.js", this.name)
    );
  }

  // sum weights of each rules
  var sum = 0;
  this.rules[name].forEach(function(rule) {
    rule.weight = rule.weight || 1;
    sum += rule.weight;
  });

  // choosing...
  var rand = this.mt.next() * sum;
  var chosen;
  for (var i = 0; i < this.rules[name].length; i++) {
    var rule = this.rules[name][i];
    if (rule.weight - rand < 0) {
      rand -= rule.weight
      continue;
    }
    chosen = rule;
    break;
  }

  // if rule could not be selected, interpreter tries to choose until 3 times
  if (!chosen) {
    retry = retry || 0;
    if (retry < 3) return this.sampling(name, ++retry);
    // if achieve max retry count
    return false;
  }

  // if achieved maxdepth
  if (chosen.maxdepth && chosen.maxdepth < this.rules[name].depth) {
    if (chosen.alternate) return this.sampling(chosen.alternate);
    if (this.rules[name].depth >= chosen.maxdepth) return false;
    if (this.depth < chosen.maxdepth) return chosen;
    return false;
  }

  // the rule randomly chosen
  return chosen;
}
<|endoftext|>


<commit_before>/******************************************
 * Form Handlers and page                 *
 * generators for TSSAA WebApp Fat Client *
 *****************************************/

$(document).on("pageinit", function(e, obj) {
    /**
     * Login form handler
     */
    $("#login_page").on("submit", function(e, obj) {

        var formData = $("#login_form").serialize();

        $.ajax( "app_dev.php/login", {
            type: "POST",
            data: formData,
            contentType: 'application/x-www-form-urlencoded',
            success: loginSuccess
        });
        return false;
    });

    /**
     * Add school form ajax handler
     */
    $('#add_school_submit').on('click', function(e, obj) {

        var  formData = $('#add_school_form').serialize();

        $.ajax("app_dev.php/add_school", {
            type: "POST",
            cache: false,
            data: formData,
            success: popDBDialog
        });
        return false;
    });

});

$(document).on("pagebeforechange", function(e, obj) {
    /**
     * Get school list ajax handler
     */
    var url= $.mobile.path.parseUrl(obj.toPage);

    if ( url.hash  === "#school_list_page") {
        $.ajax("app_dev.php/get_school_list", {
            type: "GET",
            cache: "true",
            success: generateSchoolList
        });

        e.preventDefault();
    };
});

/**
 * Login form ajax callback
 * TODO session management handled here?
 */
function loginSuccess(response) {
    $.mobile.changePage($("#options_page"), {
            transition: "slideup",
            reverse: true,
        });
}

/**
 * DB Dialog ajax success callback
 */
function popDBDialog(response) {
    try {
        var data = $.parseJSON(response);
    } catch(err) {
        alert(err);
    }

    var response_items = [];

    $.each(data, function(key, val) {
            response_items.push('
'+key+':	'+val+'
');
    });

    $('
',{
			'class': 'response_table',
			html: response_items.join('')
		}).replaceAll("#response");

    $.mobile.changePage("#db_confirm_dialog");
}

/**
 * School List response handler
 * generates ul of schools returned from DB
 */
function generateSchoolList(response) {
    var school_data;
    try {
        school_data = $.parseJSON(response);
    } catch(err) {
        alert(err);
    }

    var schools_html = [];

    $.each(school_data, function() {
                var button = 'Edit This School';

                var block = '

' +
                    this.name + '

Phone: ' +
                    this.phone + '

Address: ' +
                    this.address + '
' + button + '
';
                schools_html.push(block);
            });

    $('div#school_list').html(schools_html.join(''));

    $('div#school_list').on('click', 'a.edit_school', function (e) {
                var index = $(this).attr("ks_school_id");
                var school = school_data[index];

                console.log(index);
                console.dir(school);

                editSchoolEntity(school);
            });

    $.mobile.changePage($("#school_list_page"));
};

function editSchoolEntity(school) {
    console.dir(school);
    console.log(school.name);
    console.log("oh hai");

    $("h2#school_object").html(school.name);

    $.mobile.changePage("#edit_school_dialog", {
            transistion: "pop"
            });
}
<commit_msg>log this.id val in generate school list<commit_after>/******************************************
 * Form Handlers and page                 *
 * generators for TSSAA WebApp Fat Client *
 *****************************************/

$(document).on("pageinit", function(e, obj) {
    /**
     * Login form handler
     */
    $("#login_page").on("submit", function(e, obj) {

        var formData = $("#login_form").serialize();

        $.ajax( "app_dev.php/login", {
            type: "POST",
            data: formData,
            contentType: 'application/x-www-form-urlencoded',
            success: loginSuccess
        });
        return false;
    });

    /**
     * Add school form ajax handler
     */
    $('#add_school_submit').on('click', function(e, obj) {

        var  formData = $('#add_school_form').serialize();

        $.ajax("app_dev.php/add_school", {
            type: "POST",
            cache: false,
            data: formData,
            success: popDBDialog
        });
        return false;
    });

});

$(document).on("pagebeforechange", function(e, obj) {
    /**
     * Get school list ajax handler
     */
    var url= $.mobile.path.parseUrl(obj.toPage);

    if ( url.hash  === "#school_list_page") {
        $.ajax("app_dev.php/get_school_list", {
            type: "GET",
            cache: "true",
            success: generateSchoolList
        });

        e.preventDefault();
    };
});

/**
 * Login form ajax callback
 * TODO session management handled here?
 */
function loginSuccess(response) {
    $.mobile.changePage($("#options_page"), {
            transition: "slideup",
            reverse: true,
        });
}

/**
 * DB Dialog ajax success callback
 */
function popDBDialog(response) {
    try {
        var data = $.parseJSON(response);
    } catch(err) {
        alert(err);
    }

    var response_items = [];

    $.each(data, function(key, val) {
            response_items.push('
'+key+':	'+val+'
');
    });

    $('
',{
			'class': 'response_table',
			html: response_items.join('')
		}).replaceAll("#response");

    $.mobile.changePage("#db_confirm_dialog");
}

/**
 * School List response handler
 * generates ul of schools returned from DB
 */
function generateSchoolList(response) {
    var school_data;
    try {
        school_data = $.parseJSON(response);
    } catch(err) {
        alert(err);
    }

    var schools_html = [];

    $.each(school_data, function() {
                var button = 'Edit This School';

                var block = '

' +
                    this.name + '

Phone: ' +
                    this.phone + '

Address: ' +
                    this.address + '
' + button + '
';
                schools_html.push(block);
            });

    $('div#school_list').html(schools_html.join(''));

    $('div#school_list').on('click', 'a.edit_school', function (e) {
                var index = $(this).attr("ks_school_id");
                var school = school_data[index];

                console.log(index);
                console.dir(school);

                editSchoolEntity(school);
            });

    $.mobile.changePage($("#school_list_page"));
};

function editSchoolEntity(school) {
    console.dir(school);
    console.log(school.name);
    console.log("oh hai");

    $("h2#school_object").html(school.name);

    $.mobile.changePage("#edit_school_dialog", {
            transistion: "pop"
            });
}
<|endoftext|>