<commit_before>var get_top_choice = function(choices) {
}

var get_json = function(data) {
	return data;
}

exports.get_info_sheet = function(material, all_objects){
	var unirest = require('unirest');
	var url = 'http://data.countyofriverside.us/resource/4yyp-e7qt.json?$q='.concat(material);
	unirest.get(url)
				.end(function (data) {
					var material_info = [
						{"info_sheet" : data['body'],
						"location_information" : all_objects}];
						console.log(material_info[0].location_information.length);
					return material;
				});
}

exports.get_greeenwaste_recyclers = function(material, all_objects, info_sheet_function) {
	var unirest = require('unirest');
	var url = 'http://data.countyofriverside.us/resource/76h8-zrhh.json?$q='.concat(material);
	unirest.get(url)
			.end(function (data) {
				for (var i = 0; i < data['body'].length; i++) {
					var addr_json = JSON.parse(data['body'][i]['location_1']['human_address'])
					var obj_info = {"phone" : data['body'][i]['phone'],
									"facility" : data['body'][i]['facility'],
									"location_1" : {
										"latitude" : data['body'][i]['location_1']['latitude'],
										"longitude" : data['body'][i]['location_1']['longitude'],
										"human_address" : addr_json,
									},
								};
					all_objects.push(obj_info);
				}
				info_sheet_function(material, all_objects);
			});
}

exports.get_locations = function(input, info_sheet_function, greenwaste_function) {
  // assumes input is a material that is listed on the database material_accepted
  var unirest = require('unirest');
  var info_sheet;
  var url = 'http://data.countyofriverside.us/resource/fp33-e7hd.json?$q='.concat(input);
  unirest.get(url)
  	.end(function (data) {
  		var all_objects = [];
  		var data_json = JSON.parse(data['raw_body']);

  		for (var i = 0; i < data_json.length; i++ ) {
  			var result = [];
  			var addr_json = JSON.parse(data_json[i]['location_1']['human_address']);
  			var obj_info = {"phone" : data_json[i]['phone'],
  							"facility" : data_json[i]['facility'],
  							"location_1" : {
  								"latitude" : data_json[i]['location_1']['latitude'],
  								"longitude" : data_json[i]['location_1']['longitude'],
  								"human_address" : addr_json,
  							},
  						};

  			all_objects.push(obj_info);
		}
		greenwaste_function(input, all_objects, info_sheet_function);
	});

}

<commit_msg>calculates distance from each service location to current location<commit_after>var sort_distances = function (distances, locations) {

}

var get_distance = function ( i, locations, current_location, distances) {

	var distance = require('google-distance');

	var lat = locations[i].location_1.latitude;
	var longitude = locations[i].location_1.longitude;
	var my_origin = lat.concat(',').concat(longitude);

	var dest_lat = current_location[0].latitude;
	var dest_long = current_location[0].longitude;
	var my_destination = dest_lat.concat(',').concat(dest_long);

	distance.get(
	{
		origin: my_origin,
		destination: my_destination,
		units: 'imperial'
	},
	function(err, data) {
		if (err) return console.log(err);
		my_obj = {"duration" : data.durationValue, "index" : i}
		distances.push(my_obj);

		if (i+1 < locations.length) {
			get_distance(i+1, locations, current_location, distances);
		}
		else {
			console.log(distances);
		}
	});
}

var get_top_choice = function(locations, current_location) {
	// assume current_location is json with "latitude" and "longitude" entries
	// locations is json where lat/long are under "location_1" key
	var distances = [];
	get_distance(0, locations, current_location, distances);

}

var get_json = function(data) {
	return data;
}

exports.get_info_sheet = function(material, all_objects){
	var unirest = require('unirest');
	var url = 'http://data.countyofriverside.us/resource/4yyp-e7qt.json?$q='.concat(material);
	unirest.get(url)
				.end(function (data) {
					var material_info = [
						{"info_sheet" : data['body'],
						"location_information" : all_objects}];
					var current_location = [{
						"latitude": "33.948769",
						 "longitude": "-117.380940"}];
					 // console.log(material_info[0].location_information[0]);
					get_top_choice(material_info[0].location_information, current_location);
					// return material;
				});
}

exports.get_greeenwaste_recyclers = function(material, all_objects, info_sheet_function) {
	var unirest = require('unirest');
	var url = 'http://data.countyofriverside.us/resource/76h8-zrhh.json?$q='.concat(material);
	unirest.get(url)
			.end(function (data) {
				for (var i = 0; i < data['body'].length; i++) {
					var addr_json = JSON.parse(data['body'][i]['location_1']['human_address'])
					var obj_info = {"phone" : data['body'][i]['phone'],
									"facility" : data['body'][i]['facility'],
									"location_1" : {
										"latitude" : data['body'][i]['location_1']['latitude'],
										"longitude" : data['body'][i]['location_1']['longitude'],
										"human_address" : addr_json,
									},
								};
					all_objects.push(obj_info);
				}
				info_sheet_function(material, all_objects);
			});
}

exports.get_locations = function(input, info_sheet_function, greenwaste_function) {
  // assumes input is a material that is listed on the database material_accepted
  var unirest = require('unirest');
  var info_sheet;
  var url = 'http://data.countyofriverside.us/resource/fp33-e7hd.json?$q='.concat(input);
  unirest.get(url)
  	.end(function (data) {
  		var all_objects = [];
  		var data_json = JSON.parse(data['raw_body']);

  		for (var i = 0; i < data_json.length; i++ ) {
  			var result = [];
  			var addr_json = JSON.parse(data_json[i]['location_1']['human_address']);
  			var obj_info = {"phone" : data_json[i]['phone'],
  							"facility" : data_json[i]['facility'],
  							"location_1" : {
  								"latitude" : data_json[i]['location_1']['latitude'],
  								"longitude" : data_json[i]['location_1']['longitude'],
  								"human_address" : addr_json,
  							},
  						};

  			all_objects.push(obj_info);
		}
		greenwaste_function(input, all_objects, info_sheet_function);
	});

}

<|endoftext|>


<commit_before>const datePickerStyle = {

  // wrapper of entire component
  style: {
    borderRadius: 2,
    width: 267,
    textAlign: 'center',
    boxSizing: 'border-box',
    backgroundColor: 'white',

    /*
     To avoid any kind of flickering the user won't get feedback
     for selecting the button text
     */
    WebkitUserSelect: 'none',
    MozUserSelect: 'none',
    MsUserSelect: 'none',
    userSelect: 'none',

    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',

    transition: 'color 0.1s',
    transitionTimingFunction: 'ease-out',
  },

  disabledStyle: {
    backgroundColor: '#D8D8D8',
  },

  readOnlyStyle: {
  },

  hoverStyle: {
  },

  focusStyle: {
    boxShadow: '0 0 0 2px rgba(140, 224, 255, 0.6) inset',
    outline: 0,
  },

  disabledHoverStyle: {
    backgroundColor: '#E1E9EC',
  },

  // nav-bar at top for month navigation
  navBarStyle: {
    height: 30,
    lineHeight: '30px',
    border: '1px solid #E0E0E0',
  },

  // left button in nav-bar to go to previous month
  prevMonthNavStyle: {
    cursor: 'pointer',
    float: 'left',

    /*
     To avoid any kind of flickering the user won't get feedback
     for selecting the button text
     */
    WebkitUserSelect: 'none',
    MozUserSelect: 'none',
    MsUserSelect: 'none',
    userSelect: 'none',

    /* This button can only be pressed */
    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',
  },

  hoverPrevMonthNavStyle: {
  },

  activePrevMonthNavStyle: {
  },

  // right button in nav-bar to go to previous month
  nextMonthNavStyle: {
    cursor: 'pointer',
    float: 'right',

    /*
     To avoid any kind of flickering the user won't get feedback
     for selecting the button text
     */
    WebkitUserSelect: 'none',
    MozUserSelect: 'none',
    MsUserSelect: 'none',
    userSelect: 'none',

    /* This button can only be pressed */
    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',
  },

  hoverNextMonthNavStyle: {
  },

  activeNextMonthNavStyle: {
  },

  // styling for month label on top of calendar
  monthLabelStyle: {
    fontSize: 15,
    width: 125,
    boxSizing: 'border-box',

    /*
     User should be able to copy date.
     */
    WebkitUserSelect: 'initial',
    MozUserSelect: 'initial',
    MsUserSelect: 'initial',
    userSelect: 'initial',
  },

  // styling for entire grid of week-header and weeks
  weekGroupStyle: {
    boxSizing: 'border-box',
    overflow: 'auto',
    paddingBottom: 1,
  },

  weekHeaderStyle: {
    overflow: 'auto',
    boxSizing: 'border-box',
    boxShadow: '1px 0 0 0 #E0E0E0 inset, -1px 0 0 0 #E0E0E0 inset',
  },

  // styling for week's day label
  dayLabelStyle: {
    width: 39,
    height: 30,
    padding: 0,
    marginRight: -1,
    lineHeight: '30px',
    color: '#716D6D',
    display: 'block',
    float: 'left',
    boxSizing: 'border-box',

    /*
     User should be able to copy date.
     */
    WebkitUserSelect: 'initial',
    MozUserSelect: 'initial',
    MsUserSelect: 'initial',
    userSelect: 'initial',
  },

  disabledDayLabelStyle: {
    color: '#C1BABA',
  },

  weekendLabelStyle: {
    backgroundColor: '#F0F0F0',
  },

  // styling for individual day
  dayStyle: {
    width: 39,
    height: 30,
    lineHeight: '30px',
    cursor: 'pointer',
    border: '1px solid #E0E0E0',
    color: '#716D6D',
    display: 'block',
    float: 'left',
    padding: 0,
    marginRight: -1,
    marginBottom: -1,
    boxSizing: 'border-box',
    position: 'relative',
    zIndex: 100,

    /*
     User should be able to copy date.
     */
    WebkitUserSelect: 'initial',
    MozUserSelect: 'initial',
    MsUserSelect: 'initial',
    userSelect: 'initial',

    /* This button can only be pressed */
    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',

    transition: 'color 0.1s',
    transitionTimingFunction: 'ease-out',
  },

  disabledDayStyle: {
    color: '#C1BABA',
    cursor: 'auto',
  },

  readOnlyDayStyle: {
    cursor: 'auto',
  },

  hoverDayStyle: {
    border: '1px solid #E0E0E0',
    background: '#E0E0E0',
    color: '#616161',
  },

  activeDayStyle: {
    boxShadow: 'none',
    border: '1px solid #E0E0E0',
    borderTop: '1px solid #BDBDBD',
    background: '#E0E0E0',
    color: '#424242',
  },

  focusDayStyle: {
    border: '1px solid rgba(140, 224, 255, 0.8)',
    zIndex: 200,
  },

  disabledHoverDayStyle: {
    cursor: 'auto',
  },

  todayStyle: {
    color: '#2C87A9',
  },

  weekendStyle: {
    backgroundColor: '#F0F0F0',
  },

  selectedDayStyle: {
    boxShadow: 'none',
    border: '1px solid #E0E0E0',
    borderTop: '1px solid #BDBDBD',
    background: '#E0E0E0',
    color: '#424242',
  },

  otherMonthDayStyle: {
    color: '#BDBDBD',
  },
};

export default datePickerStyle;
<commit_msg>style(DatePicker): fix styling issue in docs<commit_after>const datePickerStyle = {

  // wrapper of entire component
  style: {
    borderRadius: 2,
    width: 267,
    textAlign: 'center',
    boxSizing: 'border-box',
    backgroundColor: 'white',

    /*
     To avoid any kind of flickering the user won't get feedback
     for selecting the button text
     */
    WebkitUserSelect: 'none',
    MozUserSelect: 'none',
    MsUserSelect: 'none',
    userSelect: 'none',

    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',

    transition: 'color 0.1s',
    transitionTimingFunction: 'ease-out',
  },

  disabledStyle: {
    backgroundColor: '#D8D8D8',
  },

  readOnlyStyle: {
  },

  hoverStyle: {
  },

  focusStyle: {
    boxShadow: '0 0 0 2px rgba(140, 224, 255, 0.6) inset',
    outline: 0,
  },

  disabledHoverStyle: {
    backgroundColor: '#E1E9EC',
  },

  // nav-bar at top for month navigation
  navBarStyle: {
    height: 30,
    border: '1px solid #E0E0E0',
  },

  // left button in nav-bar to go to previous month
  prevMonthNavStyle: {
    cursor: 'pointer',
    float: 'left',

    /*
     To avoid any kind of flickering the user won't get feedback
     for selecting the button text
     */
    WebkitUserSelect: 'none',
    MozUserSelect: 'none',
    MsUserSelect: 'none',
    userSelect: 'none',

    /* This button can only be pressed */
    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',
  },

  hoverPrevMonthNavStyle: {
  },

  activePrevMonthNavStyle: {
  },

  // right button in nav-bar to go to previous month
  nextMonthNavStyle: {
    cursor: 'pointer',
    float: 'right',

    /*
     To avoid any kind of flickering the user won't get feedback
     for selecting the button text
     */
    WebkitUserSelect: 'none',
    MozUserSelect: 'none',
    MsUserSelect: 'none',
    userSelect: 'none',

    /* This button can only be pressed */
    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',
  },

  hoverNextMonthNavStyle: {
  },

  activeNextMonthNavStyle: {
  },

  // styling for month label on top of calendar
  monthLabelStyle: {
    fontSize: 15,
    width: 125,
    boxSizing: 'border-box',

    /*
     User should be able to copy date.
     */
    WebkitUserSelect: 'initial',
    MozUserSelect: 'initial',
    MsUserSelect: 'initial',
    userSelect: 'initial',
  },

  // styling for entire grid of week-header and weeks
  weekGroupStyle: {
    boxSizing: 'border-box',
    overflow: 'auto',
    paddingBottom: 1,
  },

  weekHeaderStyle: {
    overflow: 'auto',
    boxSizing: 'border-box',
    boxShadow: '1px 0 0 0 #E0E0E0 inset, -1px 0 0 0 #E0E0E0 inset',
  },

  // styling for week's day label
  dayLabelStyle: {
    width: 39,
    height: 30,
    padding: 0,
    marginRight: -1,
    lineHeight: '30px',
    color: '#716D6D',
    display: 'block',
    float: 'left',
    boxSizing: 'border-box',

    /*
     User should be able to copy date.
     */
    WebkitUserSelect: 'initial',
    MozUserSelect: 'initial',
    MsUserSelect: 'initial',
    userSelect: 'initial',
  },

  disabledDayLabelStyle: {
    color: '#C1BABA',
  },

  weekendLabelStyle: {
    backgroundColor: '#F0F0F0',
  },

  // styling for individual day
  dayStyle: {
    width: 39,
    height: 30,
    lineHeight: '30px',
    cursor: 'pointer',
    border: '1px solid #E0E0E0',
    color: '#716D6D',
    display: 'block',
    float: 'left',
    padding: 0,
    marginRight: -1,
    marginBottom: -1,
    boxSizing: 'border-box',
    position: 'relative',
    zIndex: 100,

    /*
     User should be able to copy date.
     */
    WebkitUserSelect: 'initial',
    MozUserSelect: 'initial',
    MsUserSelect: 'initial',
    userSelect: 'initial',

    /* This button can only be pressed */
    MsTouchAction: 'manipulation',
    touchAction: 'manipulation',

    /*
     Prevent flickering while tapping on WebKit
     http://stackoverflow.com/a/3516243/837709
     */
    WebkitTapHighlightColor: 'transparent',

    transition: 'color 0.1s',
    transitionTimingFunction: 'ease-out',
  },

  disabledDayStyle: {
    color: '#C1BABA',
    cursor: 'auto',
  },

  readOnlyDayStyle: {
    cursor: 'auto',
  },

  hoverDayStyle: {
    border: '1px solid #E0E0E0',
    background: '#E0E0E0',
    color: '#616161',
  },

  activeDayStyle: {
    boxShadow: 'none',
    border: '1px solid #E0E0E0',
    borderTop: '1px solid #BDBDBD',
    background: '#E0E0E0',
    color: '#424242',
  },

  focusDayStyle: {
    border: '1px solid rgba(140, 224, 255, 0.8)',
    zIndex: 200,
  },

  disabledHoverDayStyle: {
    cursor: 'auto',
  },

  todayStyle: {
    color: '#2C87A9',
  },

  weekendStyle: {
    backgroundColor: '#F0F0F0',
  },

  selectedDayStyle: {
    boxShadow: 'none',
    border: '1px solid #E0E0E0',
    borderTop: '1px solid #BDBDBD',
    background: '#E0E0E0',
    color: '#424242',
  },

  otherMonthDayStyle: {
    color: '#BDBDBD',
  },
};

export default datePickerStyle;
<|endoftext|>


<commit_before>/*!
GPII Data Source

Copyright 2012 OCAD University

Licensed under the New BSD license. You may not use this file except in
compliance with this License.

You may obtain a copy of the License at
https://github.com/gpii/universal/LICENSE.txt
*/

(function () {

    "use strict";

    var fluid = require("infusion"),
        gpii = fluid.registerNamespace("gpii"),
        fs = require("fs"),
        http = require("http"),
        url = require("url"),
        eUC = "encodeURIComponent:";

    fluid.defaults("gpii.dataSource", {
        gradeNames: ["autoInit", "fluid.littleComponent"],
        components: {
            urlExpander: {
                type: "gpii.urlExpander"
            }
        },
        invokers: {
            get: "gpii.dataSource.get",
            resolveUrl: "gpii.dataSource.resolveUrl",
            handleError: "gpii.dataSource.handleError"
        },
        nickName: "dataSource", // framework bug FLUID-4636 - this is not resolved
        termMap: {},
        writable: false,
        preInitFunction: "gpii.dataSource.preInit"
    });

    fluid.defaults("gpii.dataSource.URL", {
        gradeNames: ["gpii.dataSource", "autoInit"]
    });

    gpii.dataSource.preInit = function (that) {
        that.nickName = "dataSource"; // work around FLUID-4636
        if (that.options.writable) {
            that.options.invokers.set = "gpii.dataSource.set";
        }
    };

    fluid.demands("gpii.dataSource.get", "gpii.dataSource.URL", {
        funcName: "gpii.dataSource.URL.all",
        args: [
            "{dataSource}",
            "{arguments}.0",
            "{arguments}.1"
        ]
    });

    fluid.demands("gpii.dataSource.set", "gpii.dataSource.URL", {
        funcName: "gpii.dataSource.URL.all",
        args: [
            "{dataSource}",
            "{arguments}.0",
            "{arguments}.1",
            "{arguments}.2"
        ]
    });

    fluid.demands("gpii.dataSource.resolveUrl", null, {
        args: [
            "{urlExpander}.expand",
            "{dataSource}.options.url",
            "{dataSource}.options.termMap",
            "{arguments}.0"
        ]
    });

    fluid.demands("gpii.dataSource.handleError", "gpii.dataSource.URL", {
        funcName: "gpii.dataSource.handleError",
        args: ["{gpii.requests}", "{arguments}.0"]
    });

    var processData = function (data, dataSource, callback) {
        var responseParser = dataSource.options.responseParser,
            handleError = dataSource.handleError;
        data = typeof data === "string" ? JSON.parse(data) : data;
        if (data && data.isError) {
            handleError(data);
        }
        if (responseParser) {
            data = typeof responseParser === "string" ?
                fluid.invokeGlobalFunction(responseParser, [data]) :
                responseParser(data);
        }
        callback(data);
    };

    var urlAll = function (path, method, request, dataSource, callback, model) {
        var urlObj = url.parse(path, true),
            opts = {
                host: urlObj.hostname,
                port: parseInt(urlObj.port, 10),
                path: urlObj.path,
                method: method
            };
        if (model) {
            opts.headers = {
                "Content-Type": "application/json",
                "Content-Length": model.length
            };
        }
        var req = http.request(opts, function (res) {
            var data = "";
            res.setEncoding("utf8");
            res.on("data", function (chunk) {
                data += chunk;
            });
            res.on("end", gpii.callbackWrapper(request, function () {
                processData(data, dataSource, callback);
            }));
        });
        req.on("error", gpii.callbackWrapper(request, function (error) {
            dataSource.handleError({
                isError: true,
                message: error.message
            });
        }));
        req.end(model);
        return req;
    };

    var fsAll = function (fileName, method, request, dataSource, callback, model) {
        var args = [fileName];
        if (model) {
            args.push(model);
        }
        args.push("utf8");
        args.push(gpii.callbackWrapper(request, function (error, data) {
            if (error) {
                dataSource.handleError({
                    isError: true,
                    message: error.message
                });
                return;
            }
            processData(data || model, dataSource, callback);
        }));
        fs[method + "File"].apply(null, args);
    };

    var stringifyModel = function (model) {
        if (!model) {
            return;
        }
        return typeof model === "string" ? model : JSON.stringify(model);
    };

    gpii.dataSource.isFileProtocol = function(uri) {
        return /^file:\/\//.test(uri);
    };

    gpii.dataSource.URL.all = function (dataSource, directModel, model, callback) {
        var resolveUrl = dataSource.resolveUrl,
            url = resolveUrl(directModel),
            isFileProtocol = gpii.dataSource.isFileProtocol(url),
            method = "PUT",
            request = fluid.resolveEnvironment("{request}", {
                fetcher: fluid.makeEnvironmentFetcher()
            }),
            func = urlAll;
        if (!model || typeof model === "function") {
            callback = model;
            method = "GET";
        }
        if (isFileProtocol) {
            func = fsAll;
            url = url.substring(7);
            method = method === "PUT" ? "write" : "read"
        }
        model = stringifyModel(model);
        func.apply(null, [url, method, request, dataSource, callback, model]);
    };

    gpii.dataSource.handleError = function (requests, data) {
        requests.events.onError.fire(data);
    };

    gpii.dataSource.resolveUrl = function (expand, url, termMap, directModel) {
        var map = fluid.copy(termMap);
        map = fluid.transform(map, function (entry) {
            var encode = false;
            if (entry.indexOf(eUC) === 0) {
                encode = true;
                entry = entry.substring(eUC.length);
            }
            if (entry.charAt(0) === "%") {
                entry = fluid.get(directModel, entry.substring(1));
            }
            if (encode) {
                entry = encodeURIComponent(entry);
            }
            return entry;
        });
        var replaced = fluid.stringTemplate(url, map);
        replaced = expand(replaced);
        return replaced;
    };

})();<commit_msg>GPII-30: Data source now has an underlying promise infrastructure<commit_after>/*!
GPII Data Source

Copyright 2012 OCAD University

Licensed under the New BSD license. You may not use this file except in
compliance with this License.

You may obtain a copy of the License at
https://github.com/gpii/universal/LICENSE.txt
*/

(function () {

    "use strict";

    var fluid = require("infusion"),
        gpii = fluid.registerNamespace("gpii"),
        fs = require("fs"),
        http = require("http"),
        url = require("url"),
        when = require("when"),
        eUC = "encodeURIComponent:";

    fluid.defaults("gpii.dataSource", {
        gradeNames: ["autoInit", "fluid.littleComponent"],
        components: {
            urlExpander: {
                type: "gpii.urlExpander"
            }
        },
        invokers: {
            get: "gpii.dataSource.get",
            resolveUrl: "gpii.dataSource.resolveUrl",
            handleError: "gpii.dataSource.handleError"
        },
        nickName: "dataSource", // framework bug FLUID-4636 - this is not resolved
        termMap: {},
        writable: false,
        preInitFunction: "gpii.dataSource.preInit"
    });

    fluid.defaults("gpii.dataSource.URL", {
        gradeNames: ["gpii.dataSource", "autoInit"]
    });

    gpii.dataSource.preInit = function (that) {
        that.nickName = "dataSource"; // work around FLUID-4636
        if (that.options.writable) {
            that.options.invokers.set = "gpii.dataSource.set";
        }
    };

    fluid.demands("gpii.dataSource.get", "gpii.dataSource.URL", {
        funcName: "gpii.dataSource.URL.all",
        args: [
            "{dataSource}",
            "get",
            "{arguments}.0",
            "{arguments}.1"
        ]
    });

    fluid.demands("gpii.dataSource.set", "gpii.dataSource.URL", {
        funcName: "gpii.dataSource.URL.all",
        args: [
            "{dataSource}",
            "set",
            "{arguments}.0",
            "{arguments}.1",
            "{arguments}.2"
        ]
    });

    fluid.demands("gpii.dataSource.resolveUrl", null, {
        args: [
            "{urlExpander}.expand",
            "{dataSource}.options.url",
            "{dataSource}.options.termMap",
            "{arguments}.0"
        ]
    });

    fluid.demands("gpii.dataSource.handleError", "gpii.dataSource.URL", {
        funcName: "gpii.dataSource.handleError",
        args: ["{gpii.requests}", "{arguments}.0"]
    });

    var wrapProcessData = function (deferred, responseParser, callback) {
        return function (data) {
            data = typeof data === "string" ? JSON.parse(data) : data;
            if (data && data.isError) {
                deferred.reject(data);
            }
            if (responseParser) {
                data = typeof responseParser === "string" ?
                    fluid.invokeGlobalFunction(responseParser, [data]) :
                    responseParser(data);
            }
            callback(data);
        };
    };

    var urlAll = function (path, method, deferred, model) {
        var urlObj = url.parse(path, true),
            opts = {
                host: urlObj.hostname,
                port: parseInt(urlObj.port, 10),
                path: urlObj.path,
                method: method
            };
        if (model) {
            opts.headers = {
                "Content-Type": "application/json",
                "Content-Length": model.length
            };
        }
        var req = http.request(opts, function (res) {
            var data = "";
            res.setEncoding("utf8");
            res.on("data", function (chunk) {
                data += chunk;
            });
            res.on("end", function () {
                deferred.resolve(data);
            });
        });
        req.on("error", function (error) {
            deferred.reject({
                isError: true,
                message: error.message
            });
        });
        req.end(model);
        return req;
    };

    var fsAll = function (fileName, method, deferred, model) {
        var args = [fileName];
        if (model) {
            args.push(model);
        }
        args.push("utf8");
        args.push(function (error, data) {
            if (error) {
                deferred.reject({
                    isError: true,
                    message: error.message
                });
                return;
            }
            deferred.resolve(data || model);
        });
        fs[method + "File"].apply(null, args);
    };

    var stringifyModel = function (model) {
        if (!model) {
            return;
        }
        return typeof model === "string" ? model : JSON.stringify(model);
    };

    gpii.dataSource.isFileProtocol = function(uri) {
        return /^file:\/\//.test(uri);
    };

    gpii.dataSource.URL.all = function (dataSource, method, directModel, model, callback) {
        var deferred = when.defer(),
            promise = deferred.promise,
            resolveUrl = dataSource.resolveUrl,
            url = resolveUrl(directModel),
            isFileProtocol = gpii.dataSource.isFileProtocol(url),
            request = fluid.resolveEnvironment("{request}", {
                fetcher: fluid.makeEnvironmentFetcher()
            }),
            func = urlAll;
        method = method === "get" ? "GET" : "PUT";

        if (isFileProtocol) {
            func = fsAll;
            url = url.substring(7);
            method = method === "GET" ? "read" : "write";
        }

        if (typeof model === "function" || !model) {
            callback = model;
        }
        model = stringifyModel(model);

        promise.then(wrapProcessData(deferred, dataSource.options.responseParser, callback), gpii.callbackWrapper(request, function (error) {
            dataSource.handleError(error);
        }));

        func.apply(null, [url, method, deferred, model]);

        return promise;
    };

    gpii.dataSource.handleError = function (requests, data) {
        requests.events.onError.fire(data);
    };

    gpii.dataSource.resolveUrl = function (expand, url, termMap, directModel) {
        var map = fluid.copy(termMap);
        map = fluid.transform(map, function (entry) {
            var encode = false;
            if (entry.indexOf(eUC) === 0) {
                encode = true;
                entry = entry.substring(eUC.length);
            }
            if (entry.charAt(0) === "%") {
                entry = fluid.get(directModel, entry.substring(1));
            }
            if (encode) {
                entry = encodeURIComponent(entry);
            }
            return entry;
        });
        var replaced = fluid.stringTemplate(url, map);
        replaced = expand(replaced);
        return replaced;
    };

})();<|endoftext|>


<commit_before>import actions from '../../client/actions/index.js';
import reducer from '../../client/reducers/playerReducer.js';

describe("Reducer: Player", () => {
  let state;

  beforeEach(() => {
    state = reducer(undefined, {});
  });

  it('should return base state on no action', () => {
    expect(state.name).toBeDefined();
    expect(state.attributes).toBeDefined();
    expect(state.equipment).toBeDefined();
  });

  it('should set gender', () => {
    let newState = reducer(state, actions.setGender('female'));
    expect(newState.gender).toEqual('female');
  });

  it('should set difficulty', () => {
    let newState = reducer(state, actions.setDifficulty('easy'));
    expect(newState.difficulty).toEqual('easy');
  });

  it('should increase attributes', () => {
    let newState = reducer(state, actions.setAttribute('Strength', 5));
    expect(newState.attributes.Strength).toEqual({value: 55, name: 'Strength'});
    expect(newState.attributes.Available).toEqual({value: 95, name: 'Available'});
  });

  it('should decrease attributes', () => {
    let newState = reducer(state, actions.setAttribute('Intelligence', -5));
    expect(newState.attributes.Intelligence).toEqual({value: 45, name: 'Intelligence'});
    expect(newState.attributes.Available).toEqual({value: 105, name: 'Available'});
  });

  it('should not increase attibutes past 100', () => {
    let newState = reducer(state, actions.setAttribute('Dexterity', 55));
    expect(newState.attributes.Dexterity).toEqual({value: 50, name: 'Dexterity'});
    expect(newState.attributes.Available).toEqual({value: 100, name: 'Available'});
  });

  it('should not decrease attributes past 0', () => {
    let newState = reducer(state, actions.setAttribute('Dexterity', -55));
    expect(newState.attributes.Dexterity).toEqual({value: 50, name: 'Dexterity'});
    expect(newState.attributes.Available).toEqual({value: 100, name: 'Available'});
  });
});<commit_msg>actually forgot some cases<commit_after>import actions from '../../client/actions/index.js';
import reducer from '../../client/reducers/playerReducer.js';

describe("Reducer: Player", () => {
  let state;

  beforeEach(() => {
    state = reducer(undefined, {});
  });

  it('should return base state on no action', () => {
    expect(state.name).toBeDefined();
    expect(state.attributes).toBeDefined();
    expect(state.equipment).toBeDefined();
  });

  it('should set gender', () => {
    let newState = reducer(state, actions.setGender('female'));
    expect(newState.gender).toEqual('female');
  });

  it('should set difficulty', () => {
    let newState = reducer(state, actions.setDifficulty('easy'));
    expect(newState.difficulty).toEqual('easy');
  });

  it('should increase attributes', () => {
    let newState = reducer(state, actions.setAttribute('Strength', 5));
    expect(newState.attributes.Strength).toEqual({value: 55, name: 'Strength'});
    expect(newState.attributes.Available).toEqual({value: 95, name: 'Available'});
  });

  it('should decrease attributes', () => {
    let newState = reducer(state, actions.setAttribute('Intelligence', -5));
    expect(newState.attributes.Intelligence).toEqual({value: 45, name: 'Intelligence'});
    expect(newState.attributes.Available).toEqual({value: 105, name: 'Available'});
  });

  it('should not increase attibutes past 100', () => {
    let newState = reducer(state, actions.setAttribute('Dexterity', 55));
    expect(newState.attributes.Dexterity).toEqual({value: 50, name: 'Dexterity'});
    expect(newState.attributes.Available).toEqual({value: 100, name: 'Available'});
  });

  it('should not decrease attributes past 0', () => {
    let newState = reducer(state, actions.setAttribute('Dexterity', -55));
    expect(newState.attributes.Dexterity).toEqual({value: 50, name: 'Dexterity'});
    expect(newState.attributes.Available).toEqual({value: 100, name: 'Available'});
  });

  it('should allow players to equip items', () => {
    throw 'TODO';
  });

  it('should allow players to unequip items', () => {
    throw 'TODO';
  });

  it('should move players', () => {
    throw 'TODO';
  });

  it('should teleport players to different parts of the map (on entering new areas)', () => {
    throw 'TODO';
  });
});<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { SyncQueue } from './SyncQueue';
import { SyncDatabase } from './SyncDatabase';
import { generateSyncJson } from './outgoingSyncUtils';
import { integrateRecord } from './incomingSyncUtils';
import { SETTINGS_KEYS } from '../settings';
import { formatDate } from '../utilities';
const {
  SYNC_IS_INITIALISED,
  SYNC_LAST_SUCCESS,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const BATCH_SIZE = 20; // Number of records to sync at one time

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally)
 * @param  {Realm}             database       The local database
 * @param  {SyncAuthenticator} authenticator  Provides authentication with the sync server
 * @param  {Settings}          settings       Access to locally stored settings
 */
export class Synchroniser {
  constructor(database, authenticator, settings) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.synchronise = this.synchronise.bind(this);
    this.initialise = this.initialise.bind(this);
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Wipe the current database, check that the given url can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   * @param  {string} serverURL        The URL to be synced against
   * @param  {string} syncSiteName     The username to use in sync auth
   * @param  {string} syncSitePassword The password to use in sync auth
   * @return {Promise}                 Resolve if successfully authenticated and
   *                                   initialised, otherwise throw error
   */
  async initialise(serverURL, syncSiteName, syncSitePassword, setProgress) {
    if (setProgress) setProgress('Initialising...');
    this.syncQueue.disable(); // Stop sync queue listening to database changes
    // Check if the serverURL passed in is the same as one we have already been using during
    // initialisation, in which case we are continuing a failed partial initialisation. If the
    // serverURL is different, it is either completely fresh, or the URL has been changed so we
    // should start afresh
    const isFresh = serverURL !== this.serverURL;
    if (isFresh)
      this.database.write(() => {
        this.database.deleteAll();
      });
    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      const thisSiteId = this.settings.get(SYNC_SITE_ID);
      const serverId = this.settings.get(SYNC_SERVER_ID);
      if (isFresh) {
        // If a fresh initialisation, tell the server to prepare required sync records
        await fetch(
          `${serverURL}/sync/v2/initial_dump/?from_site=${thisSiteId}&to_site=${serverId}`,
          {
            headers: {
              Authorization: this.authenticator.getAuthHeader(),
            },
          }
        );
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull(setProgress);
    } catch (error) {
      // Did not authenticate, sync error, or no internet, pass error up
      throw error;
    }
    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes
    this.allocateSerialNumbers(); // allocateSerialNumbers for any records with serialNumber === -1
  }

  /**
   * Return whether the synchroniser has been completely or partially initialised.
   * @return {string} Either 'complete', 'partial', 'uninitialised'
   */
  isInitialised() {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  }

  /**
   * Carry out a synchronization, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   * @return {[type]} [description]
   */
  async synchronise() {
    if (!this.isInitialised()) throw new Error('Not yet initialised');
    // Using async/await here means that any errors thrown by push or pull
    // will be passed up as a rejection of the promise returned by synchronise
    await this.push();
    await this.pull();
    await this.allocateSerialNumbers(); // allocateSerialNumbers for any records with serialNumber === -1
    this.settings.set(SYNC_LAST_SUCCESS, formatDate(new Date(), 'dots'));
  }

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  async push() {
    let recordsToSync;
    let translatedRecords;
    while (this.syncQueue.length > 0) {
      recordsToSync = this.syncQueue.next(BATCH_SIZE);
      translatedRecords = recordsToSync.map(record =>
        generateSyncJson(this.database, this.settings, record)
      );
      await this.pushRecords(translatedRecords);
      this.syncQueue.use(recordsToSync);
    }
  }

  /**
   * Pushes a collection of records to the remote sync server
   * @param  {array}   records The records to push
   * @return {Promise}         Resolves if successful, or passes up any error thrown
   */
  async pushRecords(records) {
    const serverURL = this.settings.get(SYNC_URL);
    const thisSiteId = this.settings.get(SYNC_SITE_ID);
    const serverId = this.settings.get(SYNC_SERVER_ID);
    const response = await fetch(
      `${serverURL}/sync/v2/queued_records/?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authenticator.getAuthHeader(),
        },
        body: JSON.stringify(records),
      }
    );
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      throw new Error('Unexpected response from sync server');
    }
    if (responseJson.error.length > 0) {
      throw new Error('Server rejected pushed records');
    }
  }

  /**
   * Pulls any changes to data on the sync server down to the local database
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  async pull(setProgress) {
    const serverURL = this.settings.get(SYNC_URL);
    const thisSiteId = this.settings.get(SYNC_SITE_ID);
    const serverId = this.settings.get(SYNC_SERVER_ID);
    await this.recursivePull(serverURL, thisSiteId, serverId, setProgress);
  }

  /**
   * Recursively checks how many records left to pull, pulls in a batch, and calls
   * itself
   * @param  {string} serverURL  The URL of the sync server
   * @param  {string} thisSiteId The sync ID of this sync site
   * @param  {string} serverId   The sync ID of the server
   * @return {Promise}          Resolves if successful, or passes up any error thrown
   */
  async recursivePull(serverURL, thisSiteId, serverId, setProgress) {
    const authHeader = this.authenticator.getAuthHeader();
    const waitingRecordCount = await this.getWaitingRecordCount(
      serverURL,
      thisSiteId,
      serverId,
      authHeader
    );
    if (setProgress) setProgress(`${waitingRecordCount} records to go`);
    if (waitingRecordCount === 0) return; // Done recursing through records

    // Get a batch of records and integrate them
    const incomingRecords = await this.getIncomingRecords(
      serverURL,
      thisSiteId,
      serverId,
      authHeader,
      BATCH_SIZE
    );
    this.integrateRecords(incomingRecords);
    await this.acknowledgeRecords(serverURL, thisSiteId, serverId, authHeader, incomingRecords);

    // Recurse to get the next batch of records from the server
    await this.recursivePull(serverURL, thisSiteId, serverId, setProgress);
  }

  /**
   * Returns the number of records left to pull
   * @param  {string} serverURL  URL of the sync server
   * @param  {string} thisSiteId Sync ID of this sync site
   * @param  {string} serverId   Sync ID of the server
   * @return {Promise}           Resolves with the record count, or passes up any error thrown
   */
  async getWaitingRecordCount(serverURL, thisSiteId, serverId, authHeader) {
    const response = await fetch(
      `${serverURL}/sync/v2/queued_records/count?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        headers: {
          Authorization: authHeader,
        },
      }
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    if (typeof responseJson.NumRecords !== 'number') {
      throw new Error('Unexpected response from server');
    }
    return responseJson.NumRecords;
  }

  /**
   * Returns the next batch of incoming sync records
   * @param  {string}  serverURL  The URL of the sync server
   * @param  {string}  thisSiteId The sync ID of this sync site
   * @param  {string}  serverId   The sync ID of the server
   * @param  {integer} numRecords The number of records to fetch
   * @return {Promise}            Resolves with the records, or passes up any error thrown
   */
  async getIncomingRecords(serverURL, thisSiteId, serverId, authHeader, numRecords) {
    const response = await fetch(
      `${serverURL}/sync/v2/queued_records` +
        `?from_site=${thisSiteId}&to_site=${serverId}&limit=${numRecords}`,
      {
        headers: {
          Authorization: authHeader,
        },
      }
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while pulling sync records.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    return responseJson;
  }

  /**
   * Parse the batch of incoming records, and integrate them into the local database
   * @param  {Realm}  database The local database
   * @param  {object} records  The json object the server sent to represent records
   * @return {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed
   * @param  {string}  serverURL  Base URL of the sync server
   * @param  {string}  thisSiteId Sync id of this mobile site
   * @param  {string}  serverId   Sync id of the primary server
   * @param  {string}  authHeader Base64 encoded Basic auth header
   * @param  {array}   records    Sync records that have been integrated
   * @return {none}
   */
  async acknowledgeRecords(serverURL, thisSiteId, serverId, authHeader, records) {
    const syncIds = records.map(record => record.SyncID);
    const requestBody = {
      SyncRecordIDs: syncIds,
    };
    await fetch(
      `${serverURL}/sync/v2/acknowledged_records?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: authHeader,
        },
        body: JSON.stringify(requestBody),
      }
    );
  }

}
<commit_msg>Incomplete commit<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { SyncQueue } from './SyncQueue';
import { SyncDatabase } from './SyncDatabase';
import { generateSyncJson } from './outgoingSyncUtils';
import { integrateRecord } from './incomingSyncUtils';
import { SETTINGS_KEYS } from '../settings';
import { formatDate } from '../utilities';
import { getNextNumber, NUMBER_SEQUENCE_KEYS } from '../database/utilities';
const {
  SYNC_IS_INITIALISED,
  SYNC_LAST_SUCCESS,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;
const {
  CUSTOMER_INVOICE_NUMBER,
  INVENTORY_ADJUSTMENT_SERIAL_NUMBER,
  REQUISITION_SERIAL_NUMBER,
  REQUISITION_REQUESTER_REFERENCE,
  STOCKTAKE_SERIAL_NUMBER,
  SUPPLIER_INVOICE_NUMBER,
} = NUMBER_SEQUENCE_KEYS;

const BATCH_SIZE = 20; // Number of records to sync at one time

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally)
 * @param  {Realm}             database       The local database
 * @param  {SyncAuthenticator} authenticator  Provides authentication with the sync server
 * @param  {Settings}          settings       Access to locally stored settings
 */
export class Synchroniser {
  constructor(database, authenticator, settings) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.synchronise = this.synchronise.bind(this);
    this.initialise = this.initialise.bind(this);
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Wipe the current database, check that the given url can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   * @param  {string} serverURL        The URL to be synced against
   * @param  {string} syncSiteName     The username to use in sync auth
   * @param  {string} syncSitePassword The password to use in sync auth
   * @return {Promise}                 Resolve if successfully authenticated and
   *                                   initialised, otherwise throw error
   */
  async initialise(serverURL, syncSiteName, syncSitePassword, setProgress) {
    if (setProgress) setProgress('Initialising...');
    this.syncQueue.disable(); // Stop sync queue listening to database changes
    // Check if the serverURL passed in is the same as one we have already been using during
    // initialisation, in which case we are continuing a failed partial initialisation. If the
    // serverURL is different, it is either completely fresh, or the URL has been changed so we
    // should start afresh
    const isFresh = serverURL !== this.serverURL;
    if (isFresh)
      this.database.write(() => {
        this.database.deleteAll();
      });
    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      const thisSiteId = this.settings.get(SYNC_SITE_ID);
      const serverId = this.settings.get(SYNC_SERVER_ID);
      if (isFresh) {
        // If a fresh initialisation, tell the server to prepare required sync records
        await fetch(
          `${serverURL}/sync/v2/initial_dump/?from_site=${thisSiteId}&to_site=${serverId}`,
          {
            headers: {
              Authorization: this.authenticator.getAuthHeader(),
            },
          }
        );
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull(setProgress);
    } catch (error) {
      // Did not authenticate, sync error, or no internet, pass error up
      throw error;
    }
    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes
    this.allocateSerialNumbers(); // allocateSerialNumbers for any records with serialNumber === -1
  }

  /**
   * Return whether the synchroniser has been completely or partially initialised.
   * @return {string} Either 'complete', 'partial', 'uninitialised'
   */
  isInitialised() {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  }

  /**
   * Carry out a synchronization, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   * @return {[type]} [description]
   */
  async synchronise() {
    if (!this.isInitialised()) throw new Error('Not yet initialised');
    // Using async/await here means that any errors thrown by push or pull
    // will be passed up as a rejection of the promise returned by synchronise
    await this.push();
    await this.pull();
    await this.allocateSerialNumbers(); // allocateSerialNumbers for any records with serialNumber === -1
    this.settings.set(SYNC_LAST_SUCCESS, formatDate(new Date(), 'dots'));
  }

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  async push() {
    let recordsToSync;
    let translatedRecords;
    while (this.syncQueue.length > 0) {
      recordsToSync = this.syncQueue.next(BATCH_SIZE);
      translatedRecords = recordsToSync.map(record =>
        generateSyncJson(this.database, this.settings, record)
      );
      await this.pushRecords(translatedRecords);
      this.syncQueue.use(recordsToSync);
    }
  }

  /**
   * Pushes a collection of records to the remote sync server
   * @param  {array}   records The records to push
   * @return {Promise}         Resolves if successful, or passes up any error thrown
   */
  async pushRecords(records) {
    const serverURL = this.settings.get(SYNC_URL);
    const thisSiteId = this.settings.get(SYNC_SITE_ID);
    const serverId = this.settings.get(SYNC_SERVER_ID);
    const response = await fetch(
      `${serverURL}/sync/v2/queued_records/?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authenticator.getAuthHeader(),
        },
        body: JSON.stringify(records),
      }
    );
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      throw new Error('Unexpected response from sync server');
    }
    if (responseJson.error.length > 0) {
      throw new Error('Server rejected pushed records');
    }
  }

  /**
   * Pulls any changes to data on the sync server down to the local database
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  async pull(setProgress) {
    const serverURL = this.settings.get(SYNC_URL);
    const thisSiteId = this.settings.get(SYNC_SITE_ID);
    const serverId = this.settings.get(SYNC_SERVER_ID);
    await this.recursivePull(serverURL, thisSiteId, serverId, setProgress);
  }

  /**
   * Recursively checks how many records left to pull, pulls in a batch, and calls
   * itself
   * @param  {string} serverURL  The URL of the sync server
   * @param  {string} thisSiteId The sync ID of this sync site
   * @param  {string} serverId   The sync ID of the server
   * @return {Promise}          Resolves if successful, or passes up any error thrown
   */
  async recursivePull(serverURL, thisSiteId, serverId, setProgress) {
    const authHeader = this.authenticator.getAuthHeader();
    const waitingRecordCount = await this.getWaitingRecordCount(
      serverURL,
      thisSiteId,
      serverId,
      authHeader
    );
    if (setProgress) setProgress(`${waitingRecordCount} records to go`);
    if (waitingRecordCount === 0) return; // Done recursing through records

    // Get a batch of records and integrate them
    const incomingRecords = await this.getIncomingRecords(
      serverURL,
      thisSiteId,
      serverId,
      authHeader,
      BATCH_SIZE
    );
    this.integrateRecords(incomingRecords);
    await this.acknowledgeRecords(serverURL, thisSiteId, serverId, authHeader, incomingRecords);

    // Recurse to get the next batch of records from the server
    await this.recursivePull(serverURL, thisSiteId, serverId, setProgress);
  }

  /**
   * Returns the number of records left to pull
   * @param  {string} serverURL  URL of the sync server
   * @param  {string} thisSiteId Sync ID of this sync site
   * @param  {string} serverId   Sync ID of the server
   * @return {Promise}           Resolves with the record count, or passes up any error thrown
   */
  async getWaitingRecordCount(serverURL, thisSiteId, serverId, authHeader) {
    const response = await fetch(
      `${serverURL}/sync/v2/queued_records/count?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        headers: {
          Authorization: authHeader,
        },
      }
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    if (typeof responseJson.NumRecords !== 'number') {
      throw new Error('Unexpected response from server');
    }
    return responseJson.NumRecords;
  }

  /**
   * Returns the next batch of incoming sync records
   * @param  {string}  serverURL  The URL of the sync server
   * @param  {string}  thisSiteId The sync ID of this sync site
   * @param  {string}  serverId   The sync ID of the server
   * @param  {integer} numRecords The number of records to fetch
   * @return {Promise}            Resolves with the records, or passes up any error thrown
   */
  async getIncomingRecords(serverURL, thisSiteId, serverId, authHeader, numRecords) {
    const response = await fetch(
      `${serverURL}/sync/v2/queued_records` +
        `?from_site=${thisSiteId}&to_site=${serverId}&limit=${numRecords}`,
      {
        headers: {
          Authorization: authHeader,
        },
      }
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while pulling sync records.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    return responseJson;
  }

  /**
   * Parse the batch of incoming records, and integrate them into the local database
   * @param  {Realm}  database The local database
   * @param  {object} records  The json object the server sent to represent records
   * @return {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed
   * @param  {string}  serverURL  Base URL of the sync server
   * @param  {string}  thisSiteId Sync id of this mobile site
   * @param  {string}  serverId   Sync id of the primary server
   * @param  {string}  authHeader Base64 encoded Basic auth header
   * @param  {array}   records    Sync records that have been integrated
   * @return {none}
   */
  async acknowledgeRecords(serverURL, thisSiteId, serverId, authHeader, records) {
    const syncIds = records.map(record => record.SyncID);
    const requestBody = {
      SyncRecordIDs: syncIds,
    };
    await fetch(
      `${serverURL}/sync/v2/acknowledged_records?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: authHeader,
        },
        body: JSON.stringify(requestBody),
      }
    );
  }
  /**
   * Should only be run after a sync.
   * Processes the given database for records of serial number -1, and updates them with the
   * appropriate next sequence number. These records have been generated by the server as
   * the other half of actions in other stores: Another site making a requisition here, or
   * a supplier invoice from another store
   * @param  {Realm}  database   The local database
   */
  async allocateSerialNumbers() {
    // TODO: Create data base write, query appropriate tables for serialNumber being -1, assign correct, write finish write
    const sequenceKeys = {
      customer_invoice: CUSTOMER_INVOICE_NUMBER,
      customer_credit: CUSTOMER_INVOICE_NUMBER,
      inventory_adjustment: INVENTORY_ADJUSTMENT_SERIAL_NUMBER,
      requisition: REQUISITION_SERIAL_NUMBER,
      REQUISITION_REQUESTER_REFERENCE,
      STOCKTAKE_SERIAL_NUMBER,
      supplier_invoice: SUPPLIER_INVOICE_NUMBER,
      supplier_credit: SUPPLIER_INVOICE_NUMBER,
    }[type];

    // Handle server generated transactions. If invoice_num is -1 mobile should
    // generate the serialNumber
    const serialNumber =
      record.invoice_num >= 0 ? record.invoice_num : getNextNumber(this.database, sequenceKey);
  }
}
<|endoftext|>


<commit_before>/*
Script: Slab.js
	Pretty simple templating.

Copyright and License:
	(c) 2012, Mark Obcena <markeeto@gmail.com>
	MIT-Style License
*/

(function(context){

var exps = {
	// grouping
	group: (/\{slab\s+([$A-Za-z_][$0-9A-Za-z_]*)\}([^]*?)\{endslab\}/g),

	// comments
	comment: (/\{\*[^]*?\*\}/g),

	// parser escape
	kept: (/\{keep\}([^]*?)\{endslab\}/g),
	keptIdent: (/(%#%)E[0-9]+\1/g),

	// new lines
	breaker: (/%#=#%/g),
	emptyLine: (/\n;\s*\n/g),
	appendableString: (/";\s*\n\s*_buffer_ \+= "/g),

	// strings and identifiers
	strings: (/('|").*?\1/g),
	stringIdent: (/(%#%)[0-9]+\1/g),
	identifier: (/\.?[$A-Za-z_][$0-9A-Za-z_]*/g),
	reservedIdentifier: (/__[$0-9A-Za-z_]*/g),
	extractedIdentifier: (/^([$A-Za-z_][$0-9A-Za-z_]*)\./),
	operators: (/\s+(and|or|eq|neq|is|isnt|not)\s+/g),

	// statements
	statement: (/\{\s*([^}]+?)\s*\}/g),
	escapeState: (/^:\s*(.+)/),
	endState: (/^end$/),
	elseState: (/^else$/),
	elseIfState: (/^else\s+if\s+(.+)$/),
	ifState: (/^if\s+(.+)$/),
	eachState: (/^each\s+([$A-Za-z_][$0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s(.+)$/),
	forState: (/^for\s+([$A-Za-z_][$0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s+(.+)$/),
	forEveryState: (/^forEvery\s+([$A-Za-z_][$0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s+(.+)$/),

	// inlining
	inlined: (/\{include\s+([$A-Za-z_][$0-9A-Za-z_]*)\}/g)
};

var slice = Array.prototype.slice;

function partial(fn){
	var args = slice.call(arguments, 1);
	return function(){
		return fn.apply(null, args.concat(slice.call(arguments)));
	};
}

// Operators
var ops = {
	and: ' && ', or: ' || ', eq: ' === ', neq: ' !== ',
	not: ' !', isnt: ' != ', is: ' == '
};

function parseOperators(_, op){
	return ops[op];
}

// Escape
function parseEscape(stack, _, exp){
	// Adapted from DoT.js (https://github.com/olado/doT)
	return '_strtmp = (' + parseIdents(stack, exp) + ').toString();%#=#%'
		+ '_buffer_ += _strs[_strtmp] ||'
		+ '(_strs[_strtmp] = _strtmp'
		+ '.replace(/&(?!\\w+;)/g, "&")'
		+ '.split("<").join("<")'
		+ '.split(">").join(">")'
		+ '.split("\\"").join(""")'
		+ '.split("\'").join("'")'
		+ '.split("/").join("/"));%#=#%';
}

// End Statement
function parseEnd(stack){
	stack.pop();
	stack.pop();
	return '} %#=#%';
}

// Else Statement
function parseElse(){
	return '} else { %#=#%';
}

// Else If Statement
function parseElseIf(stack, _, exp){
	return '} else if (' + parseIdents(stack, exp) +') { %#=#%';
}

// If Statement
function parseIf(stack, _, exp){
	stack.push(null, null);
	return 'if (' + parseIdents(stack, exp) + ') { %#=#%';
}

// Each Statement
var iterator = 0;

function parseEach(stack, _, iter, value, array){
	/*jsl:ignore*/
	if (!value) iter = (value = iter, 'iterator' + iterator++);
	/*jsl:end*/
	var length = 'length' + iterator++;
	array = resolveIdents(stack, array);
	stack.push(iter, value);
	return 'for (var ' + iter + ' = 0, '
		+ length + ' = ' + array + '.length; '
		+ iter + ' < ' + length + '; ' + iter + '++) { %#=#%'
		+ 'var ' + value + ' = ' + array + '[' + (iter) + ']';
}

// For Statement

function parseFor(stack, _, key, value, object){
	/*jsl:ignore*/
	if (!value) key = (value = key, 'iterator' + iterator++);
	/*jsl:end*/
	object = resolveIdents(stack, object);
	stack.push(key, value);
	return 'for (var ' + key + ' in ' + object + '){ %#=#%'
		+ 'if (!' + object + '.hasOwnProperty('+ key +')) continue; %#=#%'
		+ 'var ' + value + ' = ' + object + '[' + key +']';
}

function parseForEvery(stack, _, key, value, object){
	/*jsl:ignore*/
	if (!value) key = (value = key, 'iterator' + iterator++);
	/*jsl:end*/
	object = resolveIdents(stack, object);
	stack.push(key, value);
	return 'for (var ' + key + ' in ' + object + '){ %#=#%'
		+ 'var ' + value + ' = ' + object + '[' + key +']';
}

// Identifiers

var notIdents = {
	'true': 1, 'false': 1, 'null': 1, 'undefined': 1,
	'typeof': 1, 'instanceof': 1, 'if': 1, 'else': 1,
	'_data_': 1
};

function resolveIdents(stack, match){
	var _match = match,
		matches = match.match(exps.extractedIdentifier);
	if (matches) match = matches[1];
	if (exps.reservedIdentifier.test(match)) return match;
	return (match.charAt(0) == '.'
		|| stack.indexOf(match) != -1
		|| notIdents[match]) ? _match : '_data_.' + _match;
}

function resolveReservedIdents(match){
	return match.slice(2);
}

function parseIdents(stack, str){
	var strings = {}, ident = 0;
	return str.replace(exps.strings, function(match, id){
		strings[(id = '%#%' + (ident++) + '%#%')] = match;
		return id;
	}).replace(exps.identifier, partial(resolveIdents, stack))
	.replace(exps.reservedIdentifier, resolveReservedIdents)
	.replace(exps.stringIdent, function(match){
		return strings[match];
	});
}

// Main Parser
function parseStatement(stack, match, inner){
	var result = '"; %#=#%',
		prev = inner = inner.replace(exps.operators, parseOperators);

	inner = inner.replace(exps.elseState, parseElse)
		.replace(exps.elseIfState, partial(parseElseIf, stack))
		.replace(exps.ifState, partial(parseIf, stack))
		.replace(exps.eachState, partial(parseEach, stack))
		.replace(exps.forState, partial(parseFor, stack))
		.replace(exps.forState, partial(parseForEvery, stack))
		.replace(exps.endState, partial(parseEnd, stack))
		.replace(exps.escapeState, partial(parseEscape, stack));

	if (inner == prev){
		result += '_buffer_ += ';
		inner = parseIdents(stack, inner);
	}

	return result + inner.replace(/\t|\n|\r/, '') + '; %#=#%_buffer_ += "';
}

// Cleanup
function clean(str){
	return str.replace(/_buffer_\s\+=\s"";/g, '')
		.replace(/\n/g, '\\n')
		.replace(/\t/g, '\\t')
		.replace(/\r/g, '\\r')
		.replace(exps.breaker, '\n')
		.replace(exps.emptyLine, '\n');
		/*.replace(exps.appendableString, '');*/
}

// Classes
function SlabError(msg){
	this.message = msg;
}

SlabError.prototype = new Error();
SlabError.prototype.name = 'SlabError';

function SlabTemplate(){
	this._templates = {};
	this._parsed = {};
	this._cache = {};
}

// Public Functions

function inlineIncludes(templates, deps, _, name){
	if (deps.indexOf(name) != -1){
		deps.push(name);
		throw new SlabError('Recursive includes for template "' + name + '" (' + deps.join(' > ') + ').');
	}

	deps.push(name);
	var item = templates[name];
	if (!item) throw new SlabError('Cannot find template named "' + name + '" (' + deps.join(' > ') + ').');
	return item;
}

function resolveIncludes(template, templates){
	var deps = [],
		resolver = partial(inlineIncludes, templates, deps);
	while (template != (template = template.replace(exps.inlined, resolver))) null;
	return template;
}

function parseSub(str, slab){
	var cache = slab._cache;
	if (cache[str]) return cache[str];

	var stack = [],
		kept = {},
		stashCount = 0;

	var  _str = str.replace(exps.kept, function(id, match){
			kept[(id = '%#%E' + (stashCount++) + '%#%')] = match;
			return id;
		}).replace(exps.statement, partial(parseStatement, stack))
		.replace(exps.keptIdent, function(match){
			return kept[match];
		});

	return (cache[str] = clean('%#=#%_buffer_ += "' + _str + '";'));
}

function parse(str, slab){
	if (!str) throw new SlabError('Template string cannot be empty.');
	if (slab && !(slab instanceof SlabTemplate))
		throw new SlabError('Object passed is not a Slab object.');

	str = str.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"');
	slab = slab || new SlabTemplate();

	var slabs = str.replace(exps.comment, '').match(exps.group);
	if (!slabs) throw new SlabError('No valid templates found in string.');

	var len = slabs.length,
		templates = slab._templates,
		parsed = slab._parsed,
		identifier;

	while (len--){
		var current = slabs[len],
			_tmp = current.replace(exps.group, function(_, id, body){
				identifier = id;
				return body;
			});
		templates[identifier] = _tmp;
	}

	for (var i in templates){
		var template = resolveIncludes(templates[i], templates);
		parsed[i] = parseSub(template, slab);
	}

	return slab;
}

function wrapTemplate(str, id){
	return 'var _buffer_ = "", _strs = {}, _strtmp;' + str
		+ '\nreturn _buffer_;\n//@ sourceURL=' + id;
}

function compile(str, slab){
	slab = parse(str, slab);
	var templates = slab._parsed;

	for (var id in templates){
		var template = templates[id];
		try {
			slab[id] = new Function('_data_', wrapTemplate(template, id));
		} catch(e){
			throw new SlabError('Can\'t compile slab template. ' + e.message);
		}
	}
	return slab;
}

var generateCount = 0;
function generate(objId, str, slab){
	if (!objId) objId = 'slabTemplate' + (generateCount++);
	slab = parse(str, slab);

	var results = [],
		templates = slab._parsed;

	for (var id in templates){
		var template = templates[id];
		results.push(id + ': function(_data_){\n' + wrapTemplate(template, objId) + '\n}');
	}
	return 'var ' + objId + '= {\n' + results.join(',\n') + '};';
}

context.slab = {
	compile: compile,
	parse: parse,
	generate: generate
};

})(typeof exports != 'undefined' ? exports : this);

<commit_msg>Fix keep closing tag. Issue #4<commit_after>/*
Script: Slab.js
	Pretty simple templating.

Copyright and License:
	(c) 2012, Mark Obcena <markeeto@gmail.com>
	MIT-Style License
*/

(function(context){

var exps = {
	// grouping
	group: (/\{slab\s+([$A-Za-z_][$0-9A-Za-z_]*)\}([^]*?)\{endslab\}/g),

	// comments
	comment: (/\{\*[^]*?\*\}/g),

	// parser escape
	kept: (/\{keep\}([^]*?)\{endkeep\}/g),
	keptIdent: (/(%#%)E[0-9]+\1/g),

	// new lines
	breaker: (/%#=#%/g),
	emptyLine: (/\n;\s*\n/g),
	appendableString: (/";\s*\n\s*_buffer_ \+= "/g),

	// strings and identifiers
	strings: (/('|").*?\1/g),
	stringIdent: (/(%#%)[0-9]+\1/g),
	identifier: (/\.?[$A-Za-z_][$0-9A-Za-z_]*/g),
	reservedIdentifier: (/__[$0-9A-Za-z_]*/g),
	extractedIdentifier: (/^([$A-Za-z_][$0-9A-Za-z_]*)\./),
	operators: (/\s+(and|or|eq|neq|is|isnt|not)\s+/g),

	// statements
	statement: (/\{\s*([^}]+?)\s*\}/g),
	escapeState: (/^:\s*(.+)/),
	endState: (/^end$/),
	elseState: (/^else$/),
	elseIfState: (/^else\s+if\s+(.+)$/),
	ifState: (/^if\s+(.+)$/),
	eachState: (/^each\s+([$A-Za-z_][$0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s(.+)$/),
	forState: (/^for\s+([$A-Za-z_][$0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s+(.+)$/),
	forEveryState: (/^forEvery\s+([$A-Za-z_][$0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s+(.+)$/),

	// inlining
	inlined: (/\{include\s+([$A-Za-z_][$0-9A-Za-z_]*)\}/g)
};

var slice = Array.prototype.slice;

function partial(fn){
	var args = slice.call(arguments, 1);
	return function(){
		return fn.apply(null, args.concat(slice.call(arguments)));
	};
}

// Operators
var ops = {
	and: ' && ', or: ' || ', eq: ' === ', neq: ' !== ',
	not: ' !', isnt: ' != ', is: ' == '
};

function parseOperators(_, op){
	return ops[op];
}

// Escape
function parseEscape(stack, _, exp){
	// Adapted from DoT.js (https://github.com/olado/doT)
	return '_strtmp = (' + parseIdents(stack, exp) + ').toString();%#=#%'
		+ '_buffer_ += _strs[_strtmp] ||'
		+ '(_strs[_strtmp] = _strtmp'
		+ '.replace(/&(?!\\w+;)/g, "&")'
		+ '.split("<").join("<")'
		+ '.split(">").join(">")'
		+ '.split("\\"").join(""")'
		+ '.split("\'").join("'")'
		+ '.split("/").join("/"));%#=#%';
}

// End Statement
function parseEnd(stack){
	stack.pop();
	stack.pop();
	return '} %#=#%';
}

// Else Statement
function parseElse(){
	return '} else { %#=#%';
}

// Else If Statement
function parseElseIf(stack, _, exp){
	return '} else if (' + parseIdents(stack, exp) +') { %#=#%';
}

// If Statement
function parseIf(stack, _, exp){
	stack.push(null, null);
	return 'if (' + parseIdents(stack, exp) + ') { %#=#%';
}

// Each Statement
var iterator = 0;

function parseEach(stack, _, iter, value, array){
	/*jsl:ignore*/
	if (!value) iter = (value = iter, 'iterator' + iterator++);
	/*jsl:end*/
	var length = 'length' + iterator++;
	array = resolveIdents(stack, array);
	stack.push(iter, value);
	return 'for (var ' + iter + ' = 0, '
		+ length + ' = ' + array + '.length; '
		+ iter + ' < ' + length + '; ' + iter + '++) { %#=#%'
		+ 'var ' + value + ' = ' + array + '[' + (iter) + ']';
}

// For Statement

function parseFor(stack, _, key, value, object){
	/*jsl:ignore*/
	if (!value) key = (value = key, 'iterator' + iterator++);
	/*jsl:end*/
	object = resolveIdents(stack, object);
	stack.push(key, value);
	return 'for (var ' + key + ' in ' + object + '){ %#=#%'
		+ 'if (!' + object + '.hasOwnProperty('+ key +')) continue; %#=#%'
		+ 'var ' + value + ' = ' + object + '[' + key +']';
}

function parseForEvery(stack, _, key, value, object){
	/*jsl:ignore*/
	if (!value) key = (value = key, 'iterator' + iterator++);
	/*jsl:end*/
	object = resolveIdents(stack, object);
	stack.push(key, value);
	return 'for (var ' + key + ' in ' + object + '){ %#=#%'
		+ 'var ' + value + ' = ' + object + '[' + key +']';
}

// Identifiers

var notIdents = {
	'true': 1, 'false': 1, 'null': 1, 'undefined': 1,
	'typeof': 1, 'instanceof': 1, 'if': 1, 'else': 1,
	'_data_': 1
};

function resolveIdents(stack, match){
	var _match = match,
		matches = match.match(exps.extractedIdentifier);
	if (matches) match = matches[1];
	if (exps.reservedIdentifier.test(match)) return match;
	return (match.charAt(0) == '.'
		|| stack.indexOf(match) != -1
		|| notIdents[match]) ? _match : '_data_.' + _match;
}

function resolveReservedIdents(match){
	return match.slice(2);
}

function parseIdents(stack, str){
	var strings = {}, ident = 0;
	return str.replace(exps.strings, function(match, id){
		strings[(id = '%#%' + (ident++) + '%#%')] = match;
		return id;
	}).replace(exps.identifier, partial(resolveIdents, stack))
	.replace(exps.reservedIdentifier, resolveReservedIdents)
	.replace(exps.stringIdent, function(match){
		return strings[match];
	});
}

// Main Parser
function parseStatement(stack, match, inner){
	var result = '"; %#=#%',
		prev = inner = inner.replace(exps.operators, parseOperators);

	inner = inner.replace(exps.elseState, parseElse)
		.replace(exps.elseIfState, partial(parseElseIf, stack))
		.replace(exps.ifState, partial(parseIf, stack))
		.replace(exps.eachState, partial(parseEach, stack))
		.replace(exps.forState, partial(parseFor, stack))
		.replace(exps.forState, partial(parseForEvery, stack))
		.replace(exps.endState, partial(parseEnd, stack))
		.replace(exps.escapeState, partial(parseEscape, stack));

	if (inner == prev){
		result += '_buffer_ += ';
		inner = parseIdents(stack, inner);
	}

	return result + inner.replace(/\t|\n|\r/, '') + '; %#=#%_buffer_ += "';
}

// Cleanup
function clean(str){
	return str.replace(/_buffer_\s\+=\s"";/g, '')
		.replace(/\n/g, '\\n')
		.replace(/\t/g, '\\t')
		.replace(/\r/g, '\\r')
		.replace(exps.breaker, '\n')
		.replace(exps.emptyLine, '\n');
		/*.replace(exps.appendableString, '');*/
}

// Classes
function SlabError(msg){
	this.message = msg;
}

SlabError.prototype = new Error();
SlabError.prototype.name = 'SlabError';

function SlabTemplate(){
	this._templates = {};
	this._parsed = {};
	this._cache = {};
}

// Public Functions

function inlineIncludes(templates, deps, _, name){
	if (deps.indexOf(name) != -1){
		deps.push(name);
		throw new SlabError('Recursive includes for template "' + name + '" (' + deps.join(' > ') + ').');
	}

	deps.push(name);
	var item = templates[name];
	if (!item) throw new SlabError('Cannot find template named "' + name + '" (' + deps.join(' > ') + ').');
	return item;
}

function resolveIncludes(template, templates){
	var deps = [],
		resolver = partial(inlineIncludes, templates, deps);
	while (template != (template = template.replace(exps.inlined, resolver))) null;
	return template;
}

function parseSub(str, slab){
	var cache = slab._cache;
	if (cache[str]) return cache[str];

	var stack = [],
		kept = {},
		stashCount = 0;

	var  _str = str.replace(exps.kept, function(id, match){
			kept[(id = '%#%E' + (stashCount++) + '%#%')] = match;
			return id;
		}).replace(exps.statement, partial(parseStatement, stack))
		.replace(exps.keptIdent, function(match){
			return kept[match];
		});

	return (cache[str] = clean('%#=#%_buffer_ += "' + _str + '";'));
}

function parse(str, slab){
	if (!str) throw new SlabError('Template string cannot be empty.');
	if (slab && !(slab instanceof SlabTemplate))
		throw new SlabError('Object passed is not a Slab object.');

	str = str.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"');
	slab = slab || new SlabTemplate();

	var slabs = str.replace(exps.comment, '').match(exps.group);
	if (!slabs) throw new SlabError('No valid templates found in string.');

	var len = slabs.length,
		templates = slab._templates,
		parsed = slab._parsed,
		identifier;

	while (len--){
		var current = slabs[len],
			_tmp = current.replace(exps.group, function(_, id, body){
				identifier = id;
				return body;
			});
		templates[identifier] = _tmp;
	}

	for (var i in templates){
		var template = resolveIncludes(templates[i], templates);
		parsed[i] = parseSub(template, slab);
	}

	return slab;
}

function wrapTemplate(str, id){
	return 'var _buffer_ = "", _strs = {}, _strtmp;' + str
		+ '\nreturn _buffer_;\n//@ sourceURL=' + id;
}

function compile(str, slab){
	slab = parse(str, slab);
	var templates = slab._parsed;

	for (var id in templates){
		var template = templates[id];
		try {
			slab[id] = new Function('_data_', wrapTemplate(template, id));
		} catch(e){
			throw new SlabError('Can\'t compile slab template. ' + e.message);
		}
	}
	return slab;
}

var generateCount = 0;
function generate(objId, str, slab){
	if (!objId) objId = 'slabTemplate' + (generateCount++);
	slab = parse(str, slab);

	var results = [],
		templates = slab._parsed;

	for (var id in templates){
		var template = templates[id];
		results.push(id + ': function(_data_){\n' + wrapTemplate(template, objId) + '\n}');
	}
	return 'var ' + objId + '= {\n' + results.join(',\n') + '};';
}

context.slab = {
	compile: compile,
	parse: parse,
	generate: generate
};

})(typeof exports != 'undefined' ? exports : this);

<|endoftext|>


<commit_before>module.exports = function(app) {
    app.use(require('./logRoute'));
};<commit_msg>use logRoute only in dev<commit_after>var env = process.env.NODE_ENV || 'development';

module.exports = function(app) {
    if ('development' === env) {
        app.use(require('./logRoute'));
    }
};<|endoftext|>


<commit_before>var uri = require('url')
  , State = require('../state')
  , swizzleRedirect = require('../utils/swizzle-redirect')
  , swizzleRender = require('../utils/swizzle-render')
  , swizzleEnd = require('../utils/swizzle-end')
  , flatten = require('utils-flatten')
  , merge = require('utils-merge')
  , utils = require('../utils')
  , validate = require('../validate')
  , debug = require('debug')('flowstate')
  , SessionStore = require('../stores/session');

  // TIPS: Generally, push a state before redirecting (unless wizard style
  //  alternatively, handler can lazily push, for its own state, to preserve storage
  //  which could be done based on the presence of a returnTo (which is not the handlers URL)

/**
 * Load state.
 *
 * This middleware is used to load state associated with a request.  The state
 * will be made available at `req.state`.
 *
 * HTTP is a stateless protocol.  In order to support stateful interactions, the
 * client and server need to operate in coordination.  The server is responsible
 * for persisting state associated with a request.  The client is responsible for
 * indicating that state in subsequent requests to the server (via a `state`
 * parameter in the query or body, by default).  The server then loads the
 * previously persisted state in order to continue processing the transaction.
 *
 * Options:
 *
 *     name   set req.state only if the state name is equal to the option value
 *     required   require state, failing otherwise (default: false)
 *
 * Examples:
 *
 *     app.get('/login',
 *       flowstate.loadState(),
 *       function(req, res, next) {
 *         res.locals.failureCount = req.state.failureCount;
 *         res.render('login');
 *       });
 *
 *     app.get('/login/callback',
 *       flowstate.loadState({ name: 'oauth2-redirect', required: true }),
 *       function(req, res, next) {
 *         // ...
 *       });
 *
 * @return {Function}
 * @api public
 */
module.exports = function(options) {
  options = options || {}

  var store = options.store || new SessionStore(options);
  var getHandle = options.getHandle || function(req) {
    return (req.query && req.query.state) || (req.body && req.body.state);
  }
  var validateRedirect = validate.origin;

  var mutationMethods = options.mutationMethods === undefined
    ? ['POST', 'PUT', 'PATCH', 'DELETE']
    : options.mutationMethods

  // WIP: On state init, set `resume` if the state is not intended for this lcoation

  // TODO: add a "locations" option, with relative paths that will be added to location

  return function state(req, res, next) {
    // self-awareness
    if (req.state) { return next(); }

    // expose store
    req.stateStore = store;

    req.pushState = function(data, url, options, cb) {
      if (typeof options == 'function') {
        cb = options;
        options = undefined;
      }
      options = options || {};

      // WIP: Test case where this is a relative URL
      data.location = uri.resolve(utils.originalURL(req), url);
      if (req.state.returnTo && !req.state.external) {
        data.returnTo = req.state.returnTo;
      } else if (req.state.resumeState) {
        data.resumeState = req.state.resumeState;
      }

      var state = new State(req, data);
      state.touch();
      req._stateStack.push(state);
      req.state = state;

      if (cb) {
        state.save(options, function(err) {
          if (err) { return cb(err); }
          return cb(null, state.handle);
        });
      }
    }

    res.resumeState = function(yields, cb) {
      if (typeof yields == 'function') {
        cb = yields;
        yields = undefined;
      }

      req.state.complete();

      if (req.state.returnTo) {
        // TODO: underscore the parameters, adn redirect with query
        this.redirect(req.state.returnTo);
      } else if (req.state.resumeState) {
        var self = this;

        req.state.destroy(function(err) {
          if (err) { return cb(err); } // FIXME: this should be next?

          store.load(req, req.state.resumeState, function(err, data) {
            if (err) { return cb(err); }  // FIXME: this should be next?

            // WIP: Need a state stack to traverse to do destroy on res.render/res.redirect

            var state = new State(req, data, req.state.resumeState);
            merge(state, yields);

            // TODO: This can be short-circuited to alway save, and optimize in there
            if (!state.isModified()) {
              self.redirect(data.location + '?state=' + state.handle);
            } else {
              state.save(function(err) {
                if (err) { return cb(err); }
                self.redirect(state.location + '?state=' + state.handle);
              });
            }
          });
        });
      } else {
        cb();
      }
    }

    // swizzle redirect to commit the state
    swizzleRedirect(res, function commitState(url, cb) {
      debug('redirect %s, %O (handle: %s, modified: %o, saved: %o, complete: %o, new: %o)', url, req.state,
        req.state.handle, req.state.isModified(), req.state.isSaved(), req.state.isComplete(), req.state.isNew());

      function proceed(returnTo, state) {
        if (!returnTo && !state) { return cb(); }

        // Add `state` as a query parameter to the URL that the user agent is
        // being redirected to.
        var l = uri.parse(url, true);
        delete l.search;
        if (returnTo) { l.query.return_to = returnTo; }
        if (state) { l.query.state = state; }
        return cb(null, uri.format(l));
      }

      if (req.state.isComplete()) {
        proceed();
      } else if (req.state.isModified()) {
        // NOTE: can be modified and new
        req.state.save(function(err) {
          if (err) { return next(err); } // FIXME: This should call cb? No
          debug('set `state` query parameter to saved state (%s)', req.state.handle);
          proceed(undefined, req.state.handle);
        });
      } else if (req.state.isNew()) {
        // newly initialized state, without any state to actually track.  Just pass on
        // the parent state.
        // TODO: Also pass on returnTo url?
        proceed(req.state.returnTo, req.state.resumeState);
      } else { // current state
        debug('set `state` query parameter to current state (%s)', req.state.handle);
        proceed(undefined, req.state.handle);
      }
    });

    // swizzle render to commit the state
    swizzleRender(res, function commitState(cb) {
      debug('render %O (handle: %s, modified: %o, saved: %o, complete: %o)', req.state,
        req.state.handle, req.state.isModified(), req.state.isSaved(), req.state.isComplete());

      // TODO: generalize this and make it an option, like csurf
      // TODO: dont' complete the state if it is flagged to continue

      // TODO: Shouldn't do this if done after resumeState.
      //.      swizzle it back to orig
      if (mutationMethods.indexOf(req.method) != -1) {
        if (Math.floor(res.statusCode / 100) == 2) {
          req.state.complete();
        }
      }

      commit(function(err, returnTo, state) {
        if (err) { return next(err); } // FIXME: This should call cb?

        if (returnTo) { res.locals.returnTo = returnTo; }
        else if (state) { res.locals.state = state; }
        cb();
      });
    });

    swizzleEnd(res, function autoDestroy(cb) {
      debug('end %O (handle: %s, complete: %o)', req.state,
        req.state.handle, req.state.isComplete());

      // WIP: figure out how to maintain incomplete states
      // probably best to mandate external states be  flagged complete ??

      commit(function() {
        cb();
      });
    });

    // WIP: More code review.  Put the stack in place and clean up on res.end.

    function generate() {
      var data = {
        location: utils.originalURLWithoutQuery(req)
      };

      var returnTo = (options.external ? utils.originalURL(req) : undefined)
        || (req.query && req.query.return_to)
        || (req.body && req.body.return_to)
        || (req.header ? req.header('referer') : undefined);
      if (returnTo) {
        var valid = validateRedirect(returnTo, req);
        if (valid) {
          data.returnTo = returnTo;
        }
      }

      req.state = new State(req, data, undefined, options.external);
      req._stateStack = [ req.state ];
    }

    function inflate(state, h) {
      req.state = new State(req, state, h);
      req._stateStack = [ req.state ];
    }

    function commit(cb) {
      var stack = req._stateStack
        , i = 0;

      function iter(err, returnTo, resumeState) {
        if (err) { return next(err); } // TODO: Test case for this

        var state = stack[i++];
        if (!state) { return cb(null, returnTo, resumeState); }

        if (req.state.isComplete()) {
          debug('destroying complete state (%s)', req.state.handle);
          req.state.destroy(function(err) {
            if (err) { return iter(err); }
            iter();
          });
        } else if (req.state.isModified()) {
          req.state.save(function(err) {
            if (err) { return iter(err); }
            debug('set `res.locals.state` to saved state (%s)', req.state.handle);
            iter(null, undefined, req.state.handle);
          });
        } else if (req.state.isNew()) {
          iter(null, req.state.returnTo, req.state.resumeState);
        } else { // current
          iter(null, undefined, req.state.handle);
        }
      }
      iter();
    }

    // Initialize a state if the browser didn't send a state handle, or the
    // endpoint indicated that state, if any, is external to this app.
    //
    // The latter case is common with federated protocols, such as OpenID
    // Connect and OAuth 2.0, in which clients make requests that initiate
    // stateful transactions within the app making use of this middleware.
    // These requests may have a `state` parameter, but its value is meant to be
    // included when the browser is redirected back to the client so the client
    // can maintain its state.
    var h = getHandle(req);
    if (!h || options.external) {
      generate();
      next();
      return;
    }

    store.load(req, h, function(err, state) {
      if (err) { return next(err); }

      if (!state) {
        generate();
        next();
        return;
      }

      var url = utils.originalURLWithoutQuery(req);
      if (state.location !== url) {
        // The referened state is not intended for the endpoint that was
        // requested.
        //
        // A new state will be initialized which, when complete, will trigger
        // the referenced state to resume.  This process is managed by building
        // a logical stack of states as the browser is redirected or navigated
        // to a series of pages, where a reference to the current state is
        // relayed with each request.  Once a request is processed which does
        // not result in a further redirect, the stack unwinds by popping the
        // state to be resumed.
        inflate({ location: url, resumeState: h });
      } else {
        inflate(state, h);
      }

      next();
    });
  };
};
<commit_msg>Commit state when redirecting.<commit_after>var uri = require('url')
  , State = require('../state')
  , swizzleRedirect = require('../utils/swizzle-redirect')
  , swizzleRender = require('../utils/swizzle-render')
  , swizzleEnd = require('../utils/swizzle-end')
  , flatten = require('utils-flatten')
  , merge = require('utils-merge')
  , utils = require('../utils')
  , validate = require('../validate')
  , debug = require('debug')('flowstate')
  , SessionStore = require('../stores/session');

  // TIPS: Generally, push a state before redirecting (unless wizard style
  //  alternatively, handler can lazily push, for its own state, to preserve storage
  //  which could be done based on the presence of a returnTo (which is not the handlers URL)

/**
 * Load state.
 *
 * This middleware is used to load state associated with a request.  The state
 * will be made available at `req.state`.
 *
 * HTTP is a stateless protocol.  In order to support stateful interactions, the
 * client and server need to operate in coordination.  The server is responsible
 * for persisting state associated with a request.  The client is responsible for
 * indicating that state in subsequent requests to the server (via a `state`
 * parameter in the query or body, by default).  The server then loads the
 * previously persisted state in order to continue processing the transaction.
 *
 * Options:
 *
 *     name   set req.state only if the state name is equal to the option value
 *     required   require state, failing otherwise (default: false)
 *
 * Examples:
 *
 *     app.get('/login',
 *       flowstate.loadState(),
 *       function(req, res, next) {
 *         res.locals.failureCount = req.state.failureCount;
 *         res.render('login');
 *       });
 *
 *     app.get('/login/callback',
 *       flowstate.loadState({ name: 'oauth2-redirect', required: true }),
 *       function(req, res, next) {
 *         // ...
 *       });
 *
 * @return {Function}
 * @api public
 */
module.exports = function(options) {
  options = options || {}

  var store = options.store || new SessionStore(options);
  var getHandle = options.getHandle || function(req) {
    return (req.query && req.query.state) || (req.body && req.body.state);
  }
  var validateRedirect = validate.origin;

  var mutationMethods = options.mutationMethods === undefined
    ? ['POST', 'PUT', 'PATCH', 'DELETE']
    : options.mutationMethods

  // WIP: On state init, set `resume` if the state is not intended for this lcoation

  // TODO: add a "locations" option, with relative paths that will be added to location

  return function state(req, res, next) {
    // self-awareness
    if (req.state) { return next(); }

    // expose store
    req.stateStore = store;

    req.pushState = function(data, url, options, cb) {
      if (typeof options == 'function') {
        cb = options;
        options = undefined;
      }
      options = options || {};

      // WIP: Test case where this is a relative URL
      data.location = uri.resolve(utils.originalURL(req), url);
      if (req.state.returnTo && !req.state.external) {
        data.returnTo = req.state.returnTo;
      } else if (req.state.resumeState) {
        data.resumeState = req.state.resumeState;
      }

      var state = new State(req, data);
      state.touch();
      req._stateStack.push(state);
      req.state = state;

      if (cb) {
        state.save(options, function(err) {
          if (err) { return cb(err); }
          return cb(null, state.handle);
        });
      }
    }

    res.resumeState = function(yields, cb) {
      if (typeof yields == 'function') {
        cb = yields;
        yields = undefined;
      }

      req.state.complete();

      if (req.state.returnTo) {
        // TODO: underscore the parameters, adn redirect with query
        this.redirect(req.state.returnTo);
      } else if (req.state.resumeState) {
        var self = this;

        req.state.destroy(function(err) {
          if (err) { return cb(err); } // FIXME: this should be next?

          store.load(req, req.state.resumeState, function(err, data) {
            if (err) { return cb(err); }  // FIXME: this should be next?

            // WIP: Need a state stack to traverse to do destroy on res.render/res.redirect

            var state = new State(req, data, req.state.resumeState);
            merge(state, yields);

            // TODO: This can be short-circuited to alway save, and optimize in there
            if (!state.isModified()) {
              self.redirect(data.location + '?state=' + state.handle);
            } else {
              state.save(function(err) {
                if (err) { return cb(err); }
                self.redirect(state.location + '?state=' + state.handle);
              });
            }
          });
        });
      } else {
        cb();
      }
    }

    // swizzle redirect to commit the state
    swizzleRedirect(res, function commitState(url, cb) {
      debug('redirect %s, %O (handle: %s, modified: %o, saved: %o, complete: %o, new: %o)', url, req.state,
        req.state.handle, req.state.isModified(), req.state.isSaved(), req.state.isComplete(), req.state.isNew());

      function proceed(returnTo, state) {
        if (!returnTo && !state) { return cb(); }

        // Add `state` as a query parameter to the URL that the user agent is
        // being redirected to.
        var l = uri.parse(url, true);
        delete l.search;
        if (returnTo) { l.query.return_to = returnTo; }
        if (state) { l.query.state = state; }
        return cb(null, uri.format(l));
      }

      commit(function(err, returnTo, state) {
        if (err) { return next(err); } // FIXME: This should call cb? No
        proceed(returnTo, state);
      });
    });

    // swizzle render to commit the state
    swizzleRender(res, function commitState(cb) {
      debug('render %O (handle: %s, modified: %o, saved: %o, complete: %o)', req.state,
        req.state.handle, req.state.isModified(), req.state.isSaved(), req.state.isComplete());

      // TODO: generalize this and make it an option, like csurf
      // TODO: dont' complete the state if it is flagged to continue

      // TODO: Shouldn't do this if done after resumeState.
      //.      swizzle it back to orig
      if (mutationMethods.indexOf(req.method) != -1) {
        if (Math.floor(res.statusCode / 100) == 2) {
          req.state.complete();
        }
      }

      commit(function(err, returnTo, state) {
        if (err) { return next(err); } // FIXME: This should call cb?

        if (returnTo) { res.locals.returnTo = returnTo; }
        else if (state) { res.locals.state = state; }
        cb();
      });
    });

    swizzleEnd(res, function autoDestroy(cb) {
      debug('end %O (handle: %s, complete: %o)', req.state,
        req.state.handle, req.state.isComplete());

      // WIP: figure out how to maintain incomplete states
      // probably best to mandate external states be  flagged complete ??

      commit(function() {
        cb();
      });
    });

    // WIP: More code review.  Put the stack in place and clean up on res.end.

    function generate() {
      var data = {
        location: utils.originalURLWithoutQuery(req)
      };

      var returnTo = (options.external ? utils.originalURL(req) : undefined)
        || (req.query && req.query.return_to)
        || (req.body && req.body.return_to)
        || (req.header ? req.header('referer') : undefined);
      if (returnTo) {
        var valid = validateRedirect(returnTo, req);
        if (valid) {
          data.returnTo = returnTo;
        }
      }

      req.state = new State(req, data, undefined, options.external);
      req._stateStack = [ req.state ];
    }

    function inflate(state, h) {
      req.state = new State(req, state, h);
      req._stateStack = [ req.state ];
    }

    function commit(cb) {
      var stack = req._stateStack
        , i = 0;

      function iter(err, returnTo, resumeState) {
        if (err) { return next(err); } // TODO: Test case for this

        var state = stack[i++];
        if (!state) { return cb(null, returnTo, resumeState); }

        if (req.state.isComplete()) {
          debug('destroying complete state (%s)', req.state.handle);
          req.state.destroy(function(err) {
            if (err) { return iter(err); }
            iter();
          });
        } else if (req.state.isModified()) {
          req.state.save(function(err) {
            if (err) { return iter(err); }
            debug('set `res.locals.state` to saved state (%s)', req.state.handle);
            iter(null, undefined, req.state.handle);
          });
        } else if (req.state.isNew()) {
          iter(null, req.state.returnTo, req.state.resumeState);
        } else { // current
          iter(null, undefined, req.state.handle);
        }
      }
      iter();
    }

    // Initialize a state if the browser didn't send a state handle, or the
    // endpoint indicated that state, if any, is external to this app.
    //
    // The latter case is common with federated protocols, such as OpenID
    // Connect and OAuth 2.0, in which clients make requests that initiate
    // stateful transactions within the app making use of this middleware.
    // These requests may have a `state` parameter, but its value is meant to be
    // included when the browser is redirected back to the client so the client
    // can maintain its state.
    var h = getHandle(req);
    if (!h || options.external) {
      generate();
      next();
      return;
    }

    store.load(req, h, function(err, state) {
      if (err) { return next(err); }

      if (!state) {
        generate();
        next();
        return;
      }

      var url = utils.originalURLWithoutQuery(req);
      if (state.location !== url) {
        // The referened state is not intended for the endpoint that was
        // requested.
        //
        // A new state will be initialized which, when complete, will trigger
        // the referenced state to resume.  This process is managed by building
        // a logical stack of states as the browser is redirected or navigated
        // to a series of pages, where a reference to the current state is
        // relayed with each request.  Once a request is processed which does
        // not result in a further redirect, the stack unwinds by popping the
        // state to be resumed.
        inflate({ location: url, resumeState: h });
      } else {
        inflate(state, h);
      }

      next();
    });
  };
};
<|endoftext|>