<commit_before>function countUniqueAttributes(items, attr) {
    //var counter = items.reduce(function(acc, item) { acc[item[categoryAttr]] = acc[item[categoryAttr]] ? acc[item[categoryAttr]] + 1 : 1; return acc }, {});
    var counter = {};

    for(var i in items) {
        var item = items[i];

        var attrValue = item[attr];

        if(counter[attrValue]) {
            counter[attrValue] += 1;
        } else {
            counter[attrValue] = 1;
        }
    }

    return counter;
}

function entropy(items, attr) {
    var counter = countUniqueAttributes(items, attr);

    var entropy = 0;
    for(var i in counter) {
        var p = counter[i] / items.length;
        entropy += -p * Math.log(p);
    }

    return entropy;
}

var predicates = {
                    '==' : function(a, b) {return a == b},
                    '>=' : function(a, b) {return a >= b},
                    '<=' : function(a, b) {return a <= b}
                 };

function split(items, attr, predicateName, pivot) {
    var result = {
                    attribute : attr,
                    predicate : predicateName,
                    pivot     : pivot,
                    match     : [],
                    notMatch  : []
                 };

    var predicate = predicates[predicateName];

    for(var i in items) {
        var item = items[i];

        var attrValue = item[attr];

        if((attrValue != null) && predicate(attrValue, pivot)) {
            result.match.push(item);
        } else {
            result.notMatch.push(item);
        }
    };

    return result;
}

function mostFrequentCategory(items, attr) {
    var counter = countUniqueAttributes(items, attr);

    var mostFrequentCount;
    var mostFrequentCategory;

    for(var c in counter) {
        if(!mostFrequentCategory || (counter[c] > mostFrequentCount)) {

            mostFrequentCount = counter[c];
            mostFrequentCategory = c;
        }
    };

    return mostFrequentCategory;
}

var ENTROPY_THRESHOLD = 0.001;

function buildTree(items, threshold, categoryAttr) {
    if(!categoryAttr) {
        categoryAttr = 'category';
    }
    if(!threshold) {
        threshold = 1;
    }

    var initialEntropy = entropy(items, categoryAttr);

    if((initialEntropy < ENTROPY_THRESHOLD) || (items.length <= threshold)) {
        return {category : mostFrequentCategory(items, categoryAttr)}
    };

    var bestSplit;

    for(var i in items) {
        var item = items[i];

        for(var attr in item) {
            if(attr == categoryAttr) {
                continue;
            }

            for(var predicate in predicates) {

                var currSplit = split(items, attr, predicate, item[attr]);

                var matchEntropy = entropy(currSplit.match, categoryAttr);
                var notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);

                currSplit.gain = initialEntropy - (matchEntropy * currSplit.match.length + notMatchEntropy * currSplit.notMatch.length) / items.length;

                if(!bestSplit || (currSplit.gain > 0 && bestSplit.gain < currSplit.gain)) {
                    bestSplit = currSplit;
                }
            }
        }
    }
    return {attribute : bestSplit.attribute,
            predicate : bestSplit.predicate,
            pivot     : bestSplit.pivot,
            match     : buildTree(bestSplit.match, threshold, categoryAttr),
            notMatch  : buildTree(bestSplit.notMatch, threshold, categoryAttr)};
}

function predict(tree, item) {
    if(tree.category) {
        return tree.category;
    }

    var attrName = tree.attribute;
    var attrValue = item[attrName];

    var predicateName = tree.predicate;
    var predicate = predicates[predicateName];

    var pivot =  tree.pivot;

    if((attrValue != null) && predicate(attrValue, pivot)) {
        return predict(tree.match, item);
    } else {
        return predict(tree.notMatch, item);
    }
}

/*
var points = []
for(var i = 0; i < 150; i++) {
    var x = (Math.random() - Math.random()) * 10;
    var y = (Math.random() - Math.random()) * 10;
    var pt = {x : x, y : y}
    if(x * x + y * y < 5 * 5) pt.category = 'in_circle'; else pt.category = 'out_of_circle';
    points.push(pt)
}
var tree = buildTree(points, 5);
alert(JSON.stringify(tree, null, 4))
var pt = {x : 0, y : 0};
pt.prediction = predict(tree, pt);
alert(JSON.stringify(pt, null, 4))
pt = {x : 7, y : 7};
pt.prediction = predict(tree, pt);
alert(JSON.stringify(pt, null, 4))

var stat = {correct : 0, incorrect : 0}
for(var i = 0; i < 1000; i++) {
    var x = (Math.random() - Math.random()) * 10;
    var y = (Math.random() - Math.random()) * 10;
    var pt = {x : x, y : y}
    pt.prediction = predict(tree, pt);
    if(x * x + y * y < 5 * 5) pt.c = 'in_circle'; else pt.c = 'out_of_circle';
    if(pt.c == pt.prediction) stat.correct++; else stat.incorrect++;
}
alert(JSON.stringify(stat, null, 4))
*/<commit_msg>Refactoring<commit_after>function countUniqueAttributes(items, attr) {
    //var counter = items.reduce(function(acc, item) { acc[item[categoryAttr]] = acc[item[categoryAttr]] ? acc[item[categoryAttr]] + 1 : 1; return acc }, {});
    var counter = {};

    for(var i in items) {
        var item = items[i];

        var attrValue = item[attr];

        if(counter[attrValue]) {
            counter[attrValue] += 1;
        } else {
            counter[attrValue] = 1;
        }
    }

    return counter;
}

function entropy(items, attr) {
    var counter = countUniqueAttributes(items, attr);

    var entropy = 0;
    for(var i in counter) {
        var p = counter[i] / items.length;
        entropy += -p * Math.log(p);
    }

    return entropy;
}

var predicates = {
                    '==' : function(a, b) {return a == b},
                    '>=' : function(a, b) {return a >= b},
                    '<=' : function(a, b) {return a <= b}
                 };

function split(items, attr, predicateName, pivot) {
    var result = {
                    attribute : attr,
                    predicate : predicateName,
                    pivot     : pivot,
                    match     : [],
                    notMatch  : []
                 };

    var predicate = predicates[predicateName];

    for(var i in items) {
        var item = items[i];

        var attrValue = item[attr];

        if((attrValue != null) && predicate(attrValue, pivot)) {
            result.match.push(item);
        } else {
            result.notMatch.push(item);
        }
    };

    return result;
}

function mostFrequentCategory(items, attr) {
    var counter = countUniqueAttributes(items, attr);

    var mostFrequentCount;
    var mostFrequentCategory;

    for(var c in counter) {
        if(!mostFrequentCategory || (counter[c] > mostFrequentCount)) {

            mostFrequentCount = counter[c];
            mostFrequentCategory = c;
        }
    };

    return mostFrequentCategory;
}

var ENTROPY_THRESHOLD = 0.001;

function buildTree(items, threshold, categoryAttr) {
    if(!categoryAttr) {
        categoryAttr = 'category';
    }
    if(!threshold) {
        threshold = 1;
    }

    var initialEntropy = entropy(items, categoryAttr);

    if((initialEntropy < ENTROPY_THRESHOLD) || (items.length <= threshold)) {
        return {category : mostFrequentCategory(items, categoryAttr)}
    };

    var bestSplit;

    for(var i in items) {
        var item = items[i];

        for(var attr in item) {
            if(attr == categoryAttr) {
                continue;
            }

            for(var predicate in predicates) {

                var currSplit = split(items, attr, predicate, item[attr]);

                var matchEntropy = entropy(currSplit.match, categoryAttr);
                var notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);

                var newEntropy = (matchEntropy * currSplit.match.length + notMatchEntropy * currSplit.notMatch.length) / items.length;

                currSplit.gain = initialEntropy - newEntropy;

                if(!bestSplit || (currSplit.gain > 0 && bestSplit.gain < currSplit.gain)) {
                    bestSplit = currSplit;
                }
            }
        }
    }
    return {attribute : bestSplit.attribute,
            predicate : bestSplit.predicate,
            pivot     : bestSplit.pivot,
            match     : buildTree(bestSplit.match, threshold, categoryAttr),
            notMatch  : buildTree(bestSplit.notMatch, threshold, categoryAttr)};
}

function predict(tree, item) {
    if(tree.category) {
        return tree.category;
    }

    var attrName = tree.attribute;
    var attrValue = item[attrName];

    var predicateName = tree.predicate;
    var predicate = predicates[predicateName];

    var pivot =  tree.pivot;

    if((attrValue != null) && predicate(attrValue, pivot)) {
        return predict(tree.match, item);
    } else {
        return predict(tree.notMatch, item);
    }
}

/*
var points = []
for(var i = 0; i < 150; i++) {
    var x = (Math.random() - Math.random()) * 10;
    var y = (Math.random() - Math.random()) * 10;
    var pt = {x : x, y : y}
    if(x * x + y * y < 5 * 5) pt.category = 'in_circle'; else pt.category = 'out_of_circle';
    points.push(pt)
}
var tree = buildTree(points, 5);
alert(JSON.stringify(tree, null, 4))
var pt = {x : 0, y : 0};
pt.prediction = predict(tree, pt);
alert(JSON.stringify(pt, null, 4))
pt = {x : 7, y : 7};
pt.prediction = predict(tree, pt);
alert(JSON.stringify(pt, null, 4))

var stat = {correct : 0, incorrect : 0}
for(var i = 0; i < 1000; i++) {
    var x = (Math.random() - Math.random()) * 10;
    var y = (Math.random() - Math.random()) * 10;
    var pt = {x : x, y : y}
    pt.prediction = predict(tree, pt);
    if(x * x + y * y < 5 * 5) pt.c = 'in_circle'; else pt.c = 'out_of_circle';
    if(pt.c == pt.prediction) stat.correct++; else stat.incorrect++;
}
alert(JSON.stringify(stat, null, 4))
*/<|endoftext|>


<commit_before>/* ************************************ */
/* Define helper functions */
/* ************************************ */
function getDisplayElement() {
  $('

').appendTo('body')
  return $('

').appendTo('body')
}

function evalAttentionChecks() {
  var check_percent = 1
  if (run_attention_checks) {
    var attention_check_trials = jsPsych.data.getTrialsOfType('attention-check')
    var checks_passed = 0
    for (var i = 0; i < attention_check_trials.length; i++) {
      if (attention_check_trials[i].correct === true) {
        checks_passed += 1
      }
    }
    check_percent = checks_passed / attention_check_trials.length
  }
  return check_percent
}

function addID() {
  jsPsych.data.addDataToLastTrial({
    'exp_id': 'kirby'
  })
}

var getInstructFeedback = function() {
  return '

' + feedback_instruct_text +
    '

'
}

/* ************************************ */
/* Define experimental variables */
/* ************************************ */
// generic task variables
var run_attention_checks = false
var attention_check_thresh = 0.45
var sumInstructTime = 0 //ms
var instructTimeThresh = 0 ///in seconds

// task specific variables
//hard coded options in the amounts and order specified in Kirby and Marakovic (1996)
var options = {
  small_amt: [54, 55, 19, 31, 14, 47, 15, 25, 78, 40, 11, 67, 34, 27, 69, 49, 80, 24, 33, 28, 34,
    25, 41, 54, 54, 22, 20
  ],
  large_amt: [55, 75, 25, 85, 25, 50, 35, 60, 80, 55, 30, 75, 35, 50, 85, 60, 85, 35, 80, 30, 50,
    30, 75, 60, 80, 25, 55
  ],
  later_del: [117, 61, 53, 7, 19, 160, 13, 14, 192, 62, 7, 119, 186, 21, 91, 89, 157, 29, 14, 179,
    30, 80, 20, 111, 30, 136, 7
  ]
}

var stim_html = []

//loop through each option to create html
for (var i = 0; i < options.small_amt.length; i++) {
  stim_html[i] =
    "

Please select the option that you would prefer pressing 'q' for left 'p' for right:

$" +
    options.small_amt[i] +
    "
today

$" + options.large_amt[
      i] + "
" + options.later_del[i] + " days

"
}

data_prop = []

for (var i = 0; i < options.small_amt.length; i++) {
  data_prop.push({
    small_amt: options.small_amt[i],
    large_amt: options.large_amt[i],
    later_del: options.later_del[i]
  });
}

trials = []

//used new features to include the stimulus properties in recorded data
for (var i = 0; i < stim_html.length; i++) {
  trials.push({
    stimulus: stim_html[i],
    data: data_prop[i]
  });
}

/* ************************************ */
/* Set up jsPsych blocks */
/* ************************************ */
// Set up attention check node
var attention_check_block = {
  type: 'attention-check',
  data: {
    trial_id: "attention_check"
  },
  timing_response: 180000,
  response_ends_trial: true,
  timing_post_trial: 200
}

var attention_node = {
  timeline: [attention_check_block],
  conditional_function: function() {
    return run_attention_checks
  }
}

/* define static blocks */
var feedback_instruct_text =
  'Welcome to the experiment. Press enter to begin.'
var feedback_instruct_block = {
  type: 'poldrack-text',
  cont_key: [13],
  data: {
    trial_id: "instruction"
  },
  text: getInstructFeedback,
  timing_post_trial: 0,
  timing_response: 180000
};
/// This ensures that the subject does not read through the instructions too quickly.  If they do it too quickly, then we will go over the loop again.
var instruction_trials = []
var instructions_block = {
  type: 'poldrack-instructions',
  data: {
    trial_id: "instruction"
  },
  pages: [
    '

In this experiment you will be presented with two amounts of money to choose between. One of the amounts will be available now and the other will be available in the future. Your job is to indicate which option you would prefer by pressing "q" for the left option and "p" for the right option.

You should indicate your true preference because at the end of the experiment a random trial will be chosen and you will receive a bonus payment proportional to the option you selected at the time point you chose.

',
  ],
  allow_keys: false,
  show_clickable_nav: true
};

instruction_trials.push(feedback_instruct_block)
instruction_trials.push(instructions_block)

var instruction_node = {
  timeline: instruction_trials,
  /* This function defines stopping criteria */
  loop_function: function(data) {
    for (var i = 0; i < data.length; i++) {
      if ((data[i].trial_type == 'poldrack-instructions') && (data[i].rt != -1)) {
        rt = data[i].rt
        sumInstructTime = sumInstructTime + rt
      }
    }
    if (sumInstructTime <= instructTimeThresh * 1000) {
      feedback_instruct_text =
        'Read through instructions too quickly.  Please take your time and make sure you understand the instructions.  Press enter to continue.'
      return true
    } else if (sumInstructTime > instructTimeThresh * 1000) {
      feedback_instruct_text =
        'Done with instructions. Press enter to continue.'
      return false
    }
  }
}

var start_practice_block = {
  type: 'poldrack-text',
  timing_response: 180000,
  data: {
    trial_id: "practice_intro"
  },
  text: '

Here is a sample trial. Your choice for this trial will not be included in your bonus payment.

Press enter to begin.

',
  cont_key: [13],
  //timing_post_trial: 1000
};

var practice_block = {
  type: 'poldrack-single-stim',
  data: {
    trial_id: "stim",
    exp_stage: "practice"
  },
  stimulus: "

Please select the option that you would prefer pressing 'q' for left 'p' for right:

$20
today

$25
5 days

",
  is_html: true,
  choices: [80,81],
  response_ends_trial: true,
};

var start_test_block = {
  type: 'poldrack-text',
  data: {
    trial_id: "test_intro"
  },
  timing_response: 180000,
  text: '

You are now ready to begin the survey.

Remember to indicate your true preferences.

Press enter to begin.

',
  cont_key: [13],
  //timing_post_trial: 1000
};

var test_block = {
  type: 'poldrack-single-stim',
  data: {
    trial_id: "stim",
    exp_stage: "test"
  },
  timeline: trials,
  is_html: true,
  choices: [80,81],
  response_ends_trial: true,
  //used new feature to include choice info in recorded data
  on_finish: function(data) {
    var choice = false;
    if (data.key_press == 80) {
      choice = 'larger_later';
    } else if (data.key_press == 81) {
      choice = 'smaller_sooner';
    }
    jsPsych.data.addDataToLastTrial({
      choice: choice
    });
  }
};

var end_block = {
  type: 'poldrack-text',
  data: {
    trial_id: "end"
  },
  timing_response: 180000,
  text: '

Thanks for completing this task!

Press enter to continue.

',
  cont_key: [13],
  timing_post_trial: 0
};

//Set up experiment
var kirby_experiment = []
kirby_experiment.push(instruction_node);
kirby_experiment.push(start_practice_block);
kirby_experiment.push(practice_block);
kirby_experiment.push(attention_node);
kirby_experiment.push(start_test_block);
kirby_experiment.push(test_block);
kirby_experiment.push(attention_node);
kirby_experiment.push(end_block);<commit_msg>updated kirby<commit_after>/* ************************************ */
/* Define helper functions */
/* ************************************ */
function getDisplayElement() {
  $('

').appendTo('body')
  return $('

').appendTo('body')
}

function evalAttentionChecks() {
  var check_percent = 1
  if (run_attention_checks) {
    var attention_check_trials = jsPsych.data.getTrialsOfType('attention-check')
    var checks_passed = 0
    for (var i = 0; i < attention_check_trials.length; i++) {
      if (attention_check_trials[i].correct === true) {
        checks_passed += 1
      }
    }
    check_percent = checks_passed / attention_check_trials.length
  }
  return check_percent
}

function addID() {
  jsPsych.data.addDataToLastTrial({
    'exp_id': 'kirby'
  })
}

var getInstructFeedback = function() {
  return '

' + feedback_instruct_text +
    '

'
}

/* ************************************ */
/* Define experimental variables */
/* ************************************ */
// generic task variables
var run_attention_checks = false
var attention_check_thresh = 0.45
var sumInstructTime = 0 //ms
var instructTimeThresh = 0 ///in seconds

// task specific variables
//hard coded options in the amounts and order specified in Kirby and Marakovic (1996)
var options = {
  small_amt: [54, 55, 19, 31, 14, 47, 15, 25, 78, 40, 11, 67, 34, 27, 69, 49, 80, 24, 33, 28, 34,
    25, 41, 54, 54, 22, 20
  ],
  large_amt: [55, 75, 25, 85, 25, 50, 35, 60, 80, 55, 30, 75, 35, 50, 85, 60, 85, 35, 80, 30, 50,
    30, 75, 60, 80, 25, 55
  ],
  later_del: [117, 61, 53, 7, 19, 160, 13, 14, 192, 62, 7, 119, 186, 21, 91, 89, 157, 29, 14, 179,
    30, 80, 20, 111, 30, 136, 7
  ]
}

var stim_html = []

//loop through each option to create html
for (var i = 0; i < options.small_amt.length; i++) {
  stim_html[i] =
    "

Please select the option that you would prefer pressing 'q' for left 'p' for right:

$" +
    options.small_amt[i] +
    "
today

$" + options.large_amt[
      i] + "
" + options.later_del[i] + " days

"
}

data_prop = []

for (var i = 0; i < options.small_amt.length; i++) {
  data_prop.push({
    small_amt: options.small_amt[i],
    large_amt: options.large_amt[i],
    later_del: options.later_del[i]
  });
}

trials = []

//used new features to include the stimulus properties in recorded data
for (var i = 0; i < stim_html.length; i++) {
  trials.push({
    stimulus: stim_html[i],
    data: data_prop[i]
  });
}

/* ************************************ */
/* Set up jsPsych blocks */
/* ************************************ */
// Set up attention check node
var attention_check_block = {
  type: 'attention-check',
  data: {
    trial_id: "attention_check"
  },
  timing_response: 180000,
  response_ends_trial: true,
  timing_post_trial: 200
}

var attention_node = {
  timeline: [attention_check_block],
  conditional_function: function() {
    return run_attention_checks
  }
}

/* define static blocks */
var feedback_instruct_text =
  'Welcome to the experiment. Press enter to begin.'
var feedback_instruct_block = {
  type: 'poldrack-text',
  cont_key: [13],
  data: {
    trial_id: "instruction"
  },
  text: getInstructFeedback,
  timing_post_trial: 0,
  timing_response: 180000
};
/// This ensures that the subject does not read through the instructions too quickly.  If they do it too quickly, then we will go over the loop again.
var instructions_block = {
  type: 'poldrack-instructions',
  data: {
    trial_id: "instruction"
  },
  pages: [
    '

In this experiment you will be presented with two amounts of money to choose between. One of the amounts will be available now and the other will be available in the future. Your job is to indicate which option you would prefer by pressing "q" for the left option and "p" for the right option.

You should indicate your true preference because at the end of the experiment a random trial will be chosen and you will receive a bonus payment proportional to the option you selected at the time point you chose.

',
  ],
  allow_keys: false,
  show_clickable_nav: true
};

var instruction_node = {
  timeline: [feedback_instruct_block, instructions_block],
  /* This function defines stopping criteria */
  loop_function: function(data) {
    for (var i = 0; i < data.length; i++) {
      if ((data[i].trial_type == 'poldrack-instructions') && (data[i].rt != -1)) {
        rt = data[i].rt
        sumInstructTime = sumInstructTime + rt
      }
    }
    if (sumInstructTime <= instructTimeThresh * 1000) {
      feedback_instruct_text =
        'Read through instructions too quickly.  Please take your time and make sure you understand the instructions.  Press enter to continue.'
      return true
    } else if (sumInstructTime > instructTimeThresh * 1000) {
      feedback_instruct_text =
        'Done with instructions. Press enter to continue.'
      return false
    }
  }
}

var start_practice_block = {
  type: 'poldrack-text',
  timing_response: 180000,
  data: {
    trial_id: "practice_intro"
  },
  text: '

Here is a sample trial. Your choice for this trial will not be included in your bonus payment.

Press enter to begin.

',
  cont_key: [13],
  //timing_post_trial: 1000
};

var practice_block = {
  type: 'poldrack-single-stim',
  data: {
    trial_id: "stim",
    exp_stage: "practice"
  },
  stimulus: "

Please select the option that you would prefer pressing 'q' for left 'p' for right:

$20
today

$25
5 days

",
  is_html: true,
  choices: [80,81],
  response_ends_trial: true,
};

var start_test_block = {
  type: 'poldrack-text',
  data: {
    trial_id: "test_intro"
  },
  timing_response: 180000,
  text: '

You are now ready to begin the survey.

Remember to indicate your true preferences.

Press enter to begin.

',
  cont_key: [13],
  //timing_post_trial: 1000
};

var test_block = {
  type: 'poldrack-single-stim',
  data: {
    trial_id: "stim",
    exp_stage: "test"
  },
  timeline: trials,
  is_html: true,
  choices: [80,81],
  response_ends_trial: true,
  //used new feature to include choice info in recorded data
  on_finish: function(data) {
    var choice = false;
    if (data.key_press == 80) {
      choice = 'larger_later';
    } else if (data.key_press == 81) {
      choice = 'smaller_sooner';
    }
    jsPsych.data.addDataToLastTrial({
      choice: choice
    });
  }
};

var end_block = {
  type: 'poldrack-text',
  data: {
    trial_id: "end"
  },
  timing_response: 180000,
  text: '

Thanks for completing this task!

Press enter to continue.

',
  cont_key: [13],
  timing_post_trial: 0
};

//Set up experiment
var kirby_experiment = []
kirby_experiment.push(instruction_node);
kirby_experiment.push(start_practice_block);
kirby_experiment.push(practice_block);
kirby_experiment.push(attention_node);
kirby_experiment.push(start_test_block);
kirby_experiment.push(test_block);
kirby_experiment.push(attention_node);
kirby_experiment.push(end_block);<|endoftext|>


<commit_before>/*
 * go2o
 * https://github.com/goliatone/go2o
 * Created with gbase.
 * Copyright (c) 2014 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
(function(root, name, deps, factory) {
    "use strict";
    // Node
    if (typeof deps === 'function') {
        factory = deps;
        deps = [];
    }

    if (typeof exports === 'object') {
        module.exports = factory.apply(root, deps.map(require));
    } else if (typeof define === 'function' && 'amd' in define) {
        //require js, here we assume the file is named as the lower
        //case module name.
        define(name.toLowerCase(), deps, factory);
    } else {
        // Browser
        var d, i = 0,
            global = root,
            old = global[name],
            mod;
        while ((d = deps[i]) !== undefined) deps[i++] = root[d];
        global[name] = mod = factory.apply(global, deps);
        //Export no 'conflict module', aliases the module.
        mod.noConflict = function() {
            global[name] = old;
            return mod;
        };
    }
}(this, 'go2o', function() {

    /**
     * Extend method.
     * @param  {Object} target Source object
     * @return {Object}        Resulting object from
     *                         meging target to params.
     */
    var _extend = function extend(target) {
        if (!target) return {};

        var sources = [].slice.call(arguments, 1);
        sources.forEach(function(source) {
            for (var property in source) {
                if (source[property] && source[property].constructor &&
                    source[property].constructor === Object) {
                    target[property] = target[property] || {};
                    target[property] = extend(target[property], source[property]);
                } else target[property] = source[property];
            }
        });

        return target;
    };

    /**
     * Shim console, make sure that if no console
     * available calls do not generate errors.
     * @return {Object} Console shim.
     */
    var _shimConsole = function() {
        var empty = {},
            con = {},
            noop = function() {},
            properties = 'memory'.split(','),
            methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' +
                'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
                'table,time,timeEnd,timeStamp,trace,warn').split(','),
            prop,
            method;

        while (method = methods.pop()) con[method] = noop;
        while (prop = properties.pop()) con[prop] = empty;

        return con;
    };

    /**
     * Get keys in the `src` object not
     * present in the `tgt` object.
     * @param  {Object} src Source Object
     * @param  {Object} tgt Target Object
     * @return {Array}      Array with keys
     */
    var _diff = function(src, tgt) {
        if (!src) return Object.keys(tgt);
        if (!tgt) return [];
        var keys = Object.keys(tgt);
        return Object.keys(src).filter(function(i) {
            return keys.indexOf(i) < 0;
        });
    };

    /**
     * Unique elements in array.
     * @param  {Array} src Source array
     * @return {Array}     Filtered array, no dups
     */
    var _unique = function(src) {
        return src.filter(function(i, pos, self) {
            return self.indexOf(i) === pos;
        });
    };

    var _isFunction = function(src, key) {
        return src && key && src[key] && typeof src[key] === 'function';
    };

    ///////////////////////////////////////////////////
    /// HELPER JSON
    /// TODO: Implement as plugin?
    /// Go2o.use(Flattener);
    ///////////////////////////////////////////////////
    //{
    var Parser = {};
    /**
     * Flatten a deeply nested object into a map of a
     * single level where the keys match to original
     * string path of the property.
     *
     * @param  {Object} src    Object to be serialized
     * @return {Object}        Serialized object.
     */
    Parser.flatten = function flatten(src, /*private*/ prop, output) {
        prop || (prop = '');
        output || (output = {});

        if (Object(src) !== src) output[prop] = src;
        else if (Array.isArray(src)) {
            for (var i = 0, l = src.length; i < l; i++) {
                flatten(src[i], prop ? prop + '.' + i : '' + i, output);
            }
            if (l === 0) output[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in src) {
                isEmpty = false;
                flatten(src[p], prop ? prop + '.' + p : p, output);
            }
            if (isEmpty) output[prop] = {};
        }

        return (isEmpty === true) ? {} : output;
    };

    /**
     * Unflatten a map of string path keys
     * and values to a fully nested object.
     *
     * @param  {Object} src Map to be unmapped
     * @return {Object}
     */
    Parser.unflatten = function(src) {
        if (Object(src) !== src || Array.isArray(src)) return src;

        var result = {}, cur, prop, idx, last, temp;
        for (var p in src) {
            cur = result, prop = '__ROOT__', last = 0;

            do {
                idx = p.indexOf('.', last);
                temp = p.substring(last, idx !== -1 ? idx : undefined);
                cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
                prop = temp;
                last = idx + 1;
            } while (idx >= 0);

            cur[prop] = src[p];
        }

        return result['__ROOT__'] || {};
    };

    /**
     * Captures all object keys that match
     * a regexp pattern
     * @param  {Object} map     Source object
     * @param  {RegExp} pattern Matcher
     * @return {Object}         Object with the
     *                          subset of properties
     *                          matching `pattern`
     */
    Parser.glob = function(map, pattern) {
        map || (map = {});
        var out = {};
        Object.keys(map).forEach(function(key) {
            if (key.match(pattern)) out[key] = map[key];
        });
        return out;
    };
    //}
    ///////////////////////////////////////////////////
    // CONSTRUCTOR
    ///////////////////////////////////////////////////

    var OPTIONS = {
        /*
         * List of attributes that get initialized even
         * if not specified on the config object.
         */
        attributes: ['schema', 'transforms', 'flattened', 'matchers',
            'transformers', 'processors', 'preprocessors', 'postprocessors',
            'handledKeys'
        ],
        /*
         * Should we override output in case
         * of conflict, ie: object merge.
         */
        overrideOnConflict: true,
        /*
         * This pre-process should always
         * happen BEFORE than any other process
         */
        defaultPre: ['flattenPaths'],
        /*
         * This two processes should always happen,
         * unflattenPaths should always be the last
         * process to run.
         */
        defaultPost: ['mergeOrphans', 'unflattenPaths']
    };

    /**
     * Go2o constructor
     *
     * @param  {object} config Configuration object.
     */
    var Go2o = function(config) {
        //TODO: We want to only merge in valid attributes!!
        //not the whole config object!
        config = config || {};
        config = _extend({}, this.constructor.DEFAULTS, config);

        this.init(config);
    };

    /**
     * Make default options available so we
     * can override.
     */
    Go2o.DEFAULTS = OPTIONS;

    ///////////////////////////////////////////////////
    // PUBLIC METHODS
    ///////////////////////////////////////////////////

    Go2o.prototype.init = function(config) {
        if (this.initialized) return this.logger.warn('Already initialized');
        this.initialized = true;

        console.log('Go2o: Init!', config);

        _extend(this, config);

        /*
         * Create attributes.
         */
        OPTIONS.attributes.forEach(function(prop) {
            this.hasOwnProperty(prop) || (this[prop] = {});
        }, this);

        this.initializeProcessors();

        this.registerDefaultTransformations();
    };

    Go2o.prototype.initializeProcessors = function() {
        /*
         * We need to ensure that we kick start
         * pre/post collections.
         */
        this.pre || (this.pre = []);
        this.pre = _unique(this.defaultPre.concat(this.pre));

        this.post || (this.post = []);
        this.post = _unique(this.post.concat(this.defaultPost));
    };

    Go2o.prototype.registerDefaultTransformations = function() {
        /*****************************************
         * ADDING SAMPLE TRANSFORMATIONS. MOVE OUT
         *****************************************/
        this.addTransformer('rename', function rename(path, options) {
            if (typeof options === 'object' && options.glob === true) {
                Object.keys(this.flattened).forEach(function(key) {
                    if (!key.match(options.matcher)) return;
                    var name = key;
                    if (typeof options.execute === 'function') {
                        name = options.execute(key, path, options);
                    }

                    this.output[name] = this.flattened[key];
                    delete this.flattened[key];
                }, this);

                return true;
            } else if (!this.flattened.hasOwnProperty(path)) return false;

            console.log('=> rename %s to %s', path, options);
            this.output[options] = this.flattened[path];
            delete this.flattened[path];

            return true;
        });

        this.addTransformer('collapse', function collapse(path, name) {
            if (!this.flattened.hasOwnProperty(path)) return false;
            // console.log('=> collapse %s to %s', path, name);
            if (!this.output.hasOwnProperty(name)) this.output[name] = [];
            this.output[name].push(this.flattened[path]);
            return true;
        });

        this.addTransformer('remove', function remove(path, options) {
            if (typeof options === 'object' && options.glob === true) {
                Object.keys(this.flattened).forEach(function(key) {
                    if (!key.match(options.matcher)) return;
                    delete this.flattened[key];
                }, this);
            } else if (!this.flattened.hasOwnProperty(path)) return false;

            delete this.flattened[path];
            return true;
        });

        /*
         * Flatten source object into map. After this point we are
         * ready to apply our transformations.
         */
        this.addPreprocessor('flattenPaths', function flattenPaths() {
            console.warn('flattenPaths');
            this.flattened = Parser.flatten(this.source);
        });

        /*
         * Post processor that merges untransformed keys into the
         * output object.
         */
        this.addPostprocessor('mergeOrphans', function mergeOrphans() {
            /*
             * Collect all keys in the original map that were
             * not handled.
             */
            var orphans = _diff(this.flattened, this.handledKeys),
                fname = arguments.callee.name;

            orphans.forEach(function(key) {
                if (this.output.hasOwnProperty(key)) {
                    this.solveConflict(key);
                } else this.output[key] = this.flattened[key];
            }, this);
        });

        /*
         * We are done, rehydrate our object from the map.
         */
        this.addPostprocessor('unflattenPaths', function() {
            this.output = Parser.unflatten(this.output);
        });
    };

    /**
     * Simple method to solve conflict when
     * merging elements.
     * @param  {String} key Element ID
     * @return {void}
     */
    Go2o.prototype.solveConflict = function(key) {
        this.logger.warn('Conflict, solving for key', key);

        if (this.overrideOnConflict === true) {
            this.logger.warn('√ now %s, had value %s', this.flattened[key], this.output[key]);
            this.output[key] = this.flattened[key];
        } else {
            this.logger.warn('= now %s, ignore %s', this.output[key], this.flattened[key]);
        }
    };

    /**
     * Run processes and transformations.
     * @param  {Object} data Object to which
     *                       we want to modify.
     * @return {Object}      Transformed object.
     */
    Go2o.prototype.run = function(data) {
        console.log('parsing', data);

        this.source = _extend({}, data);

        //this is a reset
        this.output = {};
        this.handledKeys = {};

        this.runPre();

        //Iterate over the source properties in transforms:
        Object.keys(this.transforms).map(this.applyTransformTo, this);

        //after we run all trasforms, we should pic all properties of
        //original object and append those to the output.
        this.runPost();

        return this.output;
    };

    Go2o.prototype.applyTransformTo = function(path) {
        var rules = this.transforms[path],
            arg = null,
            handled = false;

        //for now we assume that all schema values are objs.
        Object.keys(rules).forEach(function(event) {
            arg = rules[event];

            if (!_isFunction(this.transformers, event)) return;

            handled = this.transformers[event].call(this, path, arg);
            if (handled === true) this.handledKeys[path] = true;
        }, this);
    };

    /**
     * Transformation method attached to `event`
     * key.
     * TODO: Transformer should be an object:
     * ```
     *     var transformer = {
     *         id: 'rename',
     *         check: function(path, name){
     *             return this.flattened.hasOwnProperty(path);
     *         },
     *         execute: function(path, name){
     *             this.output[name] = this.flattened[path];
     *         }
     *     };
     * ```
     * @param {String} event       Name to trigger
     *                             transformation.
     * @param {this}
     */
    Go2o.prototype.addTransformer = function(event, transformer) {
        //WE REALLY WANT TO DO THIS WITH EVENTS!!
        // this.on(transformer.key, transformer.execute);
        this.transformers[event] = transformer;
        return this;
    };

    Go2o.prototype.addPostprocessor = function(id, post) {
        this.postprocessors[id] = post;
        return this;
    };

    Go2o.prototype.addPreprocessor = function(id, pre) {
        this.preprocessors[id] = pre;
        return this;
    };

    Go2o.prototype.runPost = function() {
        var processId;
        Object.keys(this.post).forEach(function(id) {
            processId = this.post[id];
            if (!_isFunction(this.postprocessors, processId)) return;
            this.postprocessors[processId].call(this);
        }, this);

        return this;
    };

    Go2o.prototype.runPre = function() {
        var processId;
        Object.keys(this.pre).forEach(function(id) {
            processId = this.pre[id];

            if (!_isFunction(this.preprocessors, processId)) return;

            this.preprocessors[processId].call(this);
        }, this);
        return this;
    };

    /**
     * Logger method, meant to be implemented by
     * mixin. As a placeholder, we use console if available
     * or a shim if not present.
     */
    Go2o.prototype.logger = console || _shimConsole();

    /*****************************
     * EXPOSE HELPERS, FOR TESTING.
     ******************************/
    Go2o.helpers = {};
    Go2o.helpers.diff = _diff;
    Go2o.helpers.extend = _extend;
    Go2o.helpers.isFunction = _isFunction;
    Go2o.helpers.Parser = Parser;

    return Go2o;
}));<commit_msg>Bump version.<commit_after>/*
 * go2o
 * https://github.com/goliatone/go2o
 * Created with gbase.
 * Copyright (c) 2014 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
(function(root, name, deps, factory) {
    "use strict";
    // Node
    if (typeof deps === 'function') {
        factory = deps;
        deps = [];
    }

    if (typeof exports === 'object') {
        module.exports = factory.apply(root, deps.map(require));
    } else if (typeof define === 'function' && 'amd' in define) {
        //require js, here we assume the file is named as the lower
        //case module name.
        define(name.toLowerCase(), deps, factory);
    } else {
        // Browser
        var d, i = 0,
            global = root,
            old = global[name],
            mod;
        while ((d = deps[i]) !== undefined) deps[i++] = root[d];
        global[name] = mod = factory.apply(global, deps);
        //Export no 'conflict module', aliases the module.
        mod.noConflict = function() {
            global[name] = old;
            return mod;
        };
    }
}(this, 'go2o', function() {

    /**
     * Extend method.
     * @param  {Object} target Source object
     * @return {Object}        Resulting object from
     *                         meging target to params.
     */
    var _extend = function extend(target) {
        if (!target) return {};

        var sources = [].slice.call(arguments, 1);
        sources.forEach(function(source) {
            for (var property in source) {
                if (source[property] && source[property].constructor &&
                    source[property].constructor === Object) {
                    target[property] = target[property] || {};
                    target[property] = extend(target[property], source[property]);
                } else target[property] = source[property];
            }
        });

        return target;
    };

    /**
     * Shim console, make sure that if no console
     * available calls do not generate errors.
     * @return {Object} Console shim.
     */
    var _shimConsole = function() {
        var empty = {},
            con = {},
            noop = function() {},
            properties = 'memory'.split(','),
            methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' +
                'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
                'table,time,timeEnd,timeStamp,trace,warn').split(','),
            prop,
            method;

        while (method = methods.pop()) con[method] = noop;
        while (prop = properties.pop()) con[prop] = empty;

        return con;
    };

    /**
     * Get keys in the `src` object not
     * present in the `tgt` object.
     * @param  {Object} src Source Object
     * @param  {Object} tgt Target Object
     * @return {Array}      Array with keys
     */
    var _diff = function(src, tgt) {
        if (!src) return Object.keys(tgt);
        if (!tgt) return [];
        var keys = Object.keys(tgt);
        return Object.keys(src).filter(function(i) {
            return keys.indexOf(i) < 0;
        });
    };

    /**
     * Unique elements in array.
     * @param  {Array} src Source array
     * @return {Array}     Filtered array, no dups
     */
    var _unique = function(src) {
        return src.filter(function(i, pos, self) {
            return self.indexOf(i) === pos;
        });
    };

    var _isFunction = function(src, key) {
        return src && key && src[key] && typeof src[key] === 'function';
    };

    ///////////////////////////////////////////////////
    /// HELPER JSON
    /// TODO: Implement as plugin?
    /// Go2o.use(Flattener);
    ///////////////////////////////////////////////////
    //{
    var Parser = {};
    /**
     * Flatten a deeply nested object into a map of a
     * single level where the keys match to original
     * string path of the property.
     *
     * @param  {Object} src    Object to be serialized
     * @return {Object}        Serialized object.
     */
    Parser.flatten = function flatten(src, /*private*/ prop, output) {
        prop || (prop = '');
        output || (output = {});

        if (Object(src) !== src) output[prop] = src;
        else if (Array.isArray(src)) {
            for (var i = 0, l = src.length; i < l; i++) {
                flatten(src[i], prop ? prop + '.' + i : '' + i, output);
            }
            if (l === 0) output[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in src) {
                isEmpty = false;
                flatten(src[p], prop ? prop + '.' + p : p, output);
            }
            if (isEmpty) output[prop] = {};
        }

        return (isEmpty === true) ? {} : output;
    };

    /**
     * Unflatten a map of string path keys
     * and values to a fully nested object.
     *
     * @param  {Object} src Map to be unmapped
     * @return {Object}
     */
    Parser.unflatten = function(src) {
        if (Object(src) !== src || Array.isArray(src)) return src;

        var result = {}, cur, prop, idx, last, temp;
        for (var p in src) {
            cur = result, prop = '__ROOT__', last = 0;

            do {
                idx = p.indexOf('.', last);
                temp = p.substring(last, idx !== -1 ? idx : undefined);
                cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
                prop = temp;
                last = idx + 1;
            } while (idx >= 0);

            cur[prop] = src[p];
        }

        return result['__ROOT__'] || {};
    };

    /**
     * Captures all object keys that match
     * a regexp pattern
     * @param  {Object} map     Source object
     * @param  {RegExp} pattern Matcher
     * @return {Object}         Object with the
     *                          subset of properties
     *                          matching `pattern`
     */
    Parser.glob = function(map, pattern) {
        map || (map = {});
        var out = {};
        Object.keys(map).forEach(function(key) {
            if (key.match(pattern)) out[key] = map[key];
        });
        return out;
    };
    //}
    ///////////////////////////////////////////////////
    // CONSTRUCTOR
    ///////////////////////////////////////////////////

    var OPTIONS = {
        /*
         * List of attributes that get initialized even
         * if not specified on the config object.
         */
        attributes: ['schema', 'transforms', 'flattened', 'matchers',
            'transformers', 'processors', 'preprocessors', 'postprocessors',
            'handledKeys'
        ],
        /*
         * Should we override output in case
         * of conflict, ie: object merge.
         */
        overrideOnConflict: true,
        /*
         * This pre-process should always
         * happen BEFORE than any other process
         */
        defaultPre: ['flattenPaths'],
        /*
         * This two processes should always happen,
         * unflattenPaths should always be the last
         * process to run.
         */
        defaultPost: ['mergeOrphans', 'unflattenPaths']
    };

    /**
     * Go2o constructor
     *
     * @param  {object} config Configuration object.
     */
    var Go2o = function(config) {
        //TODO: We want to only merge in valid attributes!!
        //not the whole config object!
        config = config || {};
        config = _extend({}, this.constructor.DEFAULTS, config);

        this.init(config);
    };

    Go2o.VERSION = '0.3.0';

    /**
     * Make default options available so we
     * can override.
     */
    Go2o.DEFAULTS = OPTIONS;

    ///////////////////////////////////////////////////
    // PUBLIC METHODS
    ///////////////////////////////////////////////////

    Go2o.prototype.init = function(config) {
        if (this.initialized) return this.logger.warn('Already initialized');
        this.initialized = true;

        console.log('Go2o: Init!', config);

        _extend(this, config);

        /*
         * Create attributes.
         */
        OPTIONS.attributes.forEach(function(prop) {
            this.hasOwnProperty(prop) || (this[prop] = {});
        }, this);

        this.initializeProcessors();

        this.registerDefaultTransformations();
    };

    Go2o.prototype.initializeProcessors = function() {
        /*
         * We need to ensure that we kick start
         * pre/post collections.
         */
        this.pre || (this.pre = []);
        this.pre = _unique(this.defaultPre.concat(this.pre));

        this.post || (this.post = []);
        this.post = _unique(this.post.concat(this.defaultPost));
    };

    Go2o.prototype.registerDefaultTransformations = function() {
        /*****************************************
         * ADDING SAMPLE TRANSFORMATIONS. MOVE OUT
         *****************************************/
        this.addTransformer('rename', function rename(path, options) {
            if (typeof options === 'object' && options.glob === true) {
                Object.keys(this.flattened).forEach(function(key) {
                    if (!key.match(options.matcher)) return;
                    var name = key;
                    if (typeof options.execute === 'function') {
                        name = options.execute(key, path, options);
                    }

                    this.output[name] = this.flattened[key];
                    delete this.flattened[key];
                }, this);

                return true;
            } else if (!this.flattened.hasOwnProperty(path)) return false;

            console.log('=> rename %s to %s', path, options);
            this.output[options] = this.flattened[path];
            delete this.flattened[path];

            return true;
        });

        this.addTransformer('collapse', function collapse(path, name) {
            if (!this.flattened.hasOwnProperty(path)) return false;
            // console.log('=> collapse %s to %s', path, name);
            if (!this.output.hasOwnProperty(name)) this.output[name] = [];
            this.output[name].push(this.flattened[path]);
            return true;
        });

        this.addTransformer('remove', function remove(path, options) {
            if (typeof options === 'object' && options.glob === true) {
                Object.keys(this.flattened).forEach(function(key) {
                    if (!key.match(options.matcher)) return;
                    delete this.flattened[key];
                }, this);
            } else if (!this.flattened.hasOwnProperty(path)) return false;

            delete this.flattened[path];
            return true;
        });

        /*
         * Flatten source object into map. After this point we are
         * ready to apply our transformations.
         */
        this.addPreprocessor('flattenPaths', function flattenPaths() {
            console.warn('flattenPaths');
            this.flattened = Parser.flatten(this.source);
        });

        /*
         * Post processor that merges untransformed keys into the
         * output object.
         */
        this.addPostprocessor('mergeOrphans', function mergeOrphans() {
            /*
             * Collect all keys in the original map that were
             * not handled.
             */
            var orphans = _diff(this.flattened, this.handledKeys),
                fname = arguments.callee.name;

            orphans.forEach(function(key) {
                if (this.output.hasOwnProperty(key)) {
                    this.solveConflict(key);
                } else this.output[key] = this.flattened[key];
            }, this);
        });

        /*
         * We are done, rehydrate our object from the map.
         */
        this.addPostprocessor('unflattenPaths', function() {
            this.output = Parser.unflatten(this.output);
        });
    };

    /**
     * Simple method to solve conflict when
     * merging elements.
     * @param  {String} key Element ID
     * @return {void}
     */
    Go2o.prototype.solveConflict = function(key) {
        this.logger.warn('Conflict, solving for key', key);

        if (this.overrideOnConflict === true) {
            this.logger.warn('√ now %s, had value %s', this.flattened[key], this.output[key]);
            this.output[key] = this.flattened[key];
        } else {
            this.logger.warn('= now %s, ignore %s', this.output[key], this.flattened[key]);
        }
    };

    /**
     * Run processes and transformations.
     * @param  {Object} data Object to which
     *                       we want to modify.
     * @return {Object}      Transformed object.
     */
    Go2o.prototype.run = function(data) {
        console.log('parsing', data);

        this.source = _extend({}, data);

        //this is a reset
        this.output = {};
        this.handledKeys = {};

        this.runPre();

        //Iterate over the source properties in transforms:
        Object.keys(this.transforms).map(this.applyTransformTo, this);

        //after we run all trasforms, we should pic all properties of
        //original object and append those to the output.
        this.runPost();

        return this.output;
    };

    Go2o.prototype.applyTransformTo = function(path) {
        var rules = this.transforms[path],
            arg = null,
            handled = false;

        //for now we assume that all schema values are objs.
        Object.keys(rules).forEach(function(event) {
            arg = rules[event];

            if (!_isFunction(this.transformers, event)) return;

            handled = this.transformers[event].call(this, path, arg);
            if (handled === true) this.handledKeys[path] = true;
        }, this);
    };

    /**
     * Transformation method attached to `event`
     * key.
     * TODO: Transformer should be an object:
     * ```
     *     var transformer = {
     *         id: 'rename',
     *         check: function(path, name){
     *             return this.flattened.hasOwnProperty(path);
     *         },
     *         execute: function(path, name){
     *             this.output[name] = this.flattened[path];
     *         }
     *     };
     * ```
     * @param {String} event       Name to trigger
     *                             transformation.
     * @param {this}
     */
    Go2o.prototype.addTransformer = function(event, transformer) {
        //WE REALLY WANT TO DO THIS WITH EVENTS!!
        // this.on(transformer.key, transformer.execute);
        this.transformers[event] = transformer;
        return this;
    };

    Go2o.prototype.addPostprocessor = function(id, post) {
        this.postprocessors[id] = post;
        return this;
    };

    Go2o.prototype.addPreprocessor = function(id, pre) {
        this.preprocessors[id] = pre;
        return this;
    };

    Go2o.prototype.runPost = function() {
        var processId;
        Object.keys(this.post).forEach(function(id) {
            processId = this.post[id];
            if (!_isFunction(this.postprocessors, processId)) return;
            this.postprocessors[processId].call(this);
        }, this);

        return this;
    };

    Go2o.prototype.runPre = function() {
        var processId;
        Object.keys(this.pre).forEach(function(id) {
            processId = this.pre[id];

            if (!_isFunction(this.preprocessors, processId)) return;

            this.preprocessors[processId].call(this);
        }, this);
        return this;
    };

    /**
     * Logger method, meant to be implemented by
     * mixin. As a placeholder, we use console if available
     * or a shim if not present.
     */
    Go2o.prototype.logger = console || _shimConsole();

    /*****************************
     * EXPOSE HELPERS, FOR TESTING.
     ******************************/
    Go2o.helpers = {};
    Go2o.helpers.diff = _diff;
    Go2o.helpers.extend = _extend;
    Go2o.helpers.isFunction = _isFunction;
    Go2o.helpers.Parser = Parser;

    return Go2o;
}));<|endoftext|>


<commit_before>// Constants and utils
const CONTENT_TYPE = 'Content-Type';
const errors = {
  network: 'NETWORK',
  abort: 'ABORT',
  cancel: 'CANCEL',
  timeout: 'TIMEOUT'
};

function noop() {}

function copy(value) {
  if (value !== null && typeof value === 'object') {
    const result = {};
    for (let key in value) {
      result[key] = copy(value[key]);
    }
    return result;
  } else {
    return value;
  }
}

function hasQuery(url) {
  return (url.indexOf('?') > -1);
}

function fail(message) {
  throw new TypeError('grab: ' + message);
}

function extractHeaders(xhr) {
  const tuples = xhr.getAllResponseHeaders().trim().split('\n');
  return tuples.reduce((headers, tuple)=> {
    const keyValue = tuple.split(':');
    if (keyValue[0]) {
      headers[keyValue.shift().trim()] = keyValue.join(':').trim();
    }
    return headers;
  }, {});
}

// Errors
class NetworkError extends Error {
  constructor () {
    super();
    this.name = 'NetworkError';
    this.code = errors.network;
    this.message = 'Network request failed';
  }
}

class AbortError extends Error {
  constructor () {
    super();
    this.name = 'AbortError';
    this.code = errors.abort;
    this.message = 'Request was aborted for an unknow reason';
  }
}

class CancelError extends AbortError {
  constructor () {
    super();
    this.name = 'CancelError';
    this.code = errors.cancel;
    this.message = 'Request canceled by user';
  }
}

class TimeoutError extends AbortError {
  constructor (duration) {
    super();
    this.name = 'CancelError';
    this.code = errors.timeout;
    this.message = `Request timeout after ${duration}ms`;
  }
}

function parseFormData(body, FormData) {
  if (!FormData) {
    fail('FormData is not supported');
  }

  const form = new FormData();
  body.trim().split('&').forEach(function (params) {
    if (params) {
      const keyValue = params.split('=');
      const key = keyValue.shift().replace(/\+/g, ' ');
      const value = keyValue.join('=').replace(/\+/g, ' ');
      form.append(decodeURIComponent(key), decodeURIComponent(value))
    }
  });
  return form;
}

class Response {
  constructor (xhr, status, FormData) {
    this.type = 'default';
    this.status = status;
    this.statusText = xhr.statusText;
    this.ok = this.status >= 200 && this.status < 300;
    this.headers = extractHeaders(xhr);
    this.body = 'response' in xhr ? xhr.response : xhr.responseText;
    this.FormData = FormData;
  }

  text () {
    return this.body;
  }

  json () {
    if (!this.bodyJSON) {
      this.bodyJSON = JSON.parse(this.text());
    }

    return this.bodyJSON;
  }

  formData () {
    if (!this.bodyFormData) {
      this.bodyFormData = parseFormData(this.text(), this.FormData)
    }

    return this.bodyFormData;
  }
}

// The real deal
export default function grabFactory(global, XMLHttpRequest, FormData, undef) {
  const defaults = {};

  function resetDefaults() {
    defaults.log = noop;
    defaults.timeout = 0;
    defaults.headers = {};
    defaults.cache = typeof window === 'undefined' ? false : !!(window.ActiveXObject || 'ActiveXObject' in window); // By default; only enabled on old IE (also the presence of ActiveXObject is a nice correlation with the cache bug)
    defaults.method = 'GET';
    defaults.base = '';
    defaults.credentials = false;
    defaults.FormData = FormData;
  }

  function assignDefaults(options) {
    Object.keys(defaults).forEach(key=> {
      if (options[key] === undef) {
        options[key] = copy(defaults[key]);
      }
    });
  }

  function serializeQuery(params) {
    return Object.keys(params).reduce((query, param)=> {
      if (params.hasOwnProperty(param) && params[param] !== undef) {
        query.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
      }
      return query;
    }, []).join('&');
  }

  function initXHR () {
    if (XMLHttpRequest) {
      return new XMLHttpRequest();
    }

    try { return new global.ActiveXObject('Msxml2.XMLHTTP.6.0'); }
    catch (e) {}
    try { return new global.ActiveXObject('Msxml2.XMLHTTP.3.0'); }
    catch (e) {}
    try { return new global.ActiveXObject('Microsoft.XMLHTTP'); }
    catch (e) {}

    throw new Error('I have no idea which browser you are using... but there is no such thing as XMLHttpRequest in it.');
  }

  // The main logic of the lib: sending a XMLHttpRequest
  function send(options) {
    assignDefaults(options);
    const xhr = initXHR();

    const request = new Promise((resolve, reject)=> {
      xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) { return; }

        try {
          const status = (xhr.status === 1223) ? 204 : xhr.status;

          if (!status) {
            if (canceled) {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => XHR canceled`});
              reject(new CancelError());
            } else if (timedout) {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => XHR timed out after ${options.timeout}ms`});
              reject(new TimeoutError(options.timeout));
            } else {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => XHR error`});
              reject(new NetworkError());
            }
          } else {
            if (status < 100 || status > 599) {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => ${status}`});
              reject(new NetworkError());
            } else {
              grab.defaults.log({ok: true, message: `grab: ${options.method} ${options.url} => error ${status}`});
              resolve(new Response(xhr, status, options.FormData));
            }
          }
        } catch (e) {
          reject(e); // IE could throw an error
        }
      };

      if (options.onProgress) {
        xhr.onprogress = function (progress) {
          options.onProgress(progress);
        };
      }

      // Append query params before opening
      if (options.params === undef) {
        options.params = {};
      }

      const cacheParam = options.cache === undef ? grab.defaults.cache : options.cache;

      if (cacheParam) {
        options.params[cacheParam === true ? '_' : cacheParam] = (new Date()).getTime();
      }

      const serializedParams = serializeQuery(options.params);

      const url = (options.base || '')
        + options.url
        + (serializedParams ?
          (hasQuery(options.url) ? '&' : '?') + serializedParams :
          '');

      // Open the XHR
      xhr.open(options.method, url, true, options.username, options.password);

      if (options.responseType) {
        xhr.responseType = options.responseType;
      }

      // Init headers
      if (options.headers === undef) {
        options.headers = {};
      }

      // Credentials
      if (options.credentials === false) {
        // Take priority over anything else => no credentials
      } else if (options.credentials) {
        xhr.withCredentials = true;
      }

      // Body
      if (
        options.body !== null &&
        typeof options.body === 'object' &&
        (!options.FormData || !(options.body instanceof options.FormData))
      ) {
        if (options.urlEncoded) {
          if (!(CONTENT_TYPE in options.headers)) {
            options.headers[CONTENT_TYPE] = 'application/x-www-form-urlencoded';
          }
          options.body = serializeQuery(options.body);
        } else {
          if (!(CONTENT_TYPE in options.headers)) {
            options.headers[CONTENT_TYPE] = 'application/json';
          }
          options.body = JSON.stringify(options.body);
        }
      }

      // Set headers
      Object.keys(options.headers).forEach(header => {
        xhr.setRequestHeader(header, options.headers[header]);
      });

      // Send the request
      if (options.body !== undef && options.body !== null) {
        xhr.send(options.body);
      } else {
        xhr.send();
      }
    });

    // Timeout and abort
    let canceled = false;
    let timedout = false;
    let timeout;
    if (options.timeout) {
      timeout = setTimeout(()=> {
        timedout = true;
        xhr.abort();
      }, options.timeout);
    }

    function cancelRequest() {
      canceled = true;
      if (typeof timeout === 'number') {
        clearTimeout(timeout);
      }
      xhr.abort();
    }

    // User cancellation
    if (options.cancel && options.cancel.then) {
      options.cancel.then((value)=> {
        cancelRequest();
        return value;
      });
    }

    // Expose cancel method
    request.cancel = function cancel() {
      cancelRequest();
    };

    return request;
  }

  function grab(input, options = {}) {
    if (input === undef) {
      fail('come on... there is no way do make a request without any arguments...');
    }

    if (typeof input === 'string') {
      if (typeof options !== 'object') fail('second argument "options" must be an object if the first one is a string.');
      options.url = input;
    } else if (typeof input === 'object') {
      options = input;
    } else {
      fail('grab: first argument "input" must be a string or an object but got ' + (typeof input));
    }

    if (!options.url) {
      fail('you need to provide an url, either with a string "input" or an "url" key inside the "options" object.');
    }

    if (options.onProgress && typeof options.onProgress !== 'function') {
      fail('onProgress must be a function');
    }

    if (options.timeout !== undef && (typeof options.timeout !== 'number' || options.timeout < 0)) {
      fail('timeout must be a positive number');
    }

    if (options.cancel !== undef && typeof options.cancel.then !== 'function') {
      fail('cancel must be a Promise with, at least, a "then" method');
    }

    return send(options);
  }

  // Shortcuts
  function assignShortcut(method, url, body, options) {
    if (!options) {
      options = {};
    }

    options.url = url;
    options.method = method;

    if (body !== undef) {
      options.body = body;
    }

    return options;
  }

  grab.get = function get(url, options) {
    options = assignShortcut('GET', url, undef, options);
    return grab(options);
  };

  // Not a typo, 'delete' is a reserved word
  grab['delete'] = function delet(url, options) {
    options = assignShortcut('DELETE', url, undef, options);
    return grab(options);
  };

  grab.options = function options(url, options) {
    options = assignShortcut('OPTIONS', url, undef, options);
    return grab(options);
  };

  grab.head = function head(url, options) {
    options = assignShortcut('HEAD', url, undef, options);
    return grab(options);
  };

  grab.trace = function trace(url, options) {
    options = assignShortcut('TRACE', url, undef, options);
    return grab(options);
  };

  grab.connect = function connect(url, options) {
    options = assignShortcut('CONNECT', url, undef, options);
    return grab(options);
  };

  grab.post = function post(url, body, options) {
    options = assignShortcut('POST', url, body, options);
    return grab(options);
  };

  grab.put = function put(url, body, options) {
    options = assignShortcut('PUT', url, body, options);
    return grab(options);
  };

  grab.patch = function patch(url, body, options) {
    options = assignShortcut('PATCH', url, body, options);
    return grab(options);
  };

  // Util methods
  grab.serialize = serializeQuery;

  grab.resetDefaults = resetDefaults;

  // Types
  grab.Response = Response;

  grab.NetworkError = NetworkError;
  grab.AbortError = AbortError;
  grab.TimeoutError = TimeoutError;
  grab.CancelError = CancelError;

  grab.errors = errors;

  // Expose defaults
  grab.defaults = defaults;

  resetDefaults();

  return grab;
};
<commit_msg>Fix bugs around strict Safari and FormData<commit_after>// Constants and utils
const CONTENT_TYPE = 'Content-Type';
const errors = {
  network: 'NETWORK',
  abort: 'ABORT',
  cancel: 'CANCEL',
  timeout: 'TIMEOUT'
};

function noop() {}

function copy(value) {
  if (value !== null && typeof value === 'object') {
    const result = {};
    for (let key in value) {
      result[key] = copy(value[key]);
    }
    return result;
  } else {
    return value;
  }
}

function hasQuery(url) {
  return (url.indexOf('?') > -1);
}

function fail(message) {
  throw new TypeError('grab: ' + message);
}

function extractHeaders(xhr) {
  const tuples = xhr.getAllResponseHeaders().trim().split('\n');
  return tuples.reduce((headers, tuple)=> {
    const keyValue = tuple.split(':');
    if (keyValue[0]) {
      headers[keyValue.shift().trim()] = keyValue.join(':').trim();
    }
    return headers;
  }, {});
}

// Errors
class NetworkError extends Error {
  constructor () {
    super();
    this.name = 'NetworkError';
    this.code = errors.network;
    this.message = 'Network request failed';
  }
}

class AbortError extends Error {
  constructor () {
    super();
    this.name = 'AbortError';
    this.code = errors.abort;
    this.message = 'Request was aborted for an unknow reason';
  }
}

class CancelError extends AbortError {
  constructor () {
    super();
    this.name = 'CancelError';
    this.code = errors.cancel;
    this.message = 'Request canceled by user';
  }
}

class TimeoutError extends AbortError {
  constructor (duration) {
    super();
    this.name = 'CancelError';
    this.code = errors.timeout;
    this.message = `Request timeout after ${duration}ms`;
  }
}

function parseFormData(body, FormData) {
  if (!FormData) {
    fail('FormData is not supported');
  }

  const form = new FormData();
  body.trim().split('&').forEach(function (params) {
    if (params) {
      const keyValue = params.split('=');
      const key = keyValue.shift().replace(/\+/g, ' ');
      const value = keyValue.join('=').replace(/\+/g, ' ');
      form.append(decodeURIComponent(key), decodeURIComponent(value))
    }
  });
  return form;
}

class Response {
  constructor (xhr, status, FormData) {
    this.type = 'default';
    this.status = status;
    this.statusText = xhr.statusText;
    this.ok = this.status >= 200 && this.status < 300;
    this.headers = extractHeaders(xhr);
    this.body = 'response' in xhr ? xhr.response : xhr.responseText;
    this.FormData = FormData;
  }

  text () {
    return this.body;
  }

  json () {
    if (!this.bodyJSON) {
      this.bodyJSON = JSON.parse(this.text());
    }

    return this.bodyJSON;
  }

  formData () {
    if (!this.bodyFormData) {
      this.bodyFormData = parseFormData(this.text(), this.FormData)
    }

    return this.bodyFormData;
  }
}

// The real deal
export default function grabFactory(global, XMLHttpRequest, FormData, undef) {
  const defaults = {};

  function resetDefaults() {
    defaults.log = noop;
    defaults.timeout = 0;
    defaults.headers = {};
    defaults.cache = typeof window === 'undefined' ? false : !!(window.ActiveXObject || 'ActiveXObject' in window); // By default; only enabled on old IE (also the presence of ActiveXObject is a nice correlation with the cache bug)
    defaults.method = 'GET';
    defaults.base = '';
    defaults.credentials = false;
    defaults.FormData = FormData;
  }

  function assignDefaults(options) {
    Object.keys(defaults).forEach(key=> {
      if (options[key] === undef && key !== 'FormData') {
        options[key] = copy(defaults[key]);
      }
    });

    // We need to treat FormData differently since we
    // don't want to clone the object but keep the constructor as it is
    if (options.FormData === undef) {
      options.FormData = defaults.FormData;
    }
  }

  function serializeQuery(params) {
    return Object.keys(params).reduce((query, param)=> {
      if (params.hasOwnProperty(param) && params[param] !== undef) {
        query.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
      }
      return query;
    }, []).join('&');
  }

  function initXHR () {
    if (XMLHttpRequest) {
      return new XMLHttpRequest();
    }

    try { return new global.ActiveXObject('Msxml2.XMLHTTP.6.0'); }
    catch (e) {}
    try { return new global.ActiveXObject('Msxml2.XMLHTTP.3.0'); }
    catch (e) {}
    try { return new global.ActiveXObject('Microsoft.XMLHTTP'); }
    catch (e) {}

    throw new Error('I have no idea which browser you are using... but there is no such thing as XMLHttpRequest in it.');
  }

  // The main logic of the lib: sending a XMLHttpRequest
  function send(options) {
    assignDefaults(options);
    const xhr = initXHR();

    const request = new Promise((resolve, reject)=> {
      xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) { return; }

        try {
          const status = (xhr.status === 1223) ? 204 : xhr.status;

          if (!status) {
            if (canceled) {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => XHR canceled`});
              reject(new CancelError());
            } else if (timedout) {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => XHR timed out after ${options.timeout}ms`});
              reject(new TimeoutError(options.timeout));
            } else {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => XHR error`});
              reject(new NetworkError());
            }
          } else {
            if (status < 100 || status > 599) {
              grab.defaults.log({ok: false, message: `grab: ${options.method} ${options.url} => ${status}`});
              reject(new NetworkError());
            } else {
              grab.defaults.log({ok: true, message: `grab: ${options.method} ${options.url} => error ${status}`});
              resolve(new Response(xhr, status, options.FormData));
            }
          }
        } catch (e) {
          reject(e); // IE could throw an error
        }
      };

      if (options.onProgress) {
        xhr.onprogress = function (progress) {
          options.onProgress(progress);
        };
      }

      // Append query params before opening
      if (options.params === undef) {
        options.params = {};
      }

      const cacheParam = options.cache === undef ? grab.defaults.cache : options.cache;

      if (cacheParam) {
        options.params[cacheParam === true ? '_' : cacheParam] = (new Date()).getTime();
      }

      const serializedParams = serializeQuery(options.params);

      const url = (options.base || '')
        + options.url
        + (serializedParams ?
          (hasQuery(options.url) ? '&' : '?') + serializedParams :
          '');

      // Open the XHR
      xhr.open(options.method, url, true, options.username, options.password);

      if (options.responseType) {
        xhr.responseType = options.responseType;
      }

      // Init headers
      if (options.headers === undef) {
        options.headers = {};
      }

      // Credentials
      if (options.credentials === false) {
        // Take priority over anything else => no credentials
      } else if (options.credentials) {
        xhr.withCredentials = true;
      }

      // Body
      if (
        options.body !== null &&
        typeof options.body === 'object' &&
        (!options.FormData || !(options.body instanceof options.FormData))
      ) {
        if (options.urlEncoded) {
          if (!(CONTENT_TYPE in options.headers)) {
            options.headers[CONTENT_TYPE] = 'application/x-www-form-urlencoded';
          }
          options.body = serializeQuery(options.body);
        } else {
          if (!(CONTENT_TYPE in options.headers)) {
            options.headers[CONTENT_TYPE] = 'application/json';
          }
          options.body = JSON.stringify(options.body);
        }
      }

      // Set headers
      Object.keys(options.headers).forEach(header => {
        xhr.setRequestHeader(header, options.headers[header]);
      });

      // Send the request
      if (options.body !== undef && options.body !== null) {
        xhr.send(options.body);
      } else {
        xhr.send();
      }
    });

    // Timeout and abort
    let canceled = false;
    let timedout = false;
    let timeout;
    if (options.timeout) {
      timeout = setTimeout(()=> {
        timedout = true;
        xhr.abort();
      }, options.timeout);
    }

    function cancelRequest() {
      canceled = true;
      if (typeof timeout === 'number') {
        clearTimeout(timeout);
      }
      xhr.abort();
    }

    // User cancellation
    if (options.cancel && options.cancel.then) {
      options.cancel.then((value)=> {
        cancelRequest();
        return value;
      });
    }

    // Expose cancel method
    request.cancel = function cancel() {
      cancelRequest();
    };

    return request;
  }

  function grab(input, options = {}) {
    if (input === undef) {
      fail('come on... there is no way do make a request without any arguments...');
    }

    if (typeof input === 'string') {
      if (typeof options !== 'object') fail('second argument "options" must be an object if the first one is a string.');
      options.url = input;
    } else if (typeof input === 'object') {
      options = input;
    } else {
      fail('grab: first argument "input" must be a string or an object but got ' + (typeof input));
    }

    if (!options.url) {
      fail('you need to provide an url, either with a string "input" or an "url" key inside the "options" object.');
    }

    if (options.onProgress && typeof options.onProgress !== 'function') {
      fail('onProgress must be a function');
    }

    if (options.timeout !== undef && (typeof options.timeout !== 'number' || options.timeout < 0)) {
      fail('timeout must be a positive number');
    }

    if (options.cancel !== undef && typeof options.cancel.then !== 'function') {
      fail('cancel must be a Promise with, at least, a "then" method');
    }

    return send(options);
  }

  // Shortcuts
  function assignShortcut(method, url, body, options) {
    if (!options) {
      options = {};
    }

    options.url = url;
    options.method = method;

    if (body !== undef) {
      options.body = body;
    }

    return options;
  }

  grab.get = function get(url, options) {
    options = assignShortcut('GET', url, undef, options);
    return grab(options);
  };

  // Not a typo, 'delete' is a reserved word
  grab['delete'] = function delet(url, options) {
    options = assignShortcut('DELETE', url, undef, options);
    return grab(options);
  };

  // Not a typo either, Safari didn't like having a parameter named 'options'
  // as it shadows the name of a strict mode function.
  grab.options = function options(url, opts) {
    opts = assignShortcut('OPTIONS', url, undef, opts);
    return grab(opts);
  };

  grab.head = function head(url, options) {
    options = assignShortcut('HEAD', url, undef, options);
    return grab(options);
  };

  grab.trace = function trace(url, options) {
    options = assignShortcut('TRACE', url, undef, options);
    return grab(options);
  };

  grab.connect = function connect(url, options) {
    options = assignShortcut('CONNECT', url, undef, options);
    return grab(options);
  };

  grab.post = function post(url, body, options) {
    options = assignShortcut('POST', url, body, options);
    return grab(options);
  };

  grab.put = function put(url, body, options) {
    options = assignShortcut('PUT', url, body, options);
    return grab(options);
  };

  grab.patch = function patch(url, body, options) {
    options = assignShortcut('PATCH', url, body, options);
    return grab(options);
  };

  // Util methods
  grab.serialize = serializeQuery;

  grab.resetDefaults = resetDefaults;

  // Types
  grab.Response = Response;

  grab.NetworkError = NetworkError;
  grab.AbortError = AbortError;
  grab.TimeoutError = TimeoutError;
  grab.CancelError = CancelError;

  grab.errors = errors;

  // Expose defaults
  grab.defaults = defaults;

  resetDefaults();

  return grab;
};
<|endoftext|>


<commit_before>module.exports = function (config) {
    config.set({
        frameworks: ['jasmine'],

        basePath: '../',

        files: [
            'app/vendor/angular/angular.js',
            'app/vendor/angular/angular-*.js',
            'app/vendor/*.js',
            'app/js/**/*.js',
            'app/js/controllers/**/*.js',
            'test/vendor/angular/angular-mocks.js',
            'test/vendor/jquery-2.0.3.js',
            'test/vendor/jasmine-jquery.js',
            'test/unit/**/*.js'

            // fixtures
//            {pattern: 'test/mock/*.json', watched: true, served: true, included: false}
        ],

        autoWatch: false,
        singleRun: true,

        browsers: ['Firefox'],

        junitReporter: {
          outputFile: 'test_out/unit.xml',
          suite: 'unit'
        },

        reporters: ['progress', 'coverage'],
        preprocessors: {
            'app/js/**/*.js': ['coverage']
        }
    });
};
<commit_msg>test config update<commit_after>module.exports = function (config) {
    config.set({
        frameworks: ['jasmine'],

        basePath: '../',

        files: [
            'app/vendor/angular/angular.js',
            'app/vendor/angular/angular-*.js',
            'app/vendor/*.js',
            'app/js/**/*.js',
            'app/js/controllers/**/*.js',
            'test/vendor/angular/angular-mocks.js',
            'test/vendor/jquery-2.0.3.js',
            'test/vendor/jasmine-jquery.js',
            'test/unit/**/*.js',

            // fixtures
            {pattern: 'test/mock/*.json', watched: true, served: true, included: false}
        ],

        autoWatch: false,
        singleRun: true,

        browsers: ['Firefox'],

        junitReporter: {
          outputFile: 'test_out/unit.xml',
          suite: 'unit'
        },

        reporters: ['progress', 'coverage'],
        preprocessors: {
            'app/js/**/*.js': ['coverage']
        }
    });
};
<|endoftext|>