<commit_before>var express = require('express');
var request = require('request');
var cheerio = require('cheerio');
var mongoose = require('mongoose');
var app = express();

var port = 8080;

var Issue = require('./models/issue')
// ======================
// Router for API
var router = express.Router();

url = 'http://androidweekly.net';

router.get('/latest', function(req, res) {

	request(url, function (error, response, html) {
	    if (!error && response.statusCode == 200) {
	        var $  = cheerio.load(html);

	        var latest_issue, date;
	        var sections = [];
	        var links = [];
	        var content = [];
			var articles = [];

	        var metajson = {
				latest: '',
				date: '',
				sections: ''
			};

			//Get latest issue number and date
	        $('.issue-header').filter(function(){
	        	var data = $(this)

	        	latest_issue = data.children().first().text().trim();
	        	date = data.children().last().text().trim();

				//Get the first element of the regex match result
				//The output will be the lastes issue number
	        	metajson.latest = latest_issue.match(/\#([0-9]*)/)[1];
	        	metajson.date = date;
	        })

			//Extract topic content
			$('.sections tr p').each(function(i, element){
				var data = $(this);
				content[i] = data.text().trim();
			})

			//Get all articles from current issue
	        $('.article-headline').each(function(i, element){
	        	var data = $(this);
				var title = data.text().trim();
	        	var link = $(this).attr('href');

				var article = {
					title: title,
					link: link,
					content: content[i]
				}
				articles[i] = article;
	        })
			metajson.articles = articles;

			//Get all sections
			var sections_tr = []
			$('.sections tr').each(function(i, element){
				var data = $(this);
				var section_name = data.find('h2').text().trim();

				sections_tr[i] = section_name;
			})

			var counter = 0,
		        section_cnt = 0;
			if (sections_tr.length > 0) {
				for (i = sections_tr.length - 1; i >= 0; i--) {
					if (sections_tr[i] != 0) {
						var section = {
							section: sections_tr[i],
							articles: Math.floor(counter / 2)
						};
						sections[section_cnt] = section;
						section_cnt++;

						counter = 0;
					} else {
						counter++
					}
				}
			}

			metajson.sections = sections.reverse();
			metajson.sections.articles = articles;

	        res.json(metajson);
	    }
	});

})

router.get('/archive', function(req, res) {
	Issue.find(function(err, issues) {
		if (err)
			res.send(err)

		res.json(issues);
	})
})

function parseIssue(issue) {
	var regex = /Issue \#([0-9]*)|.+/;
	return issue.match(regex);
}

//====================
// Database
mongoose.connect('mongodb://127.0.0.1:27017/myawdb', function(err, db) {
    if (!err) {
        console.log("Database connected");
		//Everytime when we connect to the db
		//we want to drop the databse to refill
		//the data again. It's not much.
		mongoose.connection.db.dropDatabase();
	}
})

//====================
//Poll data from androidweekly.net
function pollArchive() {
	request(url + '/archive', function(error, response, html) {
		if (!error) {
			var $ = cheerio.load(html);
			var issues = [];

			$('.archive-list h3').each(function(i, element) {
				var data = $(this);
				var issue_number;

				data = data.text().trim();
				issue_number = parseIssue(data);

				var archive_response = {
					issue: issue_number[1],
					link: url + '/issues/issue-' + issue_number[1]
				}
				issues[i] = archive_response;
			})

			for (i = 0; i < issues.length; i++) {
				var issue = new Issue();
				issue.issue = issues[i].issue;
				issue.link = issues[i].link;
				issue.save();
			}

			//Check if there will be a new issue
			//once a week.
			//We have 86400 seconds in a day
			setInterval(pollArchive, 7*86400*1000);
		}
	})
}

app.use('/api', router);
app.listen(port);

console.log("Starting server at: " + port);
pollArchive();

exports = module.exports = app;
<commit_msg>Convert tabs to spaces<commit_after>var express = require('express');
var request = require('request');
var cheerio = require('cheerio');
var mongoose = require('mongoose');
var app = express();

var port = 8080;

var Issue = require('./models/issue')
// ======================
// Router for API
var router = express.Router();

url = 'http://androidweekly.net';

router.get('/latest', function(req, res) {

    request(url, function (error, response, html) {
        if (!error && response.statusCode == 200) {
            var $  = cheerio.load(html);

            var latest_issue, date;
            var sections = [];
            var links = [];
            var content = [];
            var articles = [];

            var metajson = {
                latest: '',
                date: '',
                sections: ''
            };

            //Get latest issue number and date
            $('.issue-header').filter(function(){
                var data = $(this)

                latest_issue = data.children().first().text().trim();
                date = data.children().last().text().trim();

                //Get the first element of the regex match result
                //The output will be the lastes issue number
                metajson.latest = latest_issue.match(/\#([0-9]*)/)[1];
                metajson.date = date;
            })

            //Extract topic content
            $('.sections tr p').each(function(i, element){
                var data = $(this);
                content[i] = data.text().trim();
            })

            //Get all articles from current issue
            $('.article-headline').each(function(i, element){
                var data = $(this);
                var title = data.text().trim();
                var link = $(this).attr('href');

                var article = {
                    title: title,
                    link: link,
                    content: content[i]
                }
                articles[i] = article;
            })
            metajson.articles = articles;

            //Get all sections
            var sections_tr = []
            $('.sections tr').each(function(i, element){
                var data = $(this);
                var section_name = data.find('h2').text().trim();

                sections_tr[i] = section_name;
            })

            var counter = 0,
                section_cnt = 0;
            if (sections_tr.length > 0) {
                for (i = sections_tr.length - 1; i >= 0; i--) {
                    if (sections_tr[i] != 0) {
                        var section = {
                            section: sections_tr[i],
                            articles: Math.floor(counter / 2)
                        };
                        sections[section_cnt] = section;
                        section_cnt++;

                        counter = 0;
                    } else {
                        counter++
                    }
                }
            }

            metajson.sections = sections.reverse();
            metajson.sections.articles = articles;

            res.json(metajson);
        }
    });

})

router.get('/archive', function(req, res) {
    Issue.find(function(err, issues) {
        if (err)
            res.send(err)

        res.json(issues);
    })
})

function parseIssue(issue) {
    var regex = /Issue \#([0-9]*)|.+/;
    return issue.match(regex);
}

//====================
// Database
mongoose.connect('mongodb://127.0.0.1:27017/myawdb', function(err, db) {
    if (!err) {
        console.log("Database connected");
        //Everytime when we connect to the db
        //we want to drop the databse to refill
        //the data again. It's not much.
        mongoose.connection.db.dropDatabase();
    }
})

//====================
//Poll data from androidweekly.net
function pollArchive() {
    request(url + '/archive', function(error, response, html) {
        if (!error) {
            var $ = cheerio.load(html);
            var issues = [];

            $('.archive-list h3').each(function(i, element) {
                var data = $(this);
                var issue_number;

                data = data.text().trim();
                issue_number = parseIssue(data);

                var archive_response = {
                    issue: issue_number[1],
                    link: url + '/issues/issue-' + issue_number[1]
                }
                issues[i] = archive_response;
            })

            for (i = 0; i < issues.length; i++) {
                var issue = new Issue();
                issue.issue = issues[i].issue;
                issue.link = issues[i].link;
                issue.save();
            }

            //Check if there will be a new issue
            //once a week.
            //We have 86400 seconds in a day
            setInterval(pollArchive, 7*86400*1000);
        }
    })
}

app.use('/api', router);
app.listen(port);

console.log("Starting server at: " + port);
pollArchive();

exports = module.exports = app;
<|endoftext|>


<commit_before>var indexedSet = [];
var selectedRow = -1;
var selectedRowID = 'null';
var rowCounter = 0;

function buildURL() {
    var base_URL = "/api/d3/amort";

    var term = $('#termbtn').text();
    if (term.indexOf("Year") != -1)
        base_URL += '/year?';
    else
        base_URL += "?";

    var rate = "r=" + getRateDec();
    var prin = "&P=" + getPrin();
    var num = "&n=" + getTerm();
    var taxes = "&t=" + getTax();
    var ins = "&i=" + getIns();

    // monthly extra
    var extra = "&e=" + getInput('e');

    URL = base_URL + rate + prin + num + taxes + ins + extra;

    if ($('#arm_type').is(':checked')) {
        var adj_freq = "&af=" + getAdjFreq();
        var adj_cap = "&ac=" + getAdjCapDec();
        var life_cap = "&lc=" + getLifetimeCapDec();

        URL += adj_freq + adj_cap + life_cap;
    }

    return [URL, term];
}

function addToChart(add) {
    var name = getInput('name');
    add = typeof add !== 'undefined' ? add : +1;

    if (name != 'null' && !checkName(name)) {
        // show table after adding first row
        $('#mortgages-table').show();

        // my clickable rows :)
        // Need to implement some select action on click that way you can remove/update the chart's data
        $('#mortgages-table tbody').append('
' +
            '' + name + '	' +
            '' + parseFloat(getPrin()).formatCurrency() + '	' +
            '' + getRate() +
            '%	' + '' + getTerm() + '	' +
            '
');

        // build index for mortgage
        var mort = getFormState();
        mort['name'] = name;
        indexedSet.push(mort);

        // add chart title - this was duplicating it, duh...
        // $("#chart1").prepend("
Chart title here :)
");
        submitUpdate(name, add);

        // add collapsible group for amortization table tab
        addTableCollapse('row_' + rowCounter + '_table', name);

        // add collapsible group for amortization summary tab
        addSummaryCollapse('row_' + rowCounter + '_summary', name);

        // increment row counter to keep IDs unique
        rowCounter++;
    } else {
        alert('Please add a unique or non-null name.');
    }
}

function removeFromChart() {
    // if there is a row selected
    if (selectedRow >= 0) {
        // remove the row
        $('#' + selectedRowID).remove();

        // if the table only contains the header row, hide it again
        if ($('#mortgages-table tr').length == 1) {
            $('#mortgages-table').hide();
        }

        // remove it from the indexedSet
        indexedSet.splice(selectedRow, 1);

        // remove the collapsible group from table tab
        $('#' + selectedRowID + '_table').parent().remove();

        // remove the collapsible group from summary tab
        $('#' + selectedRowID + '_summary').parent().remove();

        // remove data from chart
        submitUpdate('', -1);

        // deselect row since the selectedRow since it no longer exists
        selectedRow = -1;
    } else {
        alert("Please select a row to remove.");
    }
}

// TODO: Can probably create a loop that goes through the form and grabs each input id instead of individuals.
// Doing that would make the form state capture more automated.
function getFormState() {
    var json = { 'formstate': {
        'name': getInput('name'),
        'P': getPrin(),
        'r': getRate(),
        'n': getTerm(),
        'i': getIns(),
        't': getTax(),
        'af': getAdjFreq(),
        'ac': getAdjCap(),
        'lc': getLifetimeCap(),
        'e': getInput('e'),
        'fixed_type': $('#fixed_type').is(':checked'),
        'arm_type': $('#arm_type').is(':checked'),
        'termbtn': $('#termbtn').html()
    }};
    console.debug("Form state: " + JSON.stringify(json));

    return json;
}

function selectRow(tr) {
    selectedRowID = $(tr).attr('id');
    var mortgage_name = '';

    // iterate the td elements in the row and grab the name
    $(tr).find('td').each(function () {
        if ($(this).attr('id') == 'name') {
            mortgage_name = $(this).text();
        }
    });

    for (i = 0; i < indexedSet.length; i++) {
        if (indexedSet[i]['name'] == mortgage_name) {
            selectedRow = i;
        }
    }

    console.debug("Selected row: " + selectedRow);
    console.debug("Selected row id: " + selectedRowID);

    // update form with selected row
    var temp = indexedSet[selectedRow]['formstate'];
    for (var key in temp) {
        // Added this check to fix issue #6. Now the form updates completely.
        switch (key) {
            case 'termbtn':
                $('#' + key).html(temp[key]);
                break;
            case 'fixed_type':
            case 'arm_type':
                // this fires the click method when the saved value of the button is true; thereby causing the arm_type
                // fields to hide as they do when you click the fixed button.
//                if (temp[key]) {
//                    $('#' + key).click();
//                }
                // this way sets the checked property of the radio button
                $('#' + key).prop('checked', temp[key]);
                break;
            default:
                setInput(key, temp[key]);
        }
    }
}

function updateCurrentRow() {
    // Update the table row
    // grab the indexed data and set the form according to the row selected
    var temp = indexedSet[selectedRow];
    console.debug("Old row: " + JSON.stringify(temp));
    var new_set = getFormState();
    // set name on the top level of the array
    new_set['name'] = new_set['formstate']['name'];
    new_set['data'] = temp['data']; // transpose data to new set
    console.debug("New row: " + JSON.stringify(new_set));

    // update table
    $('#row_' + selectedRow).each(function () {
        $(this).find('td').each(function () {
            var id = $(this).attr('id');
            $(this).text(new_set['formstate'][id]);

            switch (id) {
                case 'r':
                    $(this).text(new_set['formstate'][id] + "%");
                    break;
                case 'P':
                    $(this).text(parseFloat(new_set['formstate'][id]).formatCurrency());
                    break;
            }
        });
    });

    // update index
    indexedSet[selectedRow] = new_set;
}

function checkName(name) {
    for (var i in indexedSet) {
        if (name == indexedSet[i]['name']) {
            return true;
        }
    }

    return false;
}

function tryUpdate() {
    if (selectedRow >= 0) {
        // only update when  row is selected
        submitUpdate();
    } else {
        alert("Please select a row to update or add a mortgage to the chart.");
    }
}

function getDataSet(legend_key) {
    var temp = [];

    for (var key in indexedSet) {
        var data = indexedSet[key]['data'];

        // if we pass no legend_key, return all
        if (typeof legend_key === 'undefined') {
            for (var dkey in data) {
                temp.push(data[dkey]);
            }
        } else {
            for (var dkey in data) {
                // take to lowercase so that I can still use camel case
                if (data[dkey]['key'].toLowerCase().indexOf(legend_key) != -1) {
                    temp.push(data[dkey]);
//                    console.debug("Matched \'" + legend_key + "\' to \'" + data[dkey]['key'] + "\'")
                } else {
//                    console.error("Failed to match \'" + legend_key + "\'");
                }
            }
        }
    }

    return temp;
}

function updateTableTab() {
    // clear the table
    $('#' + selectedRowID + '_table-summary').empty();
    // repopulate the table by calling the api
    populateTableCollapse(selectedRowID + '_table');
}

function updateSummaryTab() {
    // clear the table
    $('#' + selectedRowID + '_summary-summary').empty();
    // repopulate the table by calling the api
    populateSummaryCollapse(selectedRowID + '_summary');
}

function submitUpdate(name, change) {
    // default for name
    name = typeof name !== 'undefined' ? name : "";
    change = typeof change !== 'undefined' ? change : 0;

    var val = buildURL();
    var URL = val[0];
    var term = val[1];
    console.debug("Update URL: " + URL);

    d3.json(URL, function (error, json) {
        if (error) return console.warn(error);

        // myAddGraph was a good first step but I still need to figure out how to iterate over indexedDB items and graph
        switch (change) {
            // case remove
            case -1:
                // do nothing here b/c the call to getDataSet() will basically add in all the data from indexedSet
                if (indexedSet.length == 0) {
                    // this hackery isn't necessary if i switch to c3...
                    // hacky solution b/c of issue 349 open against nvd3 (old data still shows when no data presented).
                    // just remove the svg for now...might need to add a no data message or something here.
                    $('#chart-pane svg').each(function () {
                        $(this).remove();
                    });

                    $('#chart-pane div').each(function () {
                        $(this).append('<svg class=\"chart\"/>');
                    });
                }
                break;
            // case add
            case +1:
                var dataSet = [];
                json.forEach(function (d) {
                    // add mortgage name to key
                    var new_key = name + " " + d['key'];
//                    console.debug("Old key: " + d['key'] + " New key: " + new_key);
                    d['key'] = new_key;

                    dataSet.push(d);
                });

                for (var key in indexedSet) {
                    if (name == indexedSet[key]['name']) {
                        indexedSet[key]['data'] = dataSet;
                    }
                }
                break;
            // case update
            case 0:
            // purposeful fall through
            default:
                updateCurrentRow();

                indexedSet[selectedRow]['data'] = [];
                json.forEach(function (d) {
                    // add mortgage name to key
                    var new_key = indexedSet[selectedRow]['name'] + " " + d['key'];
//                    console.debug("Old key: " + d['key'] + " New key: " + new_key);
                    d['key'] = new_key;

                    indexedSet[selectedRow]['data'].push(d);
                });

                updateTableTab();
                updateSummaryTab();
                break;
        }

        // this is the block to add the logic for having a single chart with only one set, but that will be a lot of
        // small changes, so will do that after i get mutli chart working solidly
//        if (indexedSet.length <= 1) {
//            nv.addGraph(myAddGraph("single-chart", term));
//        } else {
        // taxes and insurance would be static values, so they shouldn't get their own chart
        var keyArr = ["balance", "principal", "interest", "amount", "extra payment"];
        for (var i=0; i < keyArr.length; i++) {
            key = keyArr[i];
            nv.addGraph(myAddGraph(key.replace(' ', '_') + "-chart", term, key));
        }
//        }
    });
}

function myAddGraph(name, term, key) {
    // this adds the stupid block that is causing the other tabs to display funny
    $('#chart-pane').show(); // show chart pane before rendering

    var chart = nv.models.lineChart()
        .useInteractiveGuideline(true)
        .margin({left: 100})
        .x(function (d) {
            return d[0]
        })
        .y(function (d) {
            return d[1]
        });

    chart.xAxis
        .axisLabel(term)
        .tickFormat(d3.format("d"));

    chart.yAxis
        //.axisLabel("Dollars") // removing this since I can use currency now :)
        .tickFormat(d3.format("$,.2f"));

    d3.select("#" + name + " svg")
        .datum(getDataSet(key))
        .transition().duration(500).call(chart);

    nv.utils.windowResize(chart.update);

    // remove the block style that was breaking the view...
    $('#chart-pane').removeAttr('style');

    return chart;
}

function addTableCollapse(id, name) {
    addCollapse('table-pane-accordion', id, name);
    populateTableCollapse(id);
}

function addSummaryCollapse(id, name) {
    addCollapse('summary-pane-accordion', id, name);
    populateSummaryCollapse(id);
}

function addCollapse(accord, id, name) {
    $('#' + accord).append("
 \

 \

 \
                 \
                " + name + " \
                 \

 \

 \

 \

 \

\

 \

 \

");
}

function populateTableCollapse(id) {
    var url_term = buildURL();
    var url = url_term[0].replace('d3/', '');
    var term = url_term[1].trim();

    $.getJSON(url, function (data) {
        var summary_selector = $('#' + id + '-summary').append('

' +
            '' + term + '	' +
            'Balance	' +
            'Amount	' +
            'Principal	' +
            'Interest	' +
            'Extra Payment	' +
            '

');
        $.each(data['table'], function (key, val) {
            var row = '

' +
                '' + val[term.toLowerCase()] + '	' +
                '' + val['balance'].formatCurrency() + '	' +
                '' + val['amount'].formatCurrency() + '	' +
                '' + val['principal'].formatCurrency() + '	' +
                '' + val['interest'].formatCurrency() + '	' +
                '' + val['extra payment'].formatCurrency() + '	' +
                '

'
            summary_selector.append(row);
        });
    });
}

function populateSummaryCollapse(id) {
    var url_term = buildURL();
    var url = url_term[0].replace('d3/', '');

    $.getJSON(url, function (data) {
        var summary_selector = $('#' + id + '-summary').append('

' +
            '90% LTV	' +
            '80% LTV	' +
            'Total Amount Paid	' +
            'Total Extra Payment Paid	' +
            'Total Principal Paid	' +
            'Total Interest Paid	' +
            '

');
        var val = data['summary'];
        var row = '

' +
            '' + val['90% ltv'].formatCurrency() + '	' +
            '' + val['80% ltv'].formatCurrency() + '	' +
            '' + val['total amount'].formatCurrency() + '	' +
            '' + val['total extra payment'].formatCurrency() + '	' +
            '' + val['total principal'].formatCurrency() + '	' +
            '' + val['total interest'].formatCurrency() + '	' +
            '

'
        summary_selector.append(row);
    });
}

Number.prototype.formatCurrency = function (n, x) {
    if (n == undefined) n = 2;
    var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\.' : '$') + ')';
    return '$' + this.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$&,');
};<commit_msg>Starting to remove some the dependencies on d3 in the UI where I can.<commit_after>var indexedSet = [];
var selectedRow = -1;
var selectedRowID = 'null';
var rowCounter = 0;

function buildURL() {
    var base_URL = "/api/d3/amort";

    var term = $('#termbtn').text();
    if (term.indexOf("Year") != -1)
        base_URL += '/year?';
    else
        base_URL += "?";

    var rate = "r=" + getRateDec();
    var prin = "&P=" + getPrin();
    var num = "&n=" + getTerm();
    var taxes = "&t=" + getTax();
    var ins = "&i=" + getIns();

    // monthly extra
    var extra = "&e=" + getInput('e');

    URL = base_URL + rate + prin + num + taxes + ins + extra;

    if ($('#arm_type').is(':checked')) {
        var adj_freq = "&af=" + getAdjFreq();
        var adj_cap = "&ac=" + getAdjCapDec();
        var life_cap = "&lc=" + getLifetimeCapDec();

        URL += adj_freq + adj_cap + life_cap;
    }

    return [URL, term];
}

function addToChart(add) {
    var name = getInput('name');
    add = typeof add !== 'undefined' ? add : +1;

    if (name != 'null' && !checkName(name)) {
        // show table after adding first row
        $('#mortgages-table').show();

        // my clickable rows :)
        // Need to implement some select action on click that way you can remove/update the chart's data
        $('#mortgages-table tbody').append('
' +
            '' + name + '	' +
            '' + parseFloat(getPrin()).formatCurrency() + '	' +
            '' + getRate() +
            '%	' + '' + getTerm() + '	' +
            '
');

        // build index for mortgage
        var mort = getFormState();
        mort['name'] = name;
        indexedSet.push(mort);

        // add chart title - this was duplicating it, duh...
        // $("#chart1").prepend("
Chart title here :)
");
        submitUpdate(name, add);

        // add collapsible group for amortization table tab
        addTableCollapse('row_' + rowCounter + '_table', name);

        // add collapsible group for amortization summary tab
        addSummaryCollapse('row_' + rowCounter + '_summary', name);

        // increment row counter to keep IDs unique
        rowCounter++;
    } else {
        alert('Please add a unique or non-null name.');
    }
}

function removeFromChart() {
    // if there is a row selected
    if (selectedRow >= 0) {
        // remove the row
        $('#' + selectedRowID).remove();

        // if the table only contains the header row, hide it again
        if ($('#mortgages-table tr').length == 1) {
            $('#mortgages-table').hide();
        }

        // remove it from the indexedSet
        indexedSet.splice(selectedRow, 1);

        // remove the collapsible group from table tab
        $('#' + selectedRowID + '_table').parent().remove();

        // remove the collapsible group from summary tab
        $('#' + selectedRowID + '_summary').parent().remove();

        // remove data from chart
        submitUpdate('', -1);

        // deselect row since the selectedRow since it no longer exists
        selectedRow = -1;
    } else {
        alert("Please select a row to remove.");
    }
}

// TODO: Can probably create a loop that goes through the form and grabs each input id instead of individuals.
// Doing that would make the form state capture more automated.
function getFormState() {
    var json = { 'formstate': {
        'name': getInput('name'),
        'P': getPrin(),
        'r': getRate(),
        'n': getTerm(),
        'i': getIns(),
        't': getTax(),
        'af': getAdjFreq(),
        'ac': getAdjCap(),
        'lc': getLifetimeCap(),
        'e': getInput('e'),
        'fixed_type': $('#fixed_type').is(':checked'),
        'arm_type': $('#arm_type').is(':checked'),
        'termbtn': $('#termbtn').html()
    }};
    console.debug("Form state: " + JSON.stringify(json));

    return json;
}

function selectRow(tr) {
    selectedRowID = $(tr).attr('id');
    var mortgage_name = '';

    // iterate the td elements in the row and grab the name
    $(tr).find('td').each(function () {
        if ($(this).attr('id') == 'name') {
            mortgage_name = $(this).text();
        }
    });

    for (i = 0; i < indexedSet.length; i++) {
        if (indexedSet[i]['name'] == mortgage_name) {
            selectedRow = i;
        }
    }

    console.debug("Selected row: " + selectedRow);
    console.debug("Selected row id: " + selectedRowID);

    // update form with selected row
    var temp = indexedSet[selectedRow]['formstate'];
    for (var key in temp) {
        // Added this check to fix issue #6. Now the form updates completely.
        switch (key) {
            case 'termbtn':
                $('#' + key).html(temp[key]);
                break;
            case 'fixed_type':
            case 'arm_type':
                // this fires the click method when the saved value of the button is true; thereby causing the arm_type
                // fields to hide as they do when you click the fixed button.
//                if (temp[key]) {
//                    $('#' + key).click();
//                }
                // this way sets the checked property of the radio button
                $('#' + key).prop('checked', temp[key]);
                break;
            default:
                setInput(key, temp[key]);
        }
    }
}

function updateCurrentRow() {
    // Update the table row
    // grab the indexed data and set the form according to the row selected
    var temp = indexedSet[selectedRow];
    console.debug("Old row: " + JSON.stringify(temp));
    var new_set = getFormState();
    // set name on the top level of the array
    new_set['name'] = new_set['formstate']['name'];
    new_set['data'] = temp['data']; // transpose data to new set
    console.debug("New row: " + JSON.stringify(new_set));

    // update table
    $('#row_' + selectedRow).each(function () {
        $(this).find('td').each(function () {
            var id = $(this).attr('id');
            $(this).text(new_set['formstate'][id]);

            switch (id) {
                case 'r':
                    $(this).text(new_set['formstate'][id] + "%");
                    break;
                case 'P':
                    $(this).text(parseFloat(new_set['formstate'][id]).formatCurrency());
                    break;
            }
        });
    });

    // update index
    indexedSet[selectedRow] = new_set;
}

function checkName(name) {
    for (var i in indexedSet) {
        if (name == indexedSet[i]['name']) {
            return true;
        }
    }

    return false;
}

function tryUpdate() {
    if (selectedRow >= 0) {
        // only update when  row is selected
        submitUpdate();
    } else {
        alert("Please select a row to update or add a mortgage to the chart.");
    }
}

function getDataSet(legend_key) {
    var temp = [];

    for (var key in indexedSet) {
        var data = indexedSet[key]['data'];

        // if we pass no legend_key, return all
        if (typeof legend_key === 'undefined') {
            for (var dkey in data) {
                temp.push(data[dkey]);
            }
        } else {
            for (var dkey in data) {
                // take to lowercase so that I can still use camel case
                if (data[dkey]['key'].toLowerCase().indexOf(legend_key) != -1) {
                    temp.push(data[dkey]);
//                    console.debug("Matched \'" + legend_key + "\' to \'" + data[dkey]['key'] + "\'")
                } else {
//                    console.error("Failed to match \'" + legend_key + "\'");
                }
            }
        }
    }

    return temp;
}

function updateTableTab() {
    // clear the table
    $('#' + selectedRowID + '_table-summary').empty();
    // repopulate the table by calling the api
    populateTableCollapse(selectedRowID + '_table');
}

function updateSummaryTab() {
    // clear the table
    $('#' + selectedRowID + '_summary-summary').empty();
    // repopulate the table by calling the api
    populateSummaryCollapse(selectedRowID + '_summary');
}

function submitUpdate(name, change) {
    // default for name
    name = typeof name !== 'undefined' ? name : "";
    change = typeof change !== 'undefined' ? change : 0;

    var val = buildURL();
    var URL = val[0];
    var term = val[1];
    console.debug("Update URL: " + URL);

    $.getJSON(URL, function (json) {
        // myAddGraph was a good first step but I still need to figure out how to iterate over indexedDB items and graph
        switch (change) {
            // case remove
            case -1:
                // do nothing here b/c the call to getDataSet() will basically add in all the data from indexedSet
                if (indexedSet.length == 0) {
                    // this hackery isn't necessary if i switch to c3...
                    // hacky solution b/c of issue 349 open against nvd3 (old data still shows when no data presented).
                    // just remove the svg for now...might need to add a no data message or something here.
                    $('#chart-pane svg').each(function () {
                        $(this).remove();
                    });

                    $('#chart-pane div').each(function () {
                        $(this).append('<svg class=\"chart\"/>');
                    });
                }
                break;
            // case add
            case +1:
                var dataSet = [];
                json.forEach(function (d) {
                    // add mortgage name to key
                    var new_key = name + " " + d['key'];
//                    console.debug("Old key: " + d['key'] + " New key: " + new_key);
                    d['key'] = new_key;

                    dataSet.push(d);
                });

                for (var key in indexedSet) {
                    if (name == indexedSet[key]['name']) {
                        indexedSet[key]['data'] = dataSet;
                    }
                }
                break;
            // case update
            case 0:
            // purposeful fall through
            default:
                updateCurrentRow();

                indexedSet[selectedRow]['data'] = [];
                json.forEach(function (d) {
                    // add mortgage name to key
                    var new_key = indexedSet[selectedRow]['name'] + " " + d['key'];
//                    console.debug("Old key: " + d['key'] + " New key: " + new_key);
                    d['key'] = new_key;

                    indexedSet[selectedRow]['data'].push(d);
                });

                updateTableTab();
                updateSummaryTab();
                break;
        }

        // this is the block to add the logic for having a single chart with only one set, but that will be a lot of
        // small changes, so will do that after i get mutli chart working solidly
//        if (indexedSet.length <= 1) {
//            nv.addGraph(myAddGraph("single-chart", term));
//        } else {
        // taxes and insurance would be static values, so they shouldn't get their own chart
        var keyArr = ["balance", "principal", "interest", "amount", "extra payment"];
        for (var i=0; i < keyArr.length; i++) {
            key = keyArr[i];
            nv.addGraph(myAddGraph(key.replace(' ', '_') + "-chart", term, key));
        }
//        }
    });
}

function myAddGraph(name, term, key) {
    // this adds the stupid block that is causing the other tabs to display funny
    $('#chart-pane').show(); // show chart pane before rendering

    var chart = nv.models.lineChart()
        .useInteractiveGuideline(true)
        .margin({left: 100})
        .x(function (d) {
            return d[0]
        })
        .y(function (d) {
            return d[1]
        });

    chart.xAxis
        .axisLabel(term)
        .tickFormat(d3.format("d"));

    chart.yAxis
        //.axisLabel("Dollars") // removing this since I can use currency now :)
        .tickFormat(d3.format("$,.2f"));

    d3.select("#" + name + " svg")
        .datum(getDataSet(key))
        .transition().duration(500).call(chart);

    nv.utils.windowResize(chart.update);

    // remove the block style that was breaking the view...
    $('#chart-pane').removeAttr('style');

    return chart;
}

function addTableCollapse(id, name) {
    addCollapse('table-pane-accordion', id, name);
    populateTableCollapse(id);
}

function addSummaryCollapse(id, name) {
    addCollapse('summary-pane-accordion', id, name);
    populateSummaryCollapse(id);
}

function addCollapse(accord, id, name) {
    $('#' + accord).append("
 \

 \

 \
                 \
                " + name + " \
                 \

 \

 \

 \

 \

\

 \

 \

");
}

function populateTableCollapse(id) {
    var url_term = buildURL();
    var url = url_term[0].replace('d3/', '');
    var term = url_term[1].trim();

    $.getJSON(url, function (data) {
        var summary_selector = $('#' + id + '-summary').append('

' +
            '' + term + '	' +
            'Balance	' +
            'Amount	' +
            'Principal	' +
            'Interest	' +
            'Extra Payment	' +
            '

');
        $.each(data['table'], function (key, val) {
            var row = '

' +
                '' + val[term.toLowerCase()] + '	' +
                '' + val['balance'].formatCurrency() + '	' +
                '' + val['amount'].formatCurrency() + '	' +
                '' + val['principal'].formatCurrency() + '	' +
                '' + val['interest'].formatCurrency() + '	' +
                '' + val['extra payment'].formatCurrency() + '	' +
                '

'
            summary_selector.append(row);
        });
    });
}

function populateSummaryCollapse(id) {
    var url_term = buildURL();
    var url = url_term[0].replace('d3/', '');

    $.getJSON(url, function (data) {
        var summary_selector = $('#' + id + '-summary').append('

' +
            '90% LTV	' +
            '80% LTV	' +
            'Total Amount Paid	' +
            'Total Extra Payment Paid	' +
            'Total Principal Paid	' +
            'Total Interest Paid	' +
            '

');
        var val = data['summary'];
        var row = '

' +
            '' + val['90% ltv'].formatCurrency() + '	' +
            '' + val['80% ltv'].formatCurrency() + '	' +
            '' + val['total amount'].formatCurrency() + '	' +
            '' + val['total extra payment'].formatCurrency() + '	' +
            '' + val['total principal'].formatCurrency() + '	' +
            '' + val['total interest'].formatCurrency() + '	' +
            '

'
        summary_selector.append(row);
    });
}

Number.prototype.formatCurrency = function (n, x) {
    if (n == undefined) n = 2;
    var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\.' : '$') + ')';
    return '$' + this.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$&,');
};<|endoftext|>


<commit_before>goog.provide('ol.geom.point');

goog.require('ol.geom.Point');
goog.require('ol.projection');

/**
 * @typedef {Array.<number>|Object} point Point.
 */
ol.PointLike;

/**
 * @export
 * @param {ol.PointLike} opt_arg Point.
 * @return {ol.geom.Point} Point.
 */
ol.geom.point = function(opt_arg){

    if (opt_arg instanceof ol.geom.Point) {
        return opt_arg;
    }

    var x = 0;
    var y = 0;
    var z;
    var projection;

    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        if (goog.isArray(opt_arg)) {
            x = opt_arg[0];
            y = opt_arg[1];
            z = opt_arg[2];
            projection = opt_arg[3];

        } else if (goog.isObject(opt_arg)) {
            x = opt_arg.x;
            y = opt_arg.y;
            z = opt_arg.z;
            projection = opt_arg.projection;
        } else {
            throw new Error('ol.geom.point');
        }
    }
    if (goog.isDef(projection)) {
        projection = ol.projection(projection);
    }

    var p = new ol.geom.Point(x,y,z,projection);
    return p;
};
goog.inherits(ol.geom.point, ol.geom.geometry);

/**
 * @export
 * @param {number=} opt_arg X.
 * @return {ol.geom.Point|number} Result.
 */
ol.geom.Point.prototype.x = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setX(opt_arg);
        return this;
    }
    else {
        return this.getX();
    }
};

/**
 * @export
 * @param {number=} opt_arg Y.
 * @return {ol.geom.Point|number} Result.
 */
ol.geom.Point.prototype.y = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setY(opt_arg);
        return this;
    }
    else {
        return this.getY();
    }
};

/**
 * @export
 * @param {number=} opt_arg Z.
 * @return {ol.geom.Point|number|undefined} Result.
 */
ol.geom.Point.prototype.z = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setZ(opt_arg);
        return this;
    }
    else {
        return this.getZ();
    }
};

/**
 * @export
 * @param {ol.Projection=} opt_arg Projection.
 * @return {ol.geom.Point|ol.Projection|undefined} Result.
 */
ol.geom.Point.prototype.projection = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setProjection(ol.projection(opt_arg));
        return this;
    }
    else {
        return this.getProjection();
    }
};<commit_msg>API point fixed (using ADVANCED compilation)<commit_after>goog.provide('ol.geom.point');

goog.require('ol.geom.Point');
goog.require('ol.projection');

/**
 * @typedef {Array.<number>|Object} point Point.
 */
ol.PointLike;

/**
 * @export
 * @param {ol.PointLike} opt_arg Point.
 * @return {ol.geom.Point} Point.
 */
ol.geom.point = function(opt_arg){

    if (opt_arg instanceof ol.geom.Point) {
        return opt_arg;
    }

    var x = 0;
    var y = 0;
    var z;
    var projection;

    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        if (goog.isArray(opt_arg)) {
            x = opt_arg[0];
            y = opt_arg[1];
            z = opt_arg[2];
            projection = opt_arg[3];

        } else if (goog.isObject(opt_arg)) {
            x = opt_arg['x'];
            y = opt_arg['y'];
            z = opt_arg['z'];
            projection = opt_arg['projection'];
        } else {
            throw new Error('ol.geom.point');
        }
    }
    if (goog.isDef(projection)) {
        projection = ol.projection(projection);
    }

    var p = new ol.geom.Point(x,y,z,projection);
    return p;
};
goog.inherits(ol.geom.point, ol.geom.geometry);

/**
 * @export
 * @param {number=} opt_arg X.
 * @return {ol.geom.Point|number} Result.
 */
ol.geom.Point.prototype.x = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setX(opt_arg);
        return this;
    }
    else {
        return this.getX();
    }
};

/**
 * @export
 * @param {number=} opt_arg Y.
 * @return {ol.geom.Point|number} Result.
 */
ol.geom.Point.prototype.y = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setY(opt_arg);
        return this;
    }
    else {
        return this.getY();
    }
};

/**
 * @export
 * @param {number=} opt_arg Z.
 * @return {ol.geom.Point|number|undefined} Result.
 */
ol.geom.Point.prototype.z = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setZ(opt_arg);
        return this;
    }
    else {
        return this.getZ();
    }
};

/**
 * @export
 * @param {ol.Projection=} opt_arg Projection.
 * @return {ol.geom.Point|ol.Projection|undefined} Result.
 */
ol.geom.Point.prototype.projection = function(opt_arg){
    if (arguments.length == 1 && goog.isDef(opt_arg)) {
        this.setProjection(ol.projection(opt_arg));
        return this;
    }
    else {
        return this.getProjection();
    }
};<|endoftext|>


<commit_before>import React from 'react'
import Stats from '../Stats'
import Hit from '../Hit'

import './style.scss'

function renderHasMore (refine) {
  return (

        Load more

  )
}

function CustomHits (props) {
  const {toggle, get, hits, refine, hasMore} = props
  const _props = { toggle, get }

  return (

      <Stats />

        {hits.map((item, key) => <Hit item={item} key={key} {..._props} />)}

      {hasMore && renderHasMore(refine)}

  )
}

export default CustomHits
<commit_msg>Improve mobile button style<commit_after>import React from 'react'
import Stats from '../Stats'
import Hit from '../Hit'

import './style.scss'

function renderHasMore (refine) {
  return (

        Load more

  )
}

function CustomHits (props) {
  const {toggle, get, hits, refine, hasMore} = props
  const _props = { toggle, get }

  return (

      <Stats />

        {hits.map((item, key) => <Hit item={item} key={key} {..._props} />)}

      {hasMore && renderHasMore(refine)}

  )
}

export default CustomHits
<|endoftext|>


<commit_before>/**
 *
 * WoT.City Open Source Project
 *
 * Copyright 2015 Jollen
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

 "use strict";

/**
 * Module dependencies.
 */

var http = require("http")
  , url = require("url")
  , cluster = require('cluster')
  , WebSocketServer = require('websocket').server
  , EventEmitter = require('events').EventEmitter
  , util = require('util');

/**
 * Expose `WebsocketBroker` constructor.
 */
if (typeof(module) != "undefined" && typeof(exports) != "undefined") {
  exports = module.exports = WebsocketBroker;
}

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {Object} options
 * @api private
 */

function WebsocketBroker(options) {
  // Superclass Constructor
  EventEmitter.call(this);

  options = options || {};
  this.clientsPath = [];
  this.host = options.host ? options.host : 'localhost';
  this.port = options.port ? options.port : 8000;
}

util.inherits(WebsocketBroker, EventEmitter);

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {Object} request
 * @param {Object} response
 * @api private
 */

WebsocketBroker.prototype.onRequest = function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end();
};

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {String} path
 * @param {Object} data
 * @api private
 */

WebsocketBroker.prototype.dispatchData = function(path, data) {
  var connections = this.clientsPath[path];

  if (typeof(connections) === 'undefined')
    return;

  //console.log('Pushing [' + data + '] to ' + path);

  for (var i = 0; i < connections.length; i++) {
    connections[i].sendUTF(data);
  }
};

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {Object} request
 * @param {Object} response
 * @api private
 */

WebsocketBroker.prototype.dispatchStatus = function(path, data) {
  var connections = this.clientsPath[path];

  if (typeof connections === 'undefined')
    return;

  //console.log('Pushing [' + data + '] to ' + path);

  for (var i = 0; i < connections.length; i++) {
    connections[i].sendUTF(data);
  }
};

/**
 * Start websocket server.
 *
 * @param {Object} route
 * @return {}
 * @api public
 */

WebsocketBroker.prototype.start = function(route, handlers) {
  var self = this;

  if (cluster.isMaster) {
      // Count the machine's CPUs
      var cpuCount = require('os').cpus().length;

      //console.info('CPUs: ' + cpuCount);

      // Create a worker on each CPU
      for (var i = 0; i < cpuCount ; i++) {
          var port = this.port + i;
          cluster.fork({
            HOST: this.host,
            PORT: port
          });
      }

      return true;
  }

  // arguments to child processes
  var port = process.env['PORT'];
  var host = process.env['HOST'];

  var server = http.createServer(this.onRequest).listen(port, host, function() {
      var workerinfo = "";
      if (cluster.isWorker) workerinfo = " on CPU " + cluster.worker.id;
      console.info('WoT/WebSocket server is listening at ws:// ' + self.host + ':' + self.port + workerinfo);
  });

  var wsServer = new WebSocketServer({
    httpServer: server,
    autoAcceptConnections: false
  });

  /**
   * handlers
   */
  var onWsRequest = function(request) {
    var connection = request.accept('', request.origin);

    //console.log("[2]: onWsRequest");
    //console.log("[3]: resource: " + request.resource);

    // put worker object into connection
    connection.worker = cluster.worker;

    route(request.resource, connection, handlers, self.clientsPath);

    // register this thing
    self.emit('newThing', {
      name: connection.pathname
    });

    connection.on('message', onWsConnMessage);
    connection.on('close', onWsConnClose);

    if (typeof (connection.statusViewer) !== 'undefined')
      self.dispatchStatus(connection.statusViewer, JSON.stringify({ isAlive: true }));
  };

  var onWsConnMessage = function(message) {
    //console.log('onWsConnMessage: ' + this.pathname);
    //console.log('Received: ' + message.utf8Data);

    self.emit('data', {
      data: message.utf8Data
    });

    // Is it a sender ? Yes, then push data to all viewers.
    if (typeof (this.viewer) !== 'undefined')
      self.dispatchData(this.viewer, message.utf8Data);

    if (typeof (this.statusViewer) !== 'undefined')
      self.dispatchStatus(this.statusViewer, JSON.stringify({ isAlive: true }));
  };

  var onWsConnect = function(webSocketConnection) {
    console.log("[1]: onWsConnect: a new thing is connected");

    //webSocketConnection.on('message', onWsConnMessage);
    //webSocketConnection.on('close', onWsConnClose);
  };

  var onWsConnClose = function(reasonCode, description) {
    //console.log('Peer disconnected with reason: ' + reasonCode);

    // remove an element from Array
    //clientsConn.splice( clientsConn.indexOf(this), 1 );

    if (typeof (this.statusViewer) !== 'undefined')
        self.dispatchStatus(this.statusViewer, JSON.stringify({ isAlive: false }));
  };

  wsServer.on('request', onWsRequest);
  wsServer.on('connect', onWsConnect);
};<commit_msg>Minor fix<commit_after>/**
 *
 * WoT.City Open Source Project
 *
 * Copyright 2015 Jollen
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

 "use strict";

/**
 * Module dependencies.
 */

var http = require("http")
  , url = require("url")
  , cluster = require('cluster')
  , WebSocketServer = require('websocket').server
  , EventEmitter = require('events').EventEmitter
  , util = require('util');

/**
 * Expose `WebsocketBroker` constructor.
 */
if (typeof(module) != "undefined" && typeof(exports) != "undefined") {
  exports = module.exports = WebsocketBroker;
}

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {Object} options
 * @api private
 */

function WebsocketBroker(options) {
  // Superclass Constructor
  EventEmitter.call(this);

  options = options || {};
  this.clientsPath = [];
  this.host = options.host ? options.host : 'localhost';
  this.port = options.port ? options.port : 8000;
}

util.inherits(WebsocketBroker, EventEmitter);

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {Object} request
 * @param {Object} response
 * @api private
 */

WebsocketBroker.prototype.onRequest = function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end();
};

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {String} path
 * @param {Object} data
 * @api private
 */

WebsocketBroker.prototype.dispatchData = function(path, data) {
  var connections = this.clientsPath[path];

  if (typeof(connections) === 'undefined')
    return;

  //console.log('Pushing [' + data + '] to ' + path);

  for (var i = 0; i < connections.length; i++) {
    connections[i].sendUTF(data);
  }
};

/**
 * Initialize a new `WebsocketBroker` with the given `options`.
 *
 * @param {Object} request
 * @param {Object} response
 * @api private
 */

WebsocketBroker.prototype.dispatchStatus = function(path, data) {
  var connections = this.clientsPath[path];

  if (typeof connections === 'undefined')
    return;

  //console.log('Pushing [' + data + '] to ' + path);

  for (var i = 0; i < connections.length; i++) {
    connections[i].sendUTF(data);
  }
};

/**
 * Start websocket server.
 *
 * @param {Object} route
 * @return {}
 * @api public
 */

WebsocketBroker.prototype.start = function(route, handlers) {
  var self = this;

  if (cluster.isMaster) {
      // Count the machine's CPUs
      var cpuCount = require('os').cpus().length;

      //console.info('CPUs: ' + cpuCount);

      // Create a worker on each CPU
      for (var i = 0; i < cpuCount ; i++) {
          var port = this.port + i;
          cluster.fork({
            HOST: this.host,
            PORT: port
          });
      }

      return true;
  }

  // arguments to child processes
  var port = process.env['PORT'];
  var host = process.env['HOST'];

  var server = http.createServer(this.onRequest).listen(port, host, function() {
      var workerinfo = "";
      if (cluster.isWorker) workerinfo = " on CPU " + cluster.worker.id;
      console.info('WoT/WebSocket server is listening at ws://' + self.host + ':' + self.port + workerinfo);
  });

  var wsServer = new WebSocketServer({
    httpServer: server,
    autoAcceptConnections: false
  });

  /**
   * handlers
   */
  var onWsRequest = function(request) {
    var connection = request.accept('', request.origin);

    //console.log("[2]: onWsRequest");
    //console.log("[3]: resource: " + request.resource);

    // put worker object into connection
    connection.worker = cluster.worker;

    route(request.resource, connection, handlers, self.clientsPath);

    // register this thing
    self.emit('newThing', {
      name: connection.pathname
    });

    connection.on('message', onWsConnMessage);
    connection.on('close', onWsConnClose);

    if (typeof (connection.statusViewer) !== 'undefined')
      self.dispatchStatus(connection.statusViewer, JSON.stringify({ isAlive: true }));
  };

  var onWsConnMessage = function(message) {
    //console.log('onWsConnMessage: ' + this.pathname);
    //console.log('Received: ' + message.utf8Data);

    self.emit('data', {
      data: message.utf8Data
    });

    // Is it a sender ? Yes, then push data to all viewers.
    if (typeof (this.viewer) !== 'undefined')
      self.dispatchData(this.viewer, message.utf8Data);

    if (typeof (this.statusViewer) !== 'undefined')
      self.dispatchStatus(this.statusViewer, JSON.stringify({ isAlive: true }));
  };

  var onWsConnect = function(webSocketConnection) {
    console.log("[1]: onWsConnect: a new thing is connected");

    //webSocketConnection.on('message', onWsConnMessage);
    //webSocketConnection.on('close', onWsConnClose);
  };

  var onWsConnClose = function(reasonCode, description) {
    //console.log('Peer disconnected with reason: ' + reasonCode);

    // remove an element from Array
    //clientsConn.splice( clientsConn.indexOf(this), 1 );

    if (typeof (this.statusViewer) !== 'undefined')
        self.dispatchStatus(this.statusViewer, JSON.stringify({ isAlive: false }));
  };

  wsServer.on('request', onWsRequest);
  wsServer.on('connect', onWsConnect);
};
<|endoftext|>


<commit_before>/* minimal couch in node
 *
 * copyright 2011 nuno job <nunojob.com> (oO)--',--
 *
 * licensed under the apache license, version 2.0 (the "license");
 * you may not use this file except in compliance with the license.
 * you may obtain a copy of the license at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * unless required by applicable law or agreed to in writing, software
 * distributed under the license is distributed on an "as is" basis,
 * without warranties or conditions of any kind, either express or implied.
 * see the license for the specific language governing permissions and
 * limitations under the license.
 */
var request     = require('request')
  , fs          = require('fs')
  , qs          = require('querystring')
  , _           = require('underscore')
  , error       = require('./error')
  , default_url = "http://localhost:5984"
  , nano;

/*
 * nano is a library that helps you building requests to couchdb
 * that is built on top of mikeals/request
 *
 * no more, no less
 * be creative. be silly. have fun! relax (and don't forget to compact).
 *
 * dinosaurs spaceships!
 */
module.exports = exports = nano = function database_module(cfg) {
  var public_functions = {};
  if(typeof cfg === "string") {
    if(/^https?:/.test(cfg)) { cfg = {url: cfg}; } // url
    else {
      try { cfg = require(cfg); } // file path
      catch(e) { console.error("bad cfg: couldn't load file"); }
    }
  }
  if(cfg.proxy) {
    request = request.defaults({proxy: cfg.proxy}); // proxy support
  }
  if(!cfg.url) {
    console.error("bad cfg: using default=" + default_url);
    cfg = {url: default_url}; // if everything else fails, use default
  }

 /****************************************************************************
  * relax                                                                      *
  ****************************************************************************/
 /*
  * relax
  *
  * base for all request using nano
  * this function assumes familiarity with the couchdb api
  *
  * e.g.
  * nano.request( { db: "alice"
  *               , doc: "rabbit"
  *               , method: "GET"
  *               , params: { rev: "1-967a00dff5e02add41819138abb3284d"}
  *               },
  *   function (_,_,b) { console.log(b) });
  *
  * @error {request:socket} problem connecting to couchdb
  * @error {couch:*} an error proxied from couchdb
  *
  * @param {opts:object} request options; e.g. {db: "test", method: "GET"}
  *        {opts.db:string} database name
  *        {opts.method:string:optional} http method, defaults to "GET"
  *        {opts.doc:string:optional} document name
  *        {opts.att:string:optional} attachment name
  *        {opts.content_type:string:optional} content type, default to json
  *        {opts.body:object|string|binary:optional} document or attachment body
  *        {opts.encoding:string:optional} encoding for attachments
  * @param {callback:function:optional} function to call back
  */
  function relax(opts,callback) {
    var url    = cfg.url + "/" + opts.db
      , headers = { "content-type": "application/json" }
      , req    = { method: (opts.method || "GET"), headers: headers }
      , params = opts.params
      , status_code
      , parsed
      , rh;
    if(opts.doc)  {
      url += "/" + opts.doc; // add the document to the url
      if(opts.att) { url += "/" + opts.att; } // add the attachment to the url
    }
    if(opts.content_type) { req.headers["content-type"] = opts.content_type; }
    if(opts.encoding) { req.encoding = opts.encoding; }
    req.uri = url + (_.isEmpty(params) ? "" : "?" + qs.stringify(params));
    if(!callback) { return request(req); } // void callback, pipe
    if(opts.body) {
      if (Buffer.isBuffer(opts.body)) {
        req.body = opts.body; // raw data
      }
      else { req.body = JSON.stringify(opts.body); } // json data
    }
    request(req, function(e,h,b){
      if(e) { return callback(error.request_err(e,"socket",req,status_code),{},b); }
      rh = (h.headers || {});
      rh['status-code'] = status_code = h.statusCode;
      delete rh.server; // prevent security vunerabilities related to couchdb
      try { parsed = JSON.parse(b); } catch (err) { parsed = b; } // did we get json or binary?
      if (status_code >= 200 && status_code < 300) {
        callback(null,rh,parsed);
      }
      else { // proxy the error directly from couchdb
        callback(error.couch_err(parsed.reason,parsed.error,req,status_code),rh,parsed);
      }
    });
  }

 /****************************************************************************
  * db                                                                       *
  ****************************************************************************/
 /*
  * creates a couchdb database
  * http://wiki.apache.org/couchdb/HTTP_database_API
  *
  * e.g. function recursive_retries_create_db(tried,callback) {
  *        nano.db.create(db_name, function (e,b) {
  *          if(tried.tried === tried.max_retries) {
  *            callback("Retries work");
  *            return;
  *          }
  *          else {
  *            tried.tried += 1;
  *            recursive_retries_create_db(tried,callback);
  *          }
  *        });
  *      }
  *
  * @param {db_name:string} database name
  *
  * @see relax
  */
  function create_db(db_name, callback) {
    return relax({db: db_name, method: "PUT"},callback);
  }

 /*
  * annihilates a couchdb database
  *
  * e.g. nano.db.delete(db_name);
  *
  * even though this examples looks sync it is an async function
  *
  * @param {db_name:string} database name
  *
  * @see relax
  */
  function destroy_db(db_name, callback) {
    return relax({db: db_name, method: "DELETE"},callback);
  }

 /*
  * gets information about a couchdb database
  *
  * e.g. nano.db.get(db_name, function(e,b) {
  *        console.log(b);
  *      });
  *
  * @param {db_name:string} database name
  *
  * @see relax
  */
  function get_db(db_name, callback) {
    return relax({db: db_name, method: "GET"},callback);
  }

 /*
  * lists all the databases in couchdb
  *
  * e.g. nano.db.list(function(e,b) {
  *        console.log(b);
  *      });
  *
  * @see relax
  */
  function list_dbs(callback) {
    return relax({db: "_all_dbs", method: "GET"},callback);
  }

 /*
  * compacts a couchdb database
  *
  * e.g. nano.db.compact(db_name);
  *
  * @param {db_name:string} database name
  * @param {design_name:string:optional} design document name
  *
  * @see relax
  */
  function compact_db(db_name, design_name, callback) {
    if(typeof design_name === "function") {
      callback = design_name;
      design_name = null;
    }
    return relax({db: db_name, doc: "_compact", att: design_name, method: "POST"},callback);
  }

 /*
  * couchdb database _changes feed
  *
  * e.g. nano.db.changes(db_name, {since: 2}, function (e,h,r) {
  *        console.log(r);
  *      });
  *
  * @param {db_name:string} database name
  * @param {params:object:optional} additions to the querystring
  *
  * @see relax
  */
  function changes_db(db_name, params, callback) {
    if(typeof params === "function") {
      callback = params;
      params = {};
    }
    return relax({db: db_name, doc: "_changes", params: params, method: "GET"},callback);
  }

 /*
  * replicates a couchdb database
  *
  * e.g. nano.db.replicate(db_1, db_2);
  *
  * @param {source:string} name of the source database
  * @param {target:string} name of the target database
  * @param {continuous:bool:optional} continuous replication on?
  *
  * @see relax
  */
  function replicate_db(source, target, continuous, callback) {
    if(typeof continuous === "function") {
      callback   = continuous;
      continuous = false;
    }
    var body = {source: source, target: target};
    if(continuous) { body.continuous = true; }
    return relax({db: "_replicate", body: body, method: "POST"},callback);
  }

 /****************************************************************************
  * doc                                                                      *
  ****************************************************************************/
  function document_module(db_name) {
    var public_functions = {};

   /*
    * inserts a document in a couchdb database
    * http://wiki.apache.org/couchdb/HTTP_Document_API
    *
    * @param {doc:object|string} document body
    * @param {doc_name:string:optional} document name
    *
    * @see relax
    */
    function insert_doc(doc,doc_name,callback) {
      var opts = {db: db_name, body: doc, method: "POST"};
      if(doc_name) {
        if(typeof doc_name === "function") {
          callback = doc_name;
        }
        else {
          opts.doc = doc_name;
          opts.method = "PUT";
        }
      }
      return relax(opts,callback);
    }

   /*
    * destroy a document from a couchdb database
    *
    * @param {doc_name:string} document name
    * @param {rev:string} previous document revision
    *
    * @see relax
    */
    function destroy_doc(doc_name,rev,callback) {
      return relax({db: db_name, doc: doc_name, method: "DELETE", params: {rev: rev}},
        callback);
    }

   /*
    * get a document from a couchdb database
    *
    * e.g. db2.get("foo", {revs_info: true}, function (e,h,b) {
    *        console.log(e,h,b);
    *        return;
    *      });
    *
    * @param {doc_name:string} document name
    * @param {params:object:optional} additions to the querystring
    *
    * @see relax
    */
    function get_doc(doc_name,params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({db: db_name, doc: doc_name, method: "GET", params: params},callback);
    }

   /*
    * lists all the documents in a couchdb database
    *
    * @param {params:object:optional} additions to the querystring
    *
    * @see get_doc
    * @see relax
    */
    function list_docs(params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({db: db_name, doc: "_all_docs", method: "GET", params: params},callback);
    }

   /*
    * bulk update/delete/insert functionality
    * [1]: http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
    *
    * @param {docs:object} documents as per the couchdb api[1]
    *
    * @see get_doc
    * @see relax
    */
    function bulk_docs(docs,callback) {
      return relax({db: db_name, doc: "_bulk_docs", body: docs, method: "POST"},callback);
    }

   /**************************************************************************
    * attachment                                                             *
    **************************************************************************/
   /*
    * inserting an attachment
    * [2]: http://wiki.apache.org/couchdb/HTTP_Document_API
    *
    * e.g.
    * db.attachment.insert("new", "att", buffer, "image/bmp", {rev: b.rev},
    *   function(_,_,response) {
    *     console.log(response);
    * });
    *
    * don't forget that params.rev is required in most cases. only exception
    * is when creating a new document with a new attachment. consult [2] for
    * details
    *
    * @param {doc_name:string} document name
    * @param {att_name:string} attachment name
    * @param {att:buffer} attachment data
    * @param {content_type:string} attachment content-type
    * @param {params:object:optional} additions to the querystring
    *
    * @see relax
    */
    function insert_att(doc_name,att_name,att,content_type,params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({ db: db_name, att: att_name, method: "PUT", content_type: content_type
                   , doc: doc_name, params: params, body: att},callback);
    }

   /*
    * get an attachment
    *
    * @param {doc_name:string} document name
    * @param {att_name:string} attachment name
    * @param {params:object:optional} additions to the querystring
    *
    * @see relax
    */
    function get_att(doc_name,att_name,params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({ db: db_name, att: att_name, method: "GET", doc: doc_name
                   , params: params, encoding: "binary"},callback);
    }

   /*
    * destroy an attachment
    *
    * @param {doc_name:string} document name
    * @param {att_name:string} attachment name
    * @param {rev:string} previous document revision
    *
    * @see relax
    */
    function destroy_att(doc_name,att_name,rev,callback) {
      return relax({ db: db_name, att: att_name, method: "DELETE"
                  , doc: doc_name, params: {rev: rev}},callback);
    }

    public_functions = { info: function(cb) { return get_db(db_name,cb); }
                       , replicate: function(target,continuous,cb) {
                           if(typeof continuous === "function") {
                             cb         = continuous;
                             continuous = false;
                           }
                           return replicate_db(db_name,target,continuous,cb);
                         }
                       , compact: function(cb) { return compact_db(db_name,cb); }
                       , changes: function(params,cb) {
                           return changes_db(db_name,params,cb);
                         }
                       , insert: insert_doc
                       , get: get_doc
                       , destroy: destroy_doc
                       , bulk: bulk_docs
                       , list: list_docs
                       , view: { compact: function(design_name,cb) {
                                   return compact_db(db_name,design_name,cb); }
                               }
                       , attachment: { insert: insert_att
                                     , get: get_att
                                     , destroy: destroy_att
                                     }
                       };
    return public_functions;
  }

  public_functions = { db:  { create: create_db
                            , get: get_db
                            , destroy: destroy_db
                            , list: list_dbs
                            , use: document_module   // alias
                            , scope: document_module // alias
                            , compact: compact_db
                            , replicate: replicate_db
                            , changes: changes_db
                            }
                     , use: document_module
                     , scope: document_module        // alias
                     , request: relax
                     , config: cfg
                     , relax: relax                  // alias
                     , dinosaur: relax               // alias
                     };
  return public_functions;
};

/*
 * and now an ascii dinosaur
 *              _
 *            / _) ROAR! i'm a vegan!
 *     .-^^^-/ /
 *  __/       /
 * /__.|_|-|_|
 *
 * thanks for visiting! come again!
 *
 * LH1059-A321
 * LH1178-A321
 */

nano.version = JSON.parse(
  fs.readFileSync(__dirname + "/package.json")).version;
nano.path    = __dirname;<commit_msg>changed accept headers so json is returned<commit_after>/* minimal couch in node
 *
 * copyright 2011 nuno job <nunojob.com> (oO)--',--
 *
 * licensed under the apache license, version 2.0 (the "license");
 * you may not use this file except in compliance with the license.
 * you may obtain a copy of the license at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * unless required by applicable law or agreed to in writing, software
 * distributed under the license is distributed on an "as is" basis,
 * without warranties or conditions of any kind, either express or implied.
 * see the license for the specific language governing permissions and
 * limitations under the license.
 */
var request     = require('request')
  , fs          = require('fs')
  , qs          = require('querystring')
  , _           = require('underscore')
  , error       = require('./error')
  , default_url = "http://localhost:5984"
  , nano;

/*
 * nano is a library that helps you building requests to couchdb
 * that is built on top of mikeals/request
 *
 * no more, no less
 * be creative. be silly. have fun! relax (and don't forget to compact).
 *
 * dinosaurs spaceships!
 */
module.exports = exports = nano = function database_module(cfg) {
  var public_functions = {};
  if(typeof cfg === "string") {
    if(/^https?:/.test(cfg)) { cfg = {url: cfg}; } // url
    else {
      try { cfg = require(cfg); } // file path
      catch(e) { console.error("bad cfg: couldn't load file"); }
    }
  }
  if(cfg.proxy) {
    request = request.defaults({proxy: cfg.proxy}); // proxy support
  }
  if(!cfg.url) {
    console.error("bad cfg: using default=" + default_url);
    cfg = {url: default_url}; // if everything else fails, use default
  }

 /****************************************************************************
  * relax                                                                      *
  ****************************************************************************/
 /*
  * relax
  *
  * base for all request using nano
  * this function assumes familiarity with the couchdb api
  *
  * e.g.
  * nano.request( { db: "alice"
  *               , doc: "rabbit"
  *               , method: "GET"
  *               , params: { rev: "1-967a00dff5e02add41819138abb3284d"}
  *               },
  *   function (_,_,b) { console.log(b) });
  *
  * @error {request:socket} problem connecting to couchdb
  * @error {couch:*} an error proxied from couchdb
  *
  * @param {opts:object} request options; e.g. {db: "test", method: "GET"}
  *        {opts.db:string} database name
  *        {opts.method:string:optional} http method, defaults to "GET"
  *        {opts.doc:string:optional} document name
  *        {opts.att:string:optional} attachment name
  *        {opts.content_type:string:optional} content type, default to json
  *        {opts.body:object|string|binary:optional} document or attachment body
  *        {opts.encoding:string:optional} encoding for attachments
  * @param {callback:function:optional} function to call back
  */
  function relax(opts,callback) {
    var url    = cfg.url + "/" + opts.db
      , headers = { "content-type": "application/json"
                  , "accept": "application/json"
                  }
      , req    = { method: (opts.method || "GET"), headers: headers }
      , params = opts.params
      , status_code
      , parsed
      , rh;
    if(opts.doc)  {
      url += "/" + opts.doc; // add the document to the url
      if(opts.att) { url += "/" + opts.att; } // add the attachment to the url
    }
    if(opts.content_type) { req.headers["content-type"] = opts.content_type; }
    if(opts.encoding) { req.encoding = opts.encoding; }
    req.uri = url + (_.isEmpty(params) ? "" : "?" + qs.stringify(params));
    if(!callback) { return request(req); } // void callback, pipe
    if(opts.body) {
      if (Buffer.isBuffer(opts.body)) {
        req.body = opts.body; // raw data
      }
      else { req.body = JSON.stringify(opts.body); } // json data
    }
    request(req, function(e,h,b){
      if(e) { return callback(error.request_err(e,"socket",req,status_code),{},b); }
      rh = (h.headers || {});
      rh['status-code'] = status_code = h.statusCode;
      delete rh.server; // prevent security vunerabilities related to couchdb
      try { parsed = JSON.parse(b); } catch (err) { parsed = b; } // did we get json or binary?
      if (status_code >= 200 && status_code < 300) {
        callback(null,rh,parsed);
      }
      else { // proxy the error directly from couchdb
        callback(error.couch_err(parsed.reason,parsed.error,req,status_code),rh,parsed);
      }
    });
  }

 /****************************************************************************
  * db                                                                       *
  ****************************************************************************/
 /*
  * creates a couchdb database
  * http://wiki.apache.org/couchdb/HTTP_database_API
  *
  * e.g. function recursive_retries_create_db(tried,callback) {
  *        nano.db.create(db_name, function (e,b) {
  *          if(tried.tried === tried.max_retries) {
  *            callback("Retries work");
  *            return;
  *          }
  *          else {
  *            tried.tried += 1;
  *            recursive_retries_create_db(tried,callback);
  *          }
  *        });
  *      }
  *
  * @param {db_name:string} database name
  *
  * @see relax
  */
  function create_db(db_name, callback) {
    return relax({db: db_name, method: "PUT"},callback);
  }

 /*
  * annihilates a couchdb database
  *
  * e.g. nano.db.delete(db_name);
  *
  * even though this examples looks sync it is an async function
  *
  * @param {db_name:string} database name
  *
  * @see relax
  */
  function destroy_db(db_name, callback) {
    return relax({db: db_name, method: "DELETE"},callback);
  }

 /*
  * gets information about a couchdb database
  *
  * e.g. nano.db.get(db_name, function(e,b) {
  *        console.log(b);
  *      });
  *
  * @param {db_name:string} database name
  *
  * @see relax
  */
  function get_db(db_name, callback) {
    return relax({db: db_name, method: "GET"},callback);
  }

 /*
  * lists all the databases in couchdb
  *
  * e.g. nano.db.list(function(e,b) {
  *        console.log(b);
  *      });
  *
  * @see relax
  */
  function list_dbs(callback) {
    return relax({db: "_all_dbs", method: "GET"},callback);
  }

 /*
  * compacts a couchdb database
  *
  * e.g. nano.db.compact(db_name);
  *
  * @param {db_name:string} database name
  * @param {design_name:string:optional} design document name
  *
  * @see relax
  */
  function compact_db(db_name, design_name, callback) {
    if(typeof design_name === "function") {
      callback = design_name;
      design_name = null;
    }
    return relax({db: db_name, doc: "_compact", att: design_name, method: "POST"},callback);
  }

 /*
  * couchdb database _changes feed
  *
  * e.g. nano.db.changes(db_name, {since: 2}, function (e,h,r) {
  *        console.log(r);
  *      });
  *
  * @param {db_name:string} database name
  * @param {params:object:optional} additions to the querystring
  *
  * @see relax
  */
  function changes_db(db_name, params, callback) {
    if(typeof params === "function") {
      callback = params;
      params = {};
    }
    return relax({db: db_name, doc: "_changes", params: params, method: "GET"},callback);
  }

 /*
  * replicates a couchdb database
  *
  * e.g. nano.db.replicate(db_1, db_2);
  *
  * @param {source:string} name of the source database
  * @param {target:string} name of the target database
  * @param {continuous:bool:optional} continuous replication on?
  *
  * @see relax
  */
  function replicate_db(source, target, continuous, callback) {
    if(typeof continuous === "function") {
      callback   = continuous;
      continuous = false;
    }
    var body = {source: source, target: target};
    if(continuous) { body.continuous = true; }
    return relax({db: "_replicate", body: body, method: "POST"},callback);
  }

 /****************************************************************************
  * doc                                                                      *
  ****************************************************************************/
  function document_module(db_name) {
    var public_functions = {};

   /*
    * inserts a document in a couchdb database
    * http://wiki.apache.org/couchdb/HTTP_Document_API
    *
    * @param {doc:object|string} document body
    * @param {doc_name:string:optional} document name
    *
    * @see relax
    */
    function insert_doc(doc,doc_name,callback) {
      var opts = {db: db_name, body: doc, method: "POST"};
      if(doc_name) {
        if(typeof doc_name === "function") {
          callback = doc_name;
        }
        else {
          opts.doc = doc_name;
          opts.method = "PUT";
        }
      }
      return relax(opts,callback);
    }

   /*
    * destroy a document from a couchdb database
    *
    * @param {doc_name:string} document name
    * @param {rev:string} previous document revision
    *
    * @see relax
    */
    function destroy_doc(doc_name,rev,callback) {
      return relax({db: db_name, doc: doc_name, method: "DELETE", params: {rev: rev}},
        callback);
    }

   /*
    * get a document from a couchdb database
    *
    * e.g. db2.get("foo", {revs_info: true}, function (e,h,b) {
    *        console.log(e,h,b);
    *        return;
    *      });
    *
    * @param {doc_name:string} document name
    * @param {params:object:optional} additions to the querystring
    *
    * @see relax
    */
    function get_doc(doc_name,params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({db: db_name, doc: doc_name, method: "GET", params: params},callback);
    }

   /*
    * lists all the documents in a couchdb database
    *
    * @param {params:object:optional} additions to the querystring
    *
    * @see get_doc
    * @see relax
    */
    function list_docs(params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({db: db_name, doc: "_all_docs", method: "GET", params: params},callback);
    }

   /*
    * bulk update/delete/insert functionality
    * [1]: http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
    *
    * @param {docs:object} documents as per the couchdb api[1]
    *
    * @see get_doc
    * @see relax
    */
    function bulk_docs(docs,callback) {
      return relax({db: db_name, doc: "_bulk_docs", body: docs, method: "POST"},callback);
    }

   /**************************************************************************
    * attachment                                                             *
    **************************************************************************/
   /*
    * inserting an attachment
    * [2]: http://wiki.apache.org/couchdb/HTTP_Document_API
    *
    * e.g.
    * db.attachment.insert("new", "att", buffer, "image/bmp", {rev: b.rev},
    *   function(_,_,response) {
    *     console.log(response);
    * });
    *
    * don't forget that params.rev is required in most cases. only exception
    * is when creating a new document with a new attachment. consult [2] for
    * details
    *
    * @param {doc_name:string} document name
    * @param {att_name:string} attachment name
    * @param {att:buffer} attachment data
    * @param {content_type:string} attachment content-type
    * @param {params:object:optional} additions to the querystring
    *
    * @see relax
    */
    function insert_att(doc_name,att_name,att,content_type,params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({ db: db_name, att: att_name, method: "PUT", content_type: content_type
                   , doc: doc_name, params: params, body: att},callback);
    }

   /*
    * get an attachment
    *
    * @param {doc_name:string} document name
    * @param {att_name:string} attachment name
    * @param {params:object:optional} additions to the querystring
    *
    * @see relax
    */
    function get_att(doc_name,att_name,params,callback) {
      if(typeof params === "function") {
        callback = params;
        params   = {};
      }
      return relax({ db: db_name, att: att_name, method: "GET", doc: doc_name
                   , params: params, encoding: "binary"},callback);
    }

   /*
    * destroy an attachment
    *
    * @param {doc_name:string} document name
    * @param {att_name:string} attachment name
    * @param {rev:string} previous document revision
    *
    * @see relax
    */
    function destroy_att(doc_name,att_name,rev,callback) {
      return relax({ db: db_name, att: att_name, method: "DELETE"
                  , doc: doc_name, params: {rev: rev}},callback);
    }

    public_functions = { info: function(cb) { return get_db(db_name,cb); }
                       , replicate: function(target,continuous,cb) {
                           if(typeof continuous === "function") {
                             cb         = continuous;
                             continuous = false;
                           }
                           return replicate_db(db_name,target,continuous,cb);
                         }
                       , compact: function(cb) { return compact_db(db_name,cb); }
                       , changes: function(params,cb) {
                           return changes_db(db_name,params,cb);
                         }
                       , insert: insert_doc
                       , get: get_doc
                       , destroy: destroy_doc
                       , bulk: bulk_docs
                       , list: list_docs
                       , view: { compact: function(design_name,cb) {
                                   return compact_db(db_name,design_name,cb); }
                               }
                       , attachment: { insert: insert_att
                                     , get: get_att
                                     , destroy: destroy_att
                                     }
                       };
    return public_functions;
  }

  public_functions = { db:  { create: create_db
                            , get: get_db
                            , destroy: destroy_db
                            , list: list_dbs
                            , use: document_module   // alias
                            , scope: document_module // alias
                            , compact: compact_db
                            , replicate: replicate_db
                            , changes: changes_db
                            }
                     , use: document_module
                     , scope: document_module        // alias
                     , request: relax
                     , config: cfg
                     , relax: relax                  // alias
                     , dinosaur: relax               // alias
                     };
  return public_functions;
};

/*
 * and now an ascii dinosaur
 *              _
 *            / _) ROAR! i'm a vegan!
 *     .-^^^-/ /
 *  __/       /
 * /__.|_|-|_|
 *
 * thanks for visiting! come again!
 *
 * LH1059-A321
 * LH1178-A321
 */

nano.version = JSON.parse(
  fs.readFileSync(__dirname + "/package.json")).version;
nano.path    = __dirname;<|endoftext|>