<commit_before>var globalData = {
    totals: [],
    categories: {}
};

window.onload = function() {
    loadJSONFiles([
        [ 'appsURLs.json', onMostPopularLoaded ],
        [ 'totals.json', onTotalsLoaded ],
        [ 'categories.json', onCategoriesLoaded ]
    ]);
};

function loadJSONFiles(filesList, doneCallback) {

    doneCallback = doneCallback || function() {};

    var filesIter = iterator(filesList);
    var loadNext = function() {

        var nextValue = filesIter.next();

        if(nextValue.done) {
            doneCallback();
        } else {
            var tuple = nextValue.value;
            var request = new XMLHttpRequest();
            var jsonURL = tuple[0];
            var jsonCallback = tuple[1];

            request.open('GET', jsonURL);
            request.responseType = 'text';
            request.onload = function() {

                console.log('loaded', jsonURL);

                var data = JSON.parse(request.response);

                if(jsonCallback) {
                    jsonCallback(data);
                }

                setTimeout(loadNext, 1);
            };

            request.send();
        }
    };

    loadNext();

}

function onMostPopularLoaded(data) {
    console.log('most popular loaded', data.length);
    globalData.mostPopular = data;
}

function onTotalsLoaded(data) {
    console.log('totals loaded', data.length);
    globalData.totals = data;
}

function onCategoriesLoaded(data) {
    console.log('cats loaded', data.length);
    var catJSONs = data.map(function(d) {
        var name = d.split('/').pop();
        return [ 'category-' + name + '.json', function(catData) {
            onCategoryLoaded(name, catData);
        } ];
    });
    console.log(catJSONs);

    globalData.categories = {};

    loadJSONFiles(catJSONs, onAllDataLoaded);

}

function onCategoryLoaded(categoryName, data) {
    console.log('loaded category', categoryName);
    globalData.categories[categoryName] = data.map(function(d) {
        return d.split('=').pop();
    });
}

function* iterator(arr) {
    for(var i = 0; i < arr.length; i++) {
        yield arr[i];
    }
}

function onAllDataLoaded() {

    onDataLoaded(globalData.totals);
    makeCategorisedReport(globalData);
    makeMostPopularReport(globalData);
    makePermissionsReport(globalData.totals);

}

function onDataLoaded(data) {

    var totals = document.getElementById('totals');
    totals.innerHTML = '
' + data.length + ' apps
';

    makeGlobalReport('out', data.slice(0));
    makeAirReport('air', data.slice(0));

}

function makeGlobalReport(tableId, data) {

    data.sort(function(a, b) {
        return b.traits.html5.total - a.traits.html5.total;
    });

    var div = document.getElementById(tableId);
    var out = document.createElement('table');

    var groups = {
        yes: 0,
        maybe: 0,
        unlikely: 0,
        no: 0
    };

    data.forEach(function(app, appIndex) {
        var traits = app.traits.html5.traits;
        traits.sort(makeSorter('amount'));

        // Title
        var trTitle = out.insertRow(-1);
        trTitle.className = 'title';

        var tdCounter = trTitle.insertCell(-1);
        tdCounter.innerHTML = (appIndex + 1);

        // Build up the title:
        var title = app.info !== undefined ? app.info.title : app.name;

        var tdTotal = trTitle.insertCell(-1);
        tdTotal.innerHTML = app.traits.html5.total;

        var tdName = trTitle.insertCell(-1);
        tdName.innerHTML = '
' + title + '
';

        // Details

        var trDetails = out.insertRow(-1);
        trDetails.className = 'details';

        trDetails.insertCell(-1); // empty

        var tdDetails = trDetails.insertCell(-1);
        tdDetails.colSpan = 3;

        tdDetails.innerHTML = '
Play Store: ' + app.name + '
';

        var traitsTable = document.createElement('table');
        traitsTable.className = 'traits';

        traits.forEach(function(trait) {

            var traitRow = traitsTable.insertRow(-1);
            var tdAmount = traitRow.insertCell(-1);
            var tdDesc = traitRow.insertCell(-1);

            tdAmount.innerHTML = trait.amount.toFixed(2);
            tdDesc.innerHTML = trait.reason;

            var hover = trait.files.join('\n');

            traitRow.title = hover;

        });

        tdDetails.appendChild(traitsTable);

        var total = app.traits.html5.total;

        var className = isHTML5(total);

        groups[className]++;

        trTitle.classList.add(className);
        trDetails.classList.add(className);

    });

    // summary - chart with % of html5 likeability
    appendPieChart('#' + div.id, groups);

    div.appendChild(out);

    $('#' + tableId + ' tr.title').on('click', function() {
        $(this).next().toggle();
        this.classList.toggle('open');
    });

}

function makePermissionsReport(data) {
    var permissions = {};

    data.forEach(function(app) {
        if(app.permissions) {
            app.permissions.forEach(function(perm) {
                if(permissions[perm]) {
                    permissions[perm]++;
                } else {
                    permissions[perm] = 1;
                }
            });
        }
    });

    console.log(permissions);
}

function findApp(array, pkgName) {
    for(var i = 0; i < array.length; i++) {
        var app = array[i];
        if(app.name === pkgName) {
            return app;
        }
    }

    return null;
}

function isHTML5(total) {

    var className = 'no';

    if(total > 50) {
        className = 'yes';
    } else if(total > 30) {
        className = 'maybe';
    } else if(total > 15) {
        className = 'unlikely';
    }

    return className;
}

function isAir(appInfo) {
    var airTraits = appInfo.traits.html5.traits.filter(function(trait) {
        if(trait.reason === 'air') {
            return trait;
        }
    });
    return airTraits.length > 0;
}

function makeAirReport(containerId, data) {
    var airApps = data.filter(isAir);

    var container = document.getElementById(containerId);
    var percentage = (airApps.length * 100.0 / data.length).toFixed(2);

    container.innerHTML = '
' + percentage + '% of ' + data.length + ' apps
';

    var list = document.createElement('ol');
    container.appendChild(list);

    airApps.sort(function(a, b) {
        if(!(a.info && a.info.title) || !(b.info && b.info.title)) {
            return 0;
        }
        return a.info.title.toLowerCase() > b.info.title.toLowerCase();
    });

    airApps.forEach(function(app) {
        var li = document.createElement('li');
        li.innerHTML = app.info.title + ' (play store)';
        list.appendChild(li);
    });
}

function makeMostPopularReport(data) {

    var outElement = document.getElementById('popular');
    var overview = document.createElement('div');
    var table = document.createElement('table');
    var popular = data.mostPopular;
    var numPopularApps = popular.length;
    var totalApps = data.totals;
    var aggregate = {};
    var airAmount = 0;

    outElement.appendChild(overview);
    outElement.appendChild(table);

    var trHead = table.insertRow(-1);
    trHead.innerHTML = '	html5?	air?	';

    popular.forEach(function(appUrl) {

        var pkgName = getAppPackageName(appUrl);
        var app = findApp(totalApps, pkgName);

        if(app !== null) {
            var tr = table.insertRow(-1);

            var tdTitle = tr.insertCell(-1);
            var appTitle = app.info !== undefined ? app.info.title : pkgName;
            tdTitle.innerHTML = '' + appTitle + '';

            var tdHTML5ness = tr.insertCell(-1);
            var html5ness = isHTML5(app.traits.html5.total);
            tdHTML5ness.innerHTML = html5ness;

            if(aggregate[html5ness] === undefined) {
                aggregate[html5ness] = 1;
            } else {
                aggregate[html5ness]++;
            }

            var airness = tr.insertCell(-1);
            var air = isAir(app);
            airness.innerHTML = air ? 'yes' : '';

            if(air) {
                airAmount++;
            }

        }

    });

    var txtOverview = '
' + numPopularApps + ' apps' + '
';

    for(var k in aggregate) {
        var value = aggregate[k];
        txtOverview += '
' + k + ' ' + (Math.round(value * 10000 / numPopularApps) / 100 ) + '% (' + value + ')
';
    }

    overview.innerHTML = txtOverview + '

air: ' + airAmount + '
';

}

function makeCategorisedReport(data) {

    var categories = data.categories;
    var totals = data.totals;
    var categoryBreakdown = document.getElementById('category-breakdown');
    var outputTable = document.createElement('table');
    var results = [];
    var keys = [ 'yes', 'maybe', 'unlikely', 'no' ];

    categoryBreakdown.appendChild(outputTable);

    for(var catName in categories) {

        var catData = categories[catName];
        var groups = {};
        var airness = 0;
        var yesApps = [];

        var appsInCategory = totals.filter(function(app) {
            var pkgName = app.name;
            return catData.indexOf(pkgName) !== -1;
        });

        var numApps = appsInCategory.length;

        // Count how many apps fall in each 'html5 likeability' group
        appsInCategory.forEach(function(app) {

            var total = app.total;
            var html5ness = isHTML5(total);

            if(groups[html5ness] === undefined) {
                groups[html5ness] = 1;
            } else {
                groups[html5ness]++;
            }

            if(isAir(app)) {
                airness++;
            }

            if(html5ness === 'yes') {
                yesApps.push(app);
            }

        });

        var div = document.createElement('div');
        div.id = 'categoryBreakdown-' + catName;

        categoryBreakdown.appendChild(div);

        var h3 = document.createElement('h3');
        h3.innerHTML = catName + ' ' + numApps;
        div.appendChild(h3);

        appendPieChart('#' + div.id, groups);

        var numYes = groups.yes !== undefined ? groups.yes : 0;
        var yesPerc = Math.round(numYes * 10000.0 / appsInCategory.length) / 100;
        var airPerc = Math.round(airness * 10000.0 / appsInCategory.length) / 100;

        results.push({ name: catName, groups: groups, yesPerc: yesPerc, air: airness, airPerc: airPerc, count: appsInCategory.length, yesPackages: yesApps.map(function(app) {
            return app.name;
        }).join('
') });

    };

    results.sort(function(a, b) {

        var yesA = a.yesPerc;
        var yesB = b.yesPerc;

        return yesB - yesA;

    });

    var tr = outputTable.insertRow(-1);
    keys.forEach(function(k) {
        var cell = tr.insertCell(-1);
        cell.innerHTML = k;
    });

    tr.insertCell(-1).innerHTML = 'yes %';
    tr.insertCell(-1).innerHTML = '# apps';
    tr.insertCell(-1).innerHTML = '# air';
    tr.insertCell(-1).innerHTML = '% air';

    results.forEach(function(r) {

        var row = outputTable.insertRow(-1);

        keys.forEach(function(k) {
            var cell = row.insertCell(-1);
            var value = r.groups[k];

            if(value === undefined) {
                value = 0;
            }

            cell.innerHTML = value;
        });

        row.insertCell(-1).innerHTML = r.yesPerc + '%';
        row.insertCell(-1).innerHTML = r.count;

        row.insertCell(-1).innerHTML = r.air;
        row.insertCell(-1).innerHTML = r.airPerc + '%';

        row.insertCell(-1).innerHTML = r.name;

        row.insertCell(-1).innerHTML = '
' + r.yesPackages + '
';

    });
}

function getAppURL(packageName) {
    return 'https://play.google.com/store/apps/details?id=' + packageName;
}

function getAppPackageName(url) {
    return url.split('=').pop();
}

function makeSorter(propertyName) {
    return function(a, b) {
        var propA = Number(a[propertyName]);
        var propB = Number(b[propertyName]);

        return propA - propB;
    };
}

function appendPieChart(selector, values) {

    var data = [];
    var totals = 0;
    for(var k in values) {
        data.push({ label: k, value: values[k] });
        totals += values[k];
    }

    var width = 300;
    var height = 300;

    var radius = Math.min(width, height) / 2;
    var percGraph = 0.5;
    var graphRadius = radius * percGraph;
    var w = width;
    var h = height;

    var color = {
        'yes': '#00aa00',
        'maybe': '#eeff00',
        'unlikely': '#dd9900',
        'no': '#ff0000'
    };

    var vis = d3.select(selector)
        .append("svg:svg")
            .data([data])
            .attr("width", w)
            .attr("height", h)
            .append("svg:g")
                .attr("transform", "translate(" + radius + "," + radius + ")");

    var arc = d3.svg.arc()
        .outerRadius(graphRadius);

    var pie = d3.layout.pie()
        .value(function(d) { return d.value; });

    var arcs = vis.selectAll('g.slice')
        .data(pie)
        .enter()
        .append('svg:g')
        .attr('class', 'slice');

    arcs.append('svg:path')
        .attr('fill', function(d, i) { var label = data[i].label; return color[label]; } )
        .attr('d', arc);

    arcs.append('svg:text')
        .attr('dy', '5px')
        .attr('transform', function(d) {
            var alpha = (d.endAngle - d.startAngle) * 0.5 + d.startAngle;
            var dist = radius * (percGraph + 0.05);
            return 'translate(' + ( dist * Math.sin( alpha ) ) + ',' + ( - dist * Math.cos( alpha ) ) + ') rotate(' + angle(d) + ')';
        })
        .style('text-anchor', function(d) {
            var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle;
            if(rads >= 0 && rads <= Math.PI) {
                return 'start';
            } else {
                return 'end';
            }
        })
        .text(function(d, i) { return (Math.round(data[i].value * 10000.0 / totals)/100) + '% ' + data[i].label; });

    function angle(d) {
        var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
        return a > 90 ? a - 180 : a;
    }

}
<commit_msg>refactor getting title<commit_after>var globalData = {
    totals: [],
    categories: {}
};

window.onload = function() {
    loadJSONFiles([
        [ 'appsURLs.json', onMostPopularLoaded ],
        [ 'totals.json', onTotalsLoaded ],
        [ 'categories.json', onCategoriesLoaded ]
    ]);
};

function loadJSONFiles(filesList, doneCallback) {

    doneCallback = doneCallback || function() {};

    var filesIter = iterator(filesList);
    var loadNext = function() {

        var nextValue = filesIter.next();

        if(nextValue.done) {
            doneCallback();
        } else {
            var tuple = nextValue.value;
            var request = new XMLHttpRequest();
            var jsonURL = tuple[0];
            var jsonCallback = tuple[1];

            request.open('GET', jsonURL);
            request.responseType = 'text';
            request.onload = function() {

                console.log('loaded', jsonURL);

                var data = JSON.parse(request.response);

                if(jsonCallback) {
                    jsonCallback(data);
                }

                setTimeout(loadNext, 1);
            };

            request.send();
        }
    };

    loadNext();

}

function onMostPopularLoaded(data) {
    console.log('most popular loaded', data.length);
    globalData.mostPopular = data;
}

function onTotalsLoaded(data) {
    console.log('totals loaded', data.length);
    globalData.totals = data;
}

function onCategoriesLoaded(data) {
    console.log('cats loaded', data.length);
    var catJSONs = data.map(function(d) {
        var name = d.split('/').pop();
        return [ 'category-' + name + '.json', function(catData) {
            onCategoryLoaded(name, catData);
        } ];
    });
    console.log(catJSONs);

    globalData.categories = {};

    loadJSONFiles(catJSONs, onAllDataLoaded);

}

function onCategoryLoaded(categoryName, data) {
    console.log('loaded category', categoryName);
    globalData.categories[categoryName] = data.map(function(d) {
        return d.split('=').pop();
    });
}

function* iterator(arr) {
    for(var i = 0; i < arr.length; i++) {
        yield arr[i];
    }
}

function onAllDataLoaded() {

    onDataLoaded(globalData.totals);
    makeCategorisedReport(globalData);
    makeMostPopularReport(globalData);
    makePermissionsReport(globalData.totals);

}

function onDataLoaded(data) {

    var totals = document.getElementById('totals');
    totals.innerHTML = '
' + data.length + ' apps
';

    makeGlobalReport('out', data.slice(0));
    makeAirReport('air', data.slice(0));

}

function makeGlobalReport(tableId, data) {

    data.sort(function(a, b) {
        return b.traits.html5.total - a.traits.html5.total;
    });

    var div = document.getElementById(tableId);
    var out = document.createElement('table');

    var groups = {
        yes: 0,
        maybe: 0,
        unlikely: 0,
        no: 0
    };

    data.forEach(function(app, appIndex) {
        var traits = app.traits.html5.traits;
        traits.sort(makeSorter('amount'));

        // Title
        var trTitle = out.insertRow(-1);
        trTitle.className = 'title';

        var tdCounter = trTitle.insertCell(-1);
        tdCounter.innerHTML = (appIndex + 1);

        // Build up the title:
        var title = app.info !== undefined ? app.info.title : app.name;

        var tdTotal = trTitle.insertCell(-1);
        tdTotal.innerHTML = app.traits.html5.total;

        var tdName = trTitle.insertCell(-1);
        tdName.innerHTML = '
' + title + '
';

        // Details

        var trDetails = out.insertRow(-1);
        trDetails.className = 'details';

        trDetails.insertCell(-1); // empty

        var tdDetails = trDetails.insertCell(-1);
        tdDetails.colSpan = 3;

        tdDetails.innerHTML = '
Play Store: ' + app.name + '
';

        var traitsTable = document.createElement('table');
        traitsTable.className = 'traits';

        traits.forEach(function(trait) {

            var traitRow = traitsTable.insertRow(-1);
            var tdAmount = traitRow.insertCell(-1);
            var tdDesc = traitRow.insertCell(-1);

            tdAmount.innerHTML = trait.amount.toFixed(2);
            tdDesc.innerHTML = trait.reason;

            var hover = trait.files.join('\n');

            traitRow.title = hover;

        });

        tdDetails.appendChild(traitsTable);

        var total = app.traits.html5.total;

        var className = isHTML5(total);

        groups[className]++;

        trTitle.classList.add(className);
        trDetails.classList.add(className);

    });

    // summary - chart with % of html5 likeability
    appendPieChart('#' + div.id, groups);

    div.appendChild(out);

    $('#' + tableId + ' tr.title').on('click', function() {
        $(this).next().toggle();
        this.classList.toggle('open');
    });

}

function makePermissionsReport(data) {
    var permissions = {};

    data.forEach(function(app) {
        if(app.permissions) {
            app.permissions.forEach(function(perm) {
                if(permissions[perm]) {
                    permissions[perm]++;
                } else {
                    permissions[perm] = 1;
                }
            });
        }
    });

    console.log(permissions);
}

function findApp(array, pkgName) {
    for(var i = 0; i < array.length; i++) {
        var app = array[i];
        if(app.name === pkgName) {
            return app;
        }
    }

    return null;
}

function isHTML5(total) {

    var className = 'no';

    if(total > 50) {
        className = 'yes';
    } else if(total > 30) {
        className = 'maybe';
    } else if(total > 15) {
        className = 'unlikely';
    }

    return className;
}

function isAir(appInfo) {
    var airTraits = appInfo.traits.html5.traits.filter(function(trait) {
        if(trait.reason === 'air') {
            return trait;
        }
    });
    return airTraits.length > 0;
}

function getAppTitle(app) {
    return(app.info && app.info.title ? app.info.title : app.name);
}

function makeAirReport(containerId, data) {
    var airApps = data.filter(isAir);

    var container = document.getElementById(containerId);
    var percentage = (airApps.length * 100.0 / data.length).toFixed(2);

    container.innerHTML = '
' + percentage + '% of ' + data.length + ' apps
';

    var list = document.createElement('ol');
    container.appendChild(list);

    airApps.sort(function(a, b) {

        var titleA = getAppTitle(a);
        var titleB = getAppTitle(b);

        return titleA.toLowerCase() > titleB.toLowerCase();

    });

    airApps.forEach(function(app) {
        var li = document.createElement('li');
        li.innerHTML = getAppTitle(app) + ' (play store)';
        list.appendChild(li);
    });
}

function makeMostPopularReport(data) {

    var outElement = document.getElementById('popular');
    var overview = document.createElement('div');
    var table = document.createElement('table');
    var popular = data.mostPopular;
    var numPopularApps = popular.length;
    var totalApps = data.totals;
    var aggregate = {};
    var airAmount = 0;

    outElement.appendChild(overview);
    outElement.appendChild(table);

    var trHead = table.insertRow(-1);
    trHead.innerHTML = '	html5?	air?	';

    popular.forEach(function(appUrl) {

        var pkgName = getAppPackageName(appUrl);
        var app = findApp(totalApps, pkgName);

        if(app !== null) {
            var tr = table.insertRow(-1);

            var tdTitle = tr.insertCell(-1);
            var appTitle = app.info !== undefined ? app.info.title : pkgName;
            tdTitle.innerHTML = '' + appTitle + '';

            var tdHTML5ness = tr.insertCell(-1);
            var html5ness = isHTML5(app.traits.html5.total);
            tdHTML5ness.innerHTML = html5ness;

            if(aggregate[html5ness] === undefined) {
                aggregate[html5ness] = 1;
            } else {
                aggregate[html5ness]++;
            }

            var airness = tr.insertCell(-1);
            var air = isAir(app);
            airness.innerHTML = air ? 'yes' : '';

            if(air) {
                airAmount++;
            }

        }

    });

    var txtOverview = '
' + numPopularApps + ' apps' + '
';

    for(var k in aggregate) {
        var value = aggregate[k];
        txtOverview += '
' + k + ' ' + (Math.round(value * 10000 / numPopularApps) / 100 ) + '% (' + value + ')
';
    }

    overview.innerHTML = txtOverview + '

air: ' + airAmount + '
';

}

function makeCategorisedReport(data) {

    var categories = data.categories;
    var totals = data.totals;
    var categoryBreakdown = document.getElementById('category-breakdown');
    var outputTable = document.createElement('table');
    var results = [];
    var keys = [ 'yes', 'maybe', 'unlikely', 'no' ];

    categoryBreakdown.appendChild(outputTable);

    for(var catName in categories) {

        var catData = categories[catName];
        var groups = {};
        var airness = 0;
        var yesApps = [];

        var appsInCategory = totals.filter(function(app) {
            var pkgName = app.name;
            return catData.indexOf(pkgName) !== -1;
        });

        var numApps = appsInCategory.length;

        // Count how many apps fall in each 'html5 likeability' group
        appsInCategory.forEach(function(app) {

            var total = app.total;
            var html5ness = isHTML5(total);

            if(groups[html5ness] === undefined) {
                groups[html5ness] = 1;
            } else {
                groups[html5ness]++;
            }

            if(isAir(app)) {
                airness++;
            }

            if(html5ness === 'yes') {
                yesApps.push(app);
            }

        });

        var div = document.createElement('div');
        div.id = 'categoryBreakdown-' + catName;

        categoryBreakdown.appendChild(div);

        var h3 = document.createElement('h3');
        h3.innerHTML = catName + ' ' + numApps;
        div.appendChild(h3);

        appendPieChart('#' + div.id, groups);

        var numYes = groups.yes !== undefined ? groups.yes : 0;
        var yesPerc = Math.round(numYes * 10000.0 / appsInCategory.length) / 100;
        var airPerc = Math.round(airness * 10000.0 / appsInCategory.length) / 100;

        results.push({ name: catName, groups: groups, yesPerc: yesPerc, air: airness, airPerc: airPerc, count: appsInCategory.length, yesPackages: yesApps.map(function(app) {
            return app.name;
        }).join('
') });

    };

    results.sort(function(a, b) {

        var yesA = a.yesPerc;
        var yesB = b.yesPerc;

        return yesB - yesA;

    });

    var tr = outputTable.insertRow(-1);
    keys.forEach(function(k) {
        var cell = tr.insertCell(-1);
        cell.innerHTML = k;
    });

    tr.insertCell(-1).innerHTML = 'yes %';
    tr.insertCell(-1).innerHTML = '# apps';
    tr.insertCell(-1).innerHTML = '# air';
    tr.insertCell(-1).innerHTML = '% air';

    results.forEach(function(r) {

        var row = outputTable.insertRow(-1);

        keys.forEach(function(k) {
            var cell = row.insertCell(-1);
            var value = r.groups[k];

            if(value === undefined) {
                value = 0;
            }

            cell.innerHTML = value;
        });

        row.insertCell(-1).innerHTML = r.yesPerc + '%';
        row.insertCell(-1).innerHTML = r.count;

        row.insertCell(-1).innerHTML = r.air;
        row.insertCell(-1).innerHTML = r.airPerc + '%';

        row.insertCell(-1).innerHTML = r.name;

        row.insertCell(-1).innerHTML = '
' + r.yesPackages + '
';

    });
}

function getAppURL(packageName) {
    return 'https://play.google.com/store/apps/details?id=' + packageName;
}

function getAppPackageName(url) {
    return url.split('=').pop();
}

function makeSorter(propertyName) {
    return function(a, b) {
        var propA = Number(a[propertyName]);
        var propB = Number(b[propertyName]);

        return propA - propB;
    };
}

function appendPieChart(selector, values) {

    var data = [];
    var totals = 0;
    for(var k in values) {
        data.push({ label: k, value: values[k] });
        totals += values[k];
    }

    var width = 300;
    var height = 300;

    var radius = Math.min(width, height) / 2;
    var percGraph = 0.5;
    var graphRadius = radius * percGraph;
    var w = width;
    var h = height;

    var color = {
        'yes': '#00aa00',
        'maybe': '#eeff00',
        'unlikely': '#dd9900',
        'no': '#ff0000'
    };

    var vis = d3.select(selector)
        .append("svg:svg")
            .data([data])
            .attr("width", w)
            .attr("height", h)
            .append("svg:g")
                .attr("transform", "translate(" + radius + "," + radius + ")");

    var arc = d3.svg.arc()
        .outerRadius(graphRadius);

    var pie = d3.layout.pie()
        .value(function(d) { return d.value; });

    var arcs = vis.selectAll('g.slice')
        .data(pie)
        .enter()
        .append('svg:g')
        .attr('class', 'slice');

    arcs.append('svg:path')
        .attr('fill', function(d, i) { var label = data[i].label; return color[label]; } )
        .attr('d', arc);

    arcs.append('svg:text')
        .attr('dy', '5px')
        .attr('transform', function(d) {
            var alpha = (d.endAngle - d.startAngle) * 0.5 + d.startAngle;
            var dist = radius * (percGraph + 0.05);
            return 'translate(' + ( dist * Math.sin( alpha ) ) + ',' + ( - dist * Math.cos( alpha ) ) + ') rotate(' + angle(d) + ')';
        })
        .style('text-anchor', function(d) {
            var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle;
            if(rads >= 0 && rads <= Math.PI) {
                return 'start';
            } else {
                return 'end';
            }
        })
        .text(function(d, i) { return (Math.round(data[i].value * 10000.0 / totals)/100) + '% ' + data[i].label; });

    function angle(d) {
        var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
        return a > 90 ? a - 180 : a;
    }

}
<|endoftext|>


<commit_before>/* globals Terraformer, L */

L.esri.FeatureLayer = L.GeoJSON.extend({
  initialize: function(url, options){
    this.index = new Terraformer.RTree();
    //add a trailing slash to the url if the user omitted it
    if(url[url.length-1] !== "/"){
      url += "/";
    }
    this.url = url;

    L.GeoJSON.prototype.initialize.call(this, [], options);
  },
  onAdd: function(map){
    L.LayerGroup.prototype.onAdd.call(this, map);
    this.updateFeatures(map);
  },
  onRemove: function(map){
    L.LayerGroup.prototype.onRemove.call(this, map);
    map.off("viewreset moveend", L.Util.bind(this.updateFeatures, this));
  },
  updateFeatures: function(map){
    var draw = L.Util.bind(function(){
      var newBounds = map.getBounds();
      var envelope = L.esri.Util.boundsToEnvelope(newBounds);

      this.index.search(envelope).then(L.Util.bind(function(results){
        this.eachLayer(L.Util.bind(function(layer){
          var id = layer.feature.id;
          var layerid;
          this._toggleLayerVisibility(layer, L.esri.Util.indexOf(results, id) === -1);
        }, this));
      }, this));

      L.esri.get(this.url+"query", {
        geometryType: "esriGeometryEnvelope",
        geometry: JSON.stringify(L.esri.Util.boundsToExtent(newBounds)),
        outFields:"*",
        outSr: 4326
      }, L.Util.bind(function(response){
        if(response.objectIdFieldName && response.features.length && !response.error){
          var idKey = response.objectIdFieldName;
          for (var i = response.features.length - 1; i >= 0; i--) {
            var feature = response.features[i];
            var id = feature.attributes[idKey];
            if(!this._layers[id]){
              var geojson = Terraformer.ArcGIS.parse(feature);
              geojson.id = id;
              this.index.insert(geojson,id);
              this.addData(geojson);
            }
          }
        }
      }, this));
    },this);

    var tryDraw = L.Util.bind(function(){
      clearTimeout(this._delay);
      this._delay = setTimeout(L.Util.bind(function(){
        draw();
      },this), 250);
    },this);

    map.on("viewreset moveend", tryDraw);

    draw();
  },
  getLayerId: function(layer){
    return layer.feature.id;
  },
  _toggleLayerVisibility: function(layer, hide){
    var style = (hide) ? "none" : "block";
    // icon
    if(layer._icon){
      layer._icon.style.display = style;
    }

    // shadow
    if(layer._shadow){
      layer._shadow.style.display = style;
    }

    // misc layers
    if(layer._layers){
      for(var layerid in layer._layers){
        if(layer._layers.hasOwnProperty(layerid)){
          layer._layers[layerid]._container.style.display = style;
        }
      }
    }

  }
});

L.esri.featureLayer = function(url, options){
  return new L.esri.FeatureLayer(url, options);
};<commit_msg>start<commit_after>/* globals Terraformer, L */

L.esri.FeatureLayer = L.GeoJSON.extend({
  initialize: function(url, options){
    this.index = new Terraformer.RTree();
    //add a trailing slash to the url if the user omitted it
    if(url[url.length-1] !== "/"){
      url += "/";
    }
    this.url = url;

    L.GeoJSON.prototype.initialize.call(this, [], options);
  },
  onAdd: function(map){
    L.LayerGroup.prototype.onAdd.call(this, map);
    this.updateFeatures(map);
    this._update(map);
  },
  onRemove: function(map){
    L.LayerGroup.prototype.onRemove.call(this, map);
    map.off("viewreset moveend", L.Util.bind(this.updateFeatures, this));
  },
  updateFeatures: function(map){
    // var draw = L.Util.bind(function(){
    //   var newBounds = map.getBounds();
    //   var envelope = L.esri.Util.boundsToEnvelope(newBounds);

    //   this.index.search(envelope).then(L.Util.bind(function(results){
    //     this.eachLayer(L.Util.bind(function(layer){
    //       var id = layer.feature.id;
    //       var layerid;
    //       this._toggleLayerVisibility(layer, L.esri.Util.indexOf(results, id) === -1);
    //     }, this));
    //   }, this));

    //   L.esri.get(this.url+"query", {
    //     geometryType: "esriGeometryEnvelope",
    //     geometry: JSON.stringify(L.esri.Util.boundsToExtent(newBounds)),
    //     outFields:"*",
    //     outSr: 4326
    //   }, L.Util.bind(function(response){
    //     if(response.objectIdFieldName && response.features.length && !response.error){
    //       var idKey = response.objectIdFieldName;
    //       for (var i = response.features.length - 1; i >= 0; i--) {
    //         var feature = response.features[i];
    //         var id = feature.attributes[idKey];
    //         if(!this._layers[id]){
    //           var geojson = Terraformer.ArcGIS.parse(feature);
    //           geojson.id = id;
    //           this.index.insert(geojson,id);
    //           this.addData(geojson);
    //         }
    //       }
    //     }
    //   }, this));
    // },this);

    // var tryDraw = L.Util.bind(function(){
    //   clearTimeout(this._delay);
    //   this._delay = setTimeout(L.Util.bind(function(){
    //     draw();
    //   },this), 250);
    // },this);

    // map.on("viewreset moveend", tryDraw);

    // draw();
  },
  getLayerId: function(layer){
    return layer.feature.id;
  },
  _toggleLayerVisibility: function(layer, hide){
    var style = (hide) ? "none" : "block";
    // icon
    if(layer._icon){
      layer._icon.style.display = style;
    }

    // shadow
    if(layer._shadow){
      layer._shadow.style.display = style;
    }

    // misc layers
    if(layer._layers){
      for(var layerid in layer._layers){
        if(layer._layers.hasOwnProperty(layerid)){
          layer._layers[layerid]._container.style.display = style;
        }
      }
    }

  },
  _setupGrid: function(map){
    // this._origin = map.project(map.getBounds().getNorthWest());
    // this._cellSize = 256;
    // this._mapWidth = map.getSize().x;
    // this._mapHeight = map.getSize().y;
    // var rows = Math.ceil(this._mapWidth / this._cellSize)+1;
    // var cols = Math.ceil(this._mapHeight / this._cellSize)+1;
    // var cells = [];
    // for (var i = 1; i <= rows; i++) {
    //   for (var j = 1; j <= cols; j++) {
    //     var one = map.unproject(new L.Point(this._origin.x + ((i-1)*this._cellSize), this._origin.y + ((j-1)*this._cellSize)));
    //     var two = map.unproject(new L.Point(this._origin.x + (i*this._cellSize), this._origin.y + (j*this._cellSize)));
    //     L.rectangle(new L.LatLngBounds(one,two), {color: "#ff7800", weight: 2}).addTo(map);
    //   }
    // }
  },
  _update: function (map) {
    var bounds = map.getPixelBounds();
    var tileSize = 256;
    var tileBounds = L.bounds(
            bounds.min.divideBy(tileSize)._floor(),
            bounds.max.divideBy(tileSize)._floor());

    this._addTilesFromCenterOut(tileBounds);
  },
  _addTilesFromCenterOut: function (bounds) {
    var center = bounds.getCenter();
    var j, i, point;

    for (j = bounds.min.y; j <= bounds.max.y; j++) {
      for (i = bounds.min.x; i <= bounds.max.x; i++) {
        point = new L.Point(i, j);
        console.log(point);
      }
    }

    var tilesToLoad = queue.length;

    if (tilesToLoad === 0) { return; }

    // load tiles in order of their distance to center
    queue.sort(function (a, b) {
      return a.distanceTo(center) - b.distanceTo(center);
    });

    var fragment = document.createDocumentFragment();

    // if its the first batch of tiles to load
    if (!this._tilesToLoad) {
      this.fire('loading');
    }

    this._tilesToLoad += tilesToLoad;

    for (i = 0; i < tilesToLoad; i++) {
      this._addTile(queue[i], fragment);
    }

    this._tileContainer.appendChild(fragment);
  },
});

L.esri.featureLayer = function(url, options){
  return new L.esri.FeatureLayer(url, options);
};<|endoftext|>


<commit_before>import React from 'react'
import { Button, Divider, List, Segment } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import UserLink from '../../utils/link/user'
import LoginModal from './login/modal'

class LoginButton extends React.Component {
  render() {
    return (
      <LoginModal {... this.props} />
    )
  }
}

class LogoutButton extends React.Component {
  logout = (e) => {
    this.props.actions.signoutAccount()
  }
  render() {
    return (
      <Button color='orange' size='mini' onClick={this.logout}>Sign-out</Button>
    )
  }
}

const Login = (props) => {

  let account = props.account
  let display = (

      <Segment secondary attached='top'>

Greetings Steemian!

      </Segment>
      <Segment attached>

If you have an account on the Steem blockchain, hit the login button below.

        <LoginButton {... props} />
        {/*
        <Button fluid disabled>Currently Disabled</Button>
        */}
      </Segment>

  )

  if(account.isUser) {
    display = (

        <Segment secondary attached='top'>

            Welcome
            {' '}
            <UserLink username={account.name} />!

        </Segment>
        <Segment attached>

Account details and management can be found off-site using one of the following websites.

          <List>
            <List.Item>
              <List.Icon name='external'/>
              <List.Content>
                <List.Header>steemit.com</List.Header>
                <List bulleted>
                  <List.Item>
                    <Link to={`https://steemit.com/@${account.name}`} rel='nofollow' target='_blank'>
                      Manage Account
                    </Link>
                  </List.Item>
                  <List.Item>
                    <Link to={`https://steemit.com/@${account.name}/settings`} rel='nofollow' target='_blank'>
                      Edit Profile
                    </Link>
                  </List.Item>
                  <List.Item>
                    <Link to={`https://steemit.com/@${account.name}/transfers`} rel='nofollow' target='_blank'>
                      Wallet
                    </Link>
                  </List.Item>
                </List>
              </List.Content>
            </List.Item>
            <List.Item>
              <List.Icon name='external'/>
              <List.Content>
                <List.Header>steemdb.com</List.Header>
                <List bulleted>
                  <List.Item>
                    <Link to={`https://steemdb.com/@${account.name}`} rel='nofollow' target='_blank'>
                      Recent History
                    </Link>
                  </List.Item>
                </List>
              </List.Content>
            </List.Item>
          </List>
          <Divider></Divider>
          <LogoutButton {... props} />
        </Segment>

    )
  }

  return display

}

export default Login;
<commit_msg>Simplified sidebar<commit_after>import React from 'react'
import { Button, Divider, Icon, List, Segment } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import UserLink from '../../utils/link/user'
import LoginModal from './login/modal'

class LoginButton extends React.Component {
  render() {
    return (
      <LoginModal {... this.props} />
    )
  }
}

class LogoutButton extends React.Component {
  logout = (e) => {
    this.props.actions.signoutAccount()
  }
  render() {
    return (
      <Button color='orange' inverted floated='right' size='mini' onClick={this.logout}>Sign-out</Button>
    )
  }
}

const Login = (props) => {

  let account = props.account
  let display = (

      <Segment secondary attached='top'>

Greetings Steemian!

      </Segment>
      <Segment attached>

If you have an account on the Steem blockchain, hit the login button below.

        <LoginButton {... props} />
        {/*
        <Button fluid disabled>Currently Disabled</Button>
        */}
      </Segment>

  )

  if(account.isUser) {
    display = (

        <Segment secondary attached='top'>

            Welcome
            {' '}
            <UserLink username={account.name} />!

        </Segment>
        <Segment attached>

Currently, account details and management can be found on steemit.com using the button below.

          <Divider></Divider>
          <Link to={`https://steemit.com/@${account.name}`} rel='nofollow' target='_blank' className='ui primary icon mini button'>
            <Icon name='external'></Icon>
            {' '}
            Manage Account
          </Link>
          <LogoutButton {... props} />
        </Segment>

    )
  }

  return display

}

export default Login;
<|endoftext|>


<commit_before>// Load in our dependencies
var fs = require('fs');
var $ = require('jquery');
var assert = require('assert');
var expect = require('chai').expect;
var sinonUtils = require('../utils/sinon');
// DEV: For unknown reasons, we must import `sinonUtils` before `applicationUtils`
var applicationUtils = require('./utils/application');
var mouseUtils = require('./utils/mouse');
var updateImageSetFilepathEqualResponse = fs.readFileSync(
  __dirname + '/../test-files/http-responses/update-image-set-filepath-equal.json', 'utf8');
var updateImageSetFilepathNotEqualResponse = fs.readFileSync(
  __dirname + '/../test-files/http-responses/update-image-set-filepath-not-equal.json', 'utf8');

// Define reused actions in tests
// TODO: Consolidate reused actions in tests (currently not sober enough to consolidate them wisely)
function overlayDiffImg(done) {
  // DEV: We use an expanded image set so we can click/drag
  var diffImg = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
  var diffImgBounds = diffImg.getBoundingClientRect();
  mouseUtils.moveMouse({
    targetEl: diffImg,
    startCoords: {x: diffImgBounds.left, y: diffImgBounds.top},
    endCoords: {x: diffImgBounds.left + 10, y: diffImgBounds.top + 10},
    duration: 100 // ms
  }, done);
}
function clickFindSimilarImages() {
  var buttonEl = this.containerEl.querySelector(
    '[data-image-set="mock-img-not-equal"] button[data-action="find-similar-images"]');
  assert(buttonEl);
  $(buttonEl).click();
}
function approveAllXHRUpdates() {
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 200,
    headers: {'Content-Type': 'application/json'},
    // jscs:disable maximumLineLength
    // Set via: echo -n '{"imagesEqual":false}' > test/test-files/http-responses/update-image-set-filepath-not-equal.json
    // jscs:enable maximumLineLength
    body: updateImageSetFilepathEqualResponse // {imagesEqual: false}
  }]);
}
function disapproveAllXHRUpdates() {
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 200,
    headers: {'Content-Type': 'application/json'},
    // jscs:disable maximumLineLength
    // Set via: echo -n '{"imagesEqual":false}' > test/test-files/http-responses/update-image-set-filepath-not-equal.json
    // jscs:enable maximumLineLength
    body: updateImageSetFilepathNotEqualResponse // {imagesEqual: false}
  }]);
}
function clickUpdateSimilarImages() {
  // Click our update button
  var buttonEl = this.containerEl.querySelector(
    '[data-image-set="mock-img-not-equal"] button[data-action="update-similar-images"]');
  assert(buttonEl);
  $(buttonEl).click();
}

// Start our tests
describe('An application with similarly failing images', function () {
  describe.only('when updating some similarly failing images partially', function () {
    applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_NOT_EQUAL);
    before(overlayDiffImg);
    before(clickFindSimilarImages);
    disapproveAllXHRUpdates();
    before(function deselectSimilarImageSets () {
      var currentSimilarImageSetEl = this.containerEl.querySelector('[data-similar-image-set="mock-img-not-equal"]');
      var saveUpdateEl = currentSimilarImageSetEl.querySelector('[name=save_update]');
      saveUpdateEl.checked = false;
    });
    before(clickUpdateSimilarImages);
    // DEV: This is effectively waiting for new image sources to update
    // TODO: Figure out how to detect when images have new source and it's not yet loaded
    before(function wait100Ms (done) {
      setTimeout(done, 100);
    });
    applicationUtils.screenshot('update-similar-images-partially');

    it('updates selected images partially in its image set', function () {
      // Verify image set status was updated-ish
      var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal2"] .image-set__title');
      expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');

      // Assert XHR sent
      var requests = this.sinonServer.requests;
      expect(requests).to.have.length(1);
      expect(requests[0].url).to.equal('/update-image-set/mock-img-not-equal2');
      // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
      expect(requests[0].requestBody).to.contain('ref=data');

      // Deep assert XHR content
      // DEV: It's somewhere in-between so we can't quite assert it
      var currentImgEl = this.containerEl.querySelector(
        '[data-image-set="mock-img-not-equal2"] img[data-compare-type=current]');
      var currentBase64 = applicationUtils.getBase64Content(currentImgEl);
      var refImgEl = this.containerEl.querySelector(
        '[data-image-set="mock-img-not-equal2"] img[data-compare-type=ref]');
      var refBase64 = applicationUtils.getBase64Content(refImgEl);
      expect(requests[0].requestBody).to.not.equal('ref=' + encodeURIComponent(currentBase64));
      expect(requests[0].requestBody).to.not.equal('ref=' + encodeURIComponent(refBase64));

      // DEV: We could assert cachebusted URLs but that is redundant at the moment
    });

    it('doesn\'t update unselected images', function () {
      // Verify image set status not updated
      var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
      expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');

      // DEV: XHR assertions are done in previous `it`
      //   Mostly via length check + url check
    });
  });

  describe('when updating some similarly failing images fully', function () {
    applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_NOT_EQUAL);
    before(function overlayDiffImg (done) {
      // DEV: We use an expanded image set so we can click/drag
      var diffImg = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
      var diffImgBounds = diffImg.getBoundingClientRect();
      mouseUtils.moveMouse({
        targetEl: diffImg,
        startCoords: {x: diffImgBounds.left, y: diffImgBounds.top},
        endCoords: {x: diffImgBounds.left + 15, y: diffImgBounds.top + 15},
        duration: 100 // ms
      }, done);
    });
    before(clickFindSimilarImages);
    approveAllXHRUpdates();
    before(clickUpdateSimilarImages);
    applicationUtils.screenshot('update-similar-images-fully');

    it.skip('collapses current image set', function () {
      // Placeholder content
    });
  });
});
<commit_msg>Noted possible resolution<commit_after>// Load in our dependencies
var fs = require('fs');
var $ = require('jquery');
var assert = require('assert');
var expect = require('chai').expect;
var sinonUtils = require('../utils/sinon');
// DEV: For unknown reasons, we must import `sinonUtils` before `applicationUtils`
var applicationUtils = require('./utils/application');
var mouseUtils = require('./utils/mouse');
var updateImageSetFilepathEqualResponse = fs.readFileSync(
  __dirname + '/../test-files/http-responses/update-image-set-filepath-equal.json', 'utf8');
var updateImageSetFilepathNotEqualResponse = fs.readFileSync(
  __dirname + '/../test-files/http-responses/update-image-set-filepath-not-equal.json', 'utf8');

// Define reused actions in tests
// TODO: Consolidate reused actions in tests (currently not sober enough to consolidate them wisely)
function overlayDiffImg(done) {
  // DEV: We use an expanded image set so we can click/drag
  var diffImg = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
  var diffImgBounds = diffImg.getBoundingClientRect();
  mouseUtils.moveMouse({
    targetEl: diffImg,
    startCoords: {x: diffImgBounds.left, y: diffImgBounds.top},
    endCoords: {x: diffImgBounds.left + 10, y: diffImgBounds.top + 10},
    duration: 100 // ms
  }, done);
}
function clickFindSimilarImages() {
  var buttonEl = this.containerEl.querySelector(
    '[data-image-set="mock-img-not-equal"] button[data-action="find-similar-images"]');
  assert(buttonEl);
  $(buttonEl).click();
}
function approveAllXHRUpdates() {
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 200,
    headers: {'Content-Type': 'application/json'},
    // jscs:disable maximumLineLength
    // Set via: echo -n '{"imagesEqual":false}' > test/test-files/http-responses/update-image-set-filepath-not-equal.json
    // jscs:enable maximumLineLength
    body: updateImageSetFilepathEqualResponse // {imagesEqual: false}
  }]);
}
function disapproveAllXHRUpdates() {
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 200,
    headers: {'Content-Type': 'application/json'},
    // jscs:disable maximumLineLength
    // Set via: echo -n '{"imagesEqual":false}' > test/test-files/http-responses/update-image-set-filepath-not-equal.json
    // jscs:enable maximumLineLength
    body: updateImageSetFilepathNotEqualResponse // {imagesEqual: false}
  }]);
}
function clickUpdateSimilarImages() {
  // Click our update button
  var buttonEl = this.containerEl.querySelector(
    '[data-image-set="mock-img-not-equal"] button[data-action="update-similar-images"]');
  assert(buttonEl);
  $(buttonEl).click();
}

// Start our tests
describe('An application with similarly failing images', function () {
  describe.only('when updating some similarly failing images partially', function () {
    applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_NOT_EQUAL);
    before(overlayDiffImg);
    before(clickFindSimilarImages);
    disapproveAllXHRUpdates();
    before(function deselectSimilarImageSets () {
      var currentSimilarImageSetEl = this.containerEl.querySelector('[data-similar-image-set="mock-img-not-equal"]');
      var saveUpdateEl = currentSimilarImageSetEl.querySelector('[name=save_update]');
      saveUpdateEl.checked = false;
    });
    before(clickUpdateSimilarImages);
    // DEV: This is effectively waiting for new image sources to update
    // TODO: Figure out how to detect when images have new source and it's not yet loaded
    //   Maybe a cache table and cache table resetter via `applicationUtils.init`?
    before(function wait100Ms (done) {
      setTimeout(done, 100);
    });
    applicationUtils.screenshot('update-similar-images-partially');

    it('updates selected images partially in its image set', function () {
      // Verify image set status was updated-ish
      var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal2"] .image-set__title');
      expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');

      // Assert XHR sent
      var requests = this.sinonServer.requests;
      expect(requests).to.have.length(1);
      expect(requests[0].url).to.equal('/update-image-set/mock-img-not-equal2');
      // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
      expect(requests[0].requestBody).to.contain('ref=data');

      // Deep assert XHR content
      // DEV: It's somewhere in-between so we can't quite assert it
      var currentImgEl = this.containerEl.querySelector(
        '[data-image-set="mock-img-not-equal2"] img[data-compare-type=current]');
      var currentBase64 = applicationUtils.getBase64Content(currentImgEl);
      var refImgEl = this.containerEl.querySelector(
        '[data-image-set="mock-img-not-equal2"] img[data-compare-type=ref]');
      var refBase64 = applicationUtils.getBase64Content(refImgEl);
      expect(requests[0].requestBody).to.not.equal('ref=' + encodeURIComponent(currentBase64));
      expect(requests[0].requestBody).to.not.equal('ref=' + encodeURIComponent(refBase64));

      // DEV: We could assert cachebusted URLs but that is redundant at the moment
    });

    it('doesn\'t update unselected images', function () {
      // Verify image set status not updated
      var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
      expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');

      // DEV: XHR assertions are done in previous `it`
      //   Mostly via length check + url check
    });
  });

  describe('when updating some similarly failing images fully', function () {
    applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_NOT_EQUAL);
    before(function overlayDiffImg (done) {
      // DEV: We use an expanded image set so we can click/drag
      var diffImg = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
      var diffImgBounds = diffImg.getBoundingClientRect();
      mouseUtils.moveMouse({
        targetEl: diffImg,
        startCoords: {x: diffImgBounds.left, y: diffImgBounds.top},
        endCoords: {x: diffImgBounds.left + 15, y: diffImgBounds.top + 15},
        duration: 100 // ms
      }, done);
    });
    before(clickFindSimilarImages);
    approveAllXHRUpdates();
    before(clickUpdateSimilarImages);
    applicationUtils.screenshot('update-similar-images-fully');

    it.skip('collapses current image set', function () {
      // Placeholder content
    });
  });
});
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react'
import SetPublicName, {type State} from '.'
import {connect, type TypedState} from '../../../util/container'
import * as Creators from '../../../actions/login/creators'
import {clearDeviceNameError} from '../../../actions/signup'
import {type RouteProps} from '../../../route-tree/render-route'

const trimDeviceName = (s: ?string): string => {
  if (!s) return ''
  return s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()
}

const trimDeviceNames = (names: ?Array<string>): Array<string> => {
  if (!names) return []
  return names.map(n => {
    return trimDeviceName(n)
  })
}

type ContainerProps = {
  onSubmit: (deviceName: ?string) => void,
  onBack: () => void,
  clearDeviceNameError: () => void,
  waiting: boolean,
  deviceNameError: ?string,
  existingDevices: ?Array<string>,
  existingDevicesTrimmed: ?Array<string>,
}

// TODO remove this class
class _SetPublicName extends Component<ContainerProps, State> {
  state: State

  constructor(props: ContainerProps) {
    super(props)

    this.state = {
      deviceName: null,
    }
  }

  _onChange = (deviceName: string) => {
    this.setState({
      deviceName,
    })
    if (this.props.deviceNameError) {
      this.props.clearDeviceNameError()
    }
  }

  render() {
    const deviceName = this.state.deviceName || ''
    const deviceNameTrimmed: string = trimDeviceName(this.state.deviceName)
    const nameTaken = !!(this.props.existingDevicesTrimmed &&
      this.props.existingDevicesTrimmed.indexOf(deviceNameTrimmed) !== -1)
    const submitEnabled = !!(deviceNameTrimmed.length >= 3 && deviceName.length <= 64 && !nameTaken)
    const nameTakenError = nameTaken
      ? `The device name: '${deviceName}' is already taken. You can't reuse device names, even revoked ones, for security reasons. Otherwise, someone who stole one of your devices could cause a lot of confusion.`
      : null

    return (
      <SetPublicName
        deviceName={this.state.deviceName}
        onChange={this._onChange}
        onSubmit={() => this.props.onSubmit(this.state.deviceName)}
        onBack={this.props.onBack}
        deviceNameError={nameTakenError || this.props.deviceNameError}
        existingDevices={this.props.existingDevices}
        submitEnabled={submitEnabled}
        waiting={this.props.waiting}
      />
    )
  }
}

type OwnProps = RouteProps<
  {
    existingDevices?: ?Array<string>,
  },
  {}
>

const mapStateToProps = (state: TypedState, {routeProps}: OwnProps) => ({
  deviceNameError: state.signup.deviceNameError,
  existingDevices: routeProps.get('existingDevices'),
  existingDevicesTrimmed: trimDeviceNames(routeProps.get('existingDevices')),
  waiting: state.engine.get('rpcWaitingStates').get('loginRpc'),
})

const mapDispatchToProps = (dispatch: Dispatch<*>) => ({
  clearDeviceNameError: () => dispatch(clearDeviceNameError()),
  onBack: () => dispatch(Creators.onBack()),
  onSubmit: deviceName => dispatch(Creators.submitDeviceName(deviceName)),
})

export default connect(mapStateToProps, mapDispatchToProps)(_SetPublicName)
<commit_msg>Map smart apostrophes to ASCII<commit_after>// @flow
import React, {Component} from 'react'
import SetPublicName, {type State} from '.'
import {connect, type TypedState} from '../../../util/container'
import * as Creators from '../../../actions/login/creators'
import {clearDeviceNameError} from '../../../actions/signup'
import {type RouteProps} from '../../../route-tree/render-route'

const trimDeviceName = (s: ?string): string => {
  if (!s) return ''
  return s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()
}

const trimDeviceNames = (names: ?Array<string>): Array<string> => {
  if (!names) return []
  return names.map(n => {
    return trimDeviceName(n)
  })
}

type ContainerProps = {
  onSubmit: (deviceName: ?string) => void,
  onBack: () => void,
  clearDeviceNameError: () => void,
  waiting: boolean,
  deviceNameError: ?string,
  existingDevices: ?Array<string>,
  existingDevicesTrimmed: ?Array<string>,
}

// TODO remove this class
class _SetPublicName extends Component<ContainerProps, State> {
  state: State

  constructor(props: ContainerProps) {
    super(props)

    this.state = {
      deviceName: null,
    }
  }

  _onChange = (deviceName: string) => {
    this.setState({
      deviceName,
    })
    if (this.props.deviceNameError) {
      this.props.clearDeviceNameError()
    }
  }

  render() {
    const deviceName = this.state.deviceName || ''
    const deviceNameTrimmed: string = trimDeviceName(this.state.deviceName)
    const nameTaken = !!(this.props.existingDevicesTrimmed &&
      this.props.existingDevicesTrimmed.indexOf(deviceNameTrimmed) !== -1)
    const submitEnabled = !!(deviceNameTrimmed.length >= 3 && deviceName.length <= 64 && !nameTaken)
    const nameTakenError = nameTaken
      ? `The device name: '${deviceName}' is already taken. You can't reuse device names, even revoked ones, for security reasons. Otherwise, someone who stole one of your devices could cause a lot of confusion.`
      : null

    return (
      <SetPublicName
        deviceName={this.state.deviceName}
        onChange={this._onChange}
        onSubmit={() => this.props.onSubmit(this.state.deviceName)}
        onBack={this.props.onBack}
        deviceNameError={nameTakenError || this.props.deviceNameError}
        existingDevices={this.props.existingDevices}
        submitEnabled={submitEnabled}
        waiting={this.props.waiting}
      />
    )
  }
}

type OwnProps = RouteProps<
  {
    existingDevices?: ?Array<string>,
  },
  {}
>

const mapStateToProps = (state: TypedState, {routeProps}: OwnProps) => ({
  deviceNameError: state.signup.deviceNameError,
  existingDevices: routeProps.get('existingDevices'),
  existingDevicesTrimmed: trimDeviceNames(routeProps.get('existingDevices')),
  waiting: state.engine.get('rpcWaitingStates').get('loginRpc'),
})

const mapDispatchToProps = (dispatch: Dispatch<*>) => ({
  clearDeviceNameError: () => dispatch(clearDeviceNameError()),
  onBack: () => dispatch(Creators.onBack()),
  onSubmit: deviceName => {
    // map 'smart apostrophes' to ASCII (typewriter apostrophe)
    deviceName = deviceName.replace(/[\u2018\u2019\u0060\u00B4]/g, "'")
    dispatch(Creators.submitDeviceName(deviceName))
  },
})

export default connect(mapStateToProps, mapDispatchToProps)(_SetPublicName)
<|endoftext|>


<commit_before>var Plotly = require('../src/plotly');

describe('Test axes', function () {
    'use strict';

    describe('swap', function() {
        it('should swap most attributes and fix placeholder titles', function() {
            var gd = {
                data: [{x: [1,2,3], y: [1,2,3]}],
                layout: {
                    xaxis: {
                        title: 'A Title!!!',
                        type: 'log',
                        autorange: 'reversed',
                        rangemode: 'tozero',
                        autotick: true,
                        nticks: 23,
                        ticks: 'outside',
                        mirror: 'ticks',
                        ticklen: 12,
                        tickwidth: 4,
                        tickcolor: '#f00'
                    },
                    yaxis: {
                        title: 'Click to enter Y axis title',
                        type: 'date'
                    }
                }
            };
            var expectedYaxis = $.extend(true, {}, gd.layout.xaxis),
                expectedXaxis = {
                    title: 'Click to enter X axis title',
                    type: 'date'
                };

            Plotly.Plots.supplyDefaults(gd);

            Plotly.Axes.swap(gd, [0]);

            expect(gd.layout.xaxis).toEqual(expectedXaxis);
            expect(gd.layout.yaxis).toEqual(expectedYaxis);
        });

        it('should not swap noSwapAttrs', function() {
            // for reference:
            // noSwapAttrs = ['anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle'];
            var gd = {
                data: [{x: [1,2,3], y: [1,2,3]}],
                layout: {
                    xaxis: {
                        anchor: 'free',
                        domain: [0, 1],
                        overlaying: false,
                        position: 0.2,
                        tickangle: 60
                    },
                    yaxis: {
                        anchor: 'x',
                        domain: [0.1, 0.9]
                    }
                }
            };
            var expectedLayoutAfter = $.extend(true, {}, gd.layout);
            expectedLayoutAfter.xaxis.type = 'linear';
            expectedLayoutAfter.yaxis.type = 'linear';

            Plotly.Plots.supplyDefaults(gd);

            Plotly.Axes.swap(gd, [0]);

            expect(gd.layout.xaxis).toEqual(expectedLayoutAfter.xaxis);
            expect(gd.layout.yaxis).toEqual(expectedLayoutAfter.yaxis);
        });

        it('should swap shared attributes, combine linear/log, and move annotations', function() {
            var gd = {
                data: [
                    {x: [1,2,3], y: [1,2,3]},
                    {x: [1,2,3], y: [1,2,3], xaxis: 'x2'}
                ],
                layout: {
                    xaxis: {
                        type: 'linear', // combine linear/log
                        ticks: 'outside', // same as x2
                        ticklen: 5, // default value
                        tickwidth: 2, // different
                        side: 'top', // noSwap
                        domain: [0, 0.45] // noSwap
                    },
                    xaxis2: {
                        type: 'log',
                        ticks: 'outside',
                        tickcolor: '#444', // default value in 2nd axis
                        tickwidth: 3,
                        side: 'top',
                        domain: [0.55, 1]
                    },
                    yaxis: {
                        type: 'category',
                        ticks: 'inside',
                        ticklen: 10,
                        tickcolor: '#f00',
                        tickwidth: 4,
                        showline: true, // not present in either x
                        side: 'right'
                    },
                    annotations: [
                        {x: 2, y: 3}, // xy referenced by default
                        {x: 3, y: 4, xref: 'x2', yref: 'y'},
                        {x: 5, y: 0.5, xref: 'x', yref: 'paper'} // any paper ref -> don't swap
                    ]
                }
            };
            var expectedXaxis = {
                    type: 'category',
                    ticks: 'inside',
                    ticklen: 10,
                    tickcolor: '#f00',
                    tickwidth: 2,
                    showline: true,
                    side: 'top',
                    domain: [0, 0.45]
                },
                expectedXaxis2 = {
                    type: 'category',
                    ticks: 'inside',
                    ticklen: 10,
                    tickcolor: '#f00',
                    tickwidth: 3,
                    showline: true,
                    side: 'top',
                    domain: [0.55, 1]
                },
                expectedYaxis = {
                    type: 'linear',
                    ticks: 'outside',
                    ticklen: 5,
                    tickwidth: 4,
                    side: 'right'
                },
                expectedAnnotations = [
                    {x: 3, y: 2},
                    {x: 4, y: 3, xref: 'x2', yref: 'y'},
                    {x: 5, y: 0.5, xref: 'x', yref: 'paper'}
                ];

            Plotly.Plots.supplyDefaults(gd);

            Plotly.Axes.swap(gd, [0, 1]);

            expect(gd.layout.xaxis).toEqual(expectedXaxis);
            expect(gd.layout.xaxis2).toEqual(expectedXaxis2);
            expect(gd.layout.yaxis).toEqual(expectedYaxis);
            expect(gd.layout.annotations).toEqual(expectedAnnotations);
        });
    });
});
<commit_msg>get rid of a few more references to autotick<commit_after>var Plotly = require('../src/plotly');

describe('Test axes', function () {
    'use strict';

    describe('swap', function() {
        it('should swap most attributes and fix placeholder titles', function() {
            var gd = {
                data: [{x: [1,2,3], y: [1,2,3]}],
                layout: {
                    xaxis: {
                        title: 'A Title!!!',
                        type: 'log',
                        autorange: 'reversed',
                        rangemode: 'tozero',
                        tickmode: 'auto',
                        nticks: 23,
                        ticks: 'outside',
                        mirror: 'ticks',
                        ticklen: 12,
                        tickwidth: 4,
                        tickcolor: '#f00'
                    },
                    yaxis: {
                        title: 'Click to enter Y axis title',
                        type: 'date'
                    }
                }
            };
            var expectedYaxis = $.extend(true, {}, gd.layout.xaxis),
                expectedXaxis = {
                    title: 'Click to enter X axis title',
                    type: 'date'
                };

            Plotly.Plots.supplyDefaults(gd);

            Plotly.Axes.swap(gd, [0]);

            expect(gd.layout.xaxis).toEqual(expectedXaxis);
            expect(gd.layout.yaxis).toEqual(expectedYaxis);
        });

        it('should not swap noSwapAttrs', function() {
            // for reference:
            // noSwapAttrs = ['anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle'];
            var gd = {
                data: [{x: [1,2,3], y: [1,2,3]}],
                layout: {
                    xaxis: {
                        anchor: 'free',
                        domain: [0, 1],
                        overlaying: false,
                        position: 0.2,
                        tickangle: 60
                    },
                    yaxis: {
                        anchor: 'x',
                        domain: [0.1, 0.9]
                    }
                }
            };
            var expectedLayoutAfter = $.extend(true, {}, gd.layout);
            expectedLayoutAfter.xaxis.type = 'linear';
            expectedLayoutAfter.yaxis.type = 'linear';

            Plotly.Plots.supplyDefaults(gd);

            Plotly.Axes.swap(gd, [0]);

            expect(gd.layout.xaxis).toEqual(expectedLayoutAfter.xaxis);
            expect(gd.layout.yaxis).toEqual(expectedLayoutAfter.yaxis);
        });

        it('should swap shared attributes, combine linear/log, and move annotations', function() {
            var gd = {
                data: [
                    {x: [1,2,3], y: [1,2,3]},
                    {x: [1,2,3], y: [1,2,3], xaxis: 'x2'}
                ],
                layout: {
                    xaxis: {
                        type: 'linear', // combine linear/log
                        ticks: 'outside', // same as x2
                        ticklen: 5, // default value
                        tickwidth: 2, // different
                        side: 'top', // noSwap
                        domain: [0, 0.45] // noSwap
                    },
                    xaxis2: {
                        type: 'log',
                        ticks: 'outside',
                        tickcolor: '#444', // default value in 2nd axis
                        tickwidth: 3,
                        side: 'top',
                        domain: [0.55, 1]
                    },
                    yaxis: {
                        type: 'category',
                        ticks: 'inside',
                        ticklen: 10,
                        tickcolor: '#f00',
                        tickwidth: 4,
                        showline: true, // not present in either x
                        side: 'right'
                    },
                    annotations: [
                        {x: 2, y: 3}, // xy referenced by default
                        {x: 3, y: 4, xref: 'x2', yref: 'y'},
                        {x: 5, y: 0.5, xref: 'x', yref: 'paper'} // any paper ref -> don't swap
                    ]
                }
            };
            var expectedXaxis = {
                    type: 'category',
                    ticks: 'inside',
                    ticklen: 10,
                    tickcolor: '#f00',
                    tickwidth: 2,
                    showline: true,
                    side: 'top',
                    domain: [0, 0.45]
                },
                expectedXaxis2 = {
                    type: 'category',
                    ticks: 'inside',
                    ticklen: 10,
                    tickcolor: '#f00',
                    tickwidth: 3,
                    showline: true,
                    side: 'top',
                    domain: [0.55, 1]
                },
                expectedYaxis = {
                    type: 'linear',
                    ticks: 'outside',
                    ticklen: 5,
                    tickwidth: 4,
                    side: 'right'
                },
                expectedAnnotations = [
                    {x: 3, y: 2},
                    {x: 4, y: 3, xref: 'x2', yref: 'y'},
                    {x: 5, y: 0.5, xref: 'x', yref: 'paper'}
                ];

            Plotly.Plots.supplyDefaults(gd);

            Plotly.Axes.swap(gd, [0, 1]);

            expect(gd.layout.xaxis).toEqual(expectedXaxis);
            expect(gd.layout.xaxis2).toEqual(expectedXaxis2);
            expect(gd.layout.yaxis).toEqual(expectedYaxis);
            expect(gd.layout.annotations).toEqual(expectedAnnotations);
        });
    });
});
<|endoftext|>


<commit_before>// show the installed versions of packages
//
// --parseable creates output like this:
// <fullpath>:<name@ver>:<realpath>:<flags>
// Flags are a :-separated list of zero or more indicators

module.exports = exports = ls

var path = require('path')
var url = require('url')
var readPackageTree = require('read-package-tree')
var log = require('npmlog')
var archy = require('archy')
var semver = require('semver')
var color = require('ansicolors')
var npa = require('npm-package-arg')
var iferr = require('iferr')
var npm = require('./npm.js')
var logicalTree = require('./install/logical-tree.js')
var recalculateMetadata = require('./install/deps.js').recalculateMetadata

ls.usage = "npm ls [[<@scope>/]<pkg> ...]" +
    "\n\naliases: list, la, ll"

ls.completion = require('./utils/completion/installed-deep.js')

function ls (args, silent, cb) {
  if (typeof cb !== 'function') {
    cb = silent
    silent = false
  }
  var dir = path.resolve(npm.dir, '..')
  readPackageTree(dir, andRecalculateMetadata(iferr(cb, function (physicalTree) {
    lsFromTree(dir, physicalTree, args, silent, cb)
  })))
}

function andRecalculateMetadata (next) {
  return function (er, tree) {
    if (er) return next(er)
    recalculateMetadata(tree, log, next)
  }
}

var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
  if (typeof cb !== 'function') {
    cb = silent
    silent = false
  }

  // npm ls 'foo@~1.3' bar 'baz@<2'
  if (!args) {
    args = []
  } else {
    args = args.map(function (a) {
      var p = npa(a)
      var name = p.name
      var ver = semver.validRange(p.rawSpec) || ''

      return [ name, ver ]
    })
  }

  var data = logicalTree.asReadInstalled(physicalTree)
  pruneNestedExtraneous(data)
  filterByEnv(data)
  var bfs = filterFound(bfsify(data), args)
  var lite = getLite(bfs)

  if (silent) return cb(null, data, lite)

  var long = npm.config.get('long')
  var json = npm.config.get('json')
  var out
  if (json) {
    var seen = []
    var d = long ? bfs : lite
    // the raw data can be circular
    out = JSON.stringify(d, function (k, o) {
      if (typeof o === 'object') {
        if (seen.indexOf(o) !== -1) return '[Circular]'
        seen.push(o)
      }
      return o
    }, 2)
  } else if (npm.config.get('parseable')) {
    out = makeParseable(bfs, long, dir)
  } else if (data) {
    out = makeArchy(bfs, long, dir)
  }
  console.log(out)

  if (args.length && !data._found) process.exitCode = 1

  var er
  // if any errors were found, then complain and exit status 1
  if (lite.problems && lite.problems.length) {
    er = lite.problems.join('\n')
  }
  cb(er, data, lite)
}

function pruneNestedExtraneous (data, visited) {
  visited = visited || []
  visited.push(data)
  for (var i in data.dependencies) {
    if (data.dependencies[i].extraneous) {
      data.dependencies[i].dependencies = {}
    } else if (visited.indexOf(data.dependencies[i]) === -1) {
      pruneNestedExtraneous(data.dependencies[i], visited)
    }
  }
}

function filterByEnv (data) {
  var dev = npm.config.get('dev')
  var production = npm.config.get('production')
  var dependencies = {}
  var devDependencies = data.devDependencies || []
  Object.keys(data.dependencies).forEach(function (name) {
    var keys = Object.keys(devDependencies)
    if (production && !dev && keys.indexOf(name) !== -1) return
    if (dev && !production && keys.indexOf(name) === -1) return
    if (!dev && keys.indexOf(name) !== -1 && typeof data.dependencies[name] === 'string') return
    dependencies[name] = data.dependencies[name]
  })
  data.dependencies = dependencies
}

function alphasort (a, b) {
  a = a.toLowerCase()
  b = b.toLowerCase()
  return a > b ? 1
       : a < b ? -1 : 0
}

function getLite (data, noname) {
  var lite = {}
  var maxDepth = npm.config.get('depth')

  if (!noname && data.name) lite.name = data.name
  if (data.version) lite.version = data.version
  if (data.extraneous) {
    lite.extraneous = true
    lite.problems = lite.problems || []
    lite.problems.push('extraneous: ' +
                       data.name + '@' + data.version +
                       ' ' + (data.path || ''))
  }

  if (data._from) {
    lite.from = data._from
  }

  if (data._resolved) {
    lite.resolved = data._resolved
  }

  if (data.invalid) {
    lite.invalid = true
    lite.problems = lite.problems || []
    lite.problems.push('invalid: ' +
                       data.name + '@' + data.version +
                       ' ' + (data.path || ''))
  }

  if (data.peerInvalid) {
    lite.peerInvalid = true
    lite.problems = lite.problems || []
    lite.problems.push('peer dep not met: ' +
                       data.name + '@' + data.version +
                       ' ' + (data.path || ''))
  }

  var deps = (data.dependencies && Object.keys(data.dependencies)) || []
  if (deps.length) {
    lite.dependencies = deps.map(function (d) {
      var dep = data.dependencies[d]
      if (typeof dep === 'string') {
        lite.problems = lite.problems || []
        var p
        if (data.depth > maxDepth) {
          p = 'max depth reached: '
        } else {
          p = 'missing: '
        }
        p += d + '@' + dep +
            ', required by ' +
            data.name + '@' + data.version
        lite.problems.push(p)
        return [d, { required: dep, missing: true }]
      } else if (dep.peerMissing) {
        lite.problems = lite.problems || []
        var p = 'peer dep missing: ' +
            d + '@' + dep.version +
            ', required by ' +
            data.name + '@' + data.version
        lite.problems.push(p)
        return [d, { required: dep, peerMissing: true }]
      }
      return [d, getLite(dep, true)]
    }).reduce(function (deps, d) {
      if (d[1].problems) {
        lite.problems = lite.problems || []
        lite.problems.push.apply(lite.problems, d[1].problems)
      }
      deps[d[0]] = d[1]
      return deps
    }, {})
  }
  return lite
}

function bfsify (root) {
  // walk over the data, and turn it from this:
  // +-- a
  // |   `-- b
  // |       `-- a (truncated)
  // `--b (truncated)
  // into this:
  // +-- a
  // `-- b
  // which looks nicer
  var queue = [root]
  var seen = [root]

  while (queue.length) {
    var current = queue.shift()
    var deps = current.dependencies = current.dependencies || {}
    Object.keys(deps).forEach(function (d) {
      var dep = deps[d]
      if (typeof dep !== 'object') return
      if (seen.indexOf(dep) !== -1) {
        if (npm.config.get('parseable') || !npm.config.get('long')) {
          delete deps[d]
          return
        } else {
          dep = deps[d] = Object.create(dep)
          dep.dependencies = {}
        }
      }
      queue.push(dep)
      seen.push(dep)
    })
  }

  return root
}

function filterFound (root, args) {
  if (!args.length) return root
  var deps = root.dependencies
  if (deps) {
    Object.keys(deps).forEach(function (d) {
      var dep = filterFound(deps[d], args)
      if (dep.peerMissing) return

      // see if this one itself matches
      var found = false
      for (var i = 0; !found && i < args.length; i++) {
        if (d === args[i][0]) {
          found = semver.satisfies(dep.version, args[i][1], true)
        }
      }
      // included explicitly
      if (found) dep._found = true
      // included because a child was included
      if (dep._found && !root._found) root._found = 1
      // not included
      if (!dep._found) delete deps[d]
    })
  }
  if (!root._found) root._found = false
  return root
}

function makeArchy (data, long, dir) {
  var out = makeArchy_(data, long, dir, 0)
  return archy(out, '', { unicode: npm.config.get('unicode') })
}

function makeArchy_ (data, long, dir, depth, parent, d) {
  if (typeof data === 'string') {
    if (depth - 1 <= npm.config.get('depth')) {
      // just missing
      var unmet = 'UNMET DEPENDENCY'
      if (npm.color) {
        unmet = color.bgBlack(color.red(unmet))
      }
      data = unmet + ' ' + d + '@' + data
    } else {
      data = d + '@' + data
    }
    return data
  }

  var out = {}
  // the top level is a bit special.
  out.label = data._id || ''
  if (data._found === true && data._id) {
    if (npm.color) {
      out.label = color.bgBlack(color.yellow(out.label.trim())) + ' '
    } else {
      out.label = out.label.trim() + ' '
    }
  }
  if (data.link) out.label += ' -> ' + data.link

  if (data.invalid) {
    if (data.realName !== data.name) out.label += ' (' + data.realName + ')'
    var invalid = 'invalid'
    if (npm.color) invalid = color.bgBlack(color.red(invalid))
    out.label += ' ' + invalid
  }

  if (data.peerInvalid) {
    var peerInvalid = 'peer invalid'
    if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
    out.label += ' ' + peerInvalid
  }

  if (data.peerMissing) {
    var peerMissing = 'UNMET PEER DEPENDENCY'
    if (npm.color) peerMissing = color.bgBlack(color.red(peerMissing))
    out.label = peerMissing + ' ' + out.label
  }

  if (data.extraneous && data.path !== dir) {
    var extraneous = 'extraneous'
    if (npm.color) extraneous = color.bgBlack(color.green(extraneous))
    out.label += ' ' + extraneous
  }

  // add giturl to name@version
  if (data._resolved) {
    try {
      var type = npa(data._resolved).type
      var isGit = type === 'git' || type === 'hosted'
      if (isGit) {
        out.label += ' (' + data._resolved + ')'
      }
    } catch (ex) {
      // npa threw an exception then it ain't git so whatev
    }
  }

  if (long) {
    if (dir === data.path) out.label += '\n' + dir
    out.label += '\n' + getExtras(data, dir)
  } else if (dir === data.path) {
    if (out.label) out.label += ' '
    out.label += dir
  }

  // now all the children.
  out.nodes = []
  if (depth <= npm.config.get('depth')) {
    out.nodes = Object.keys(data.dependencies || {})
      .sort(alphasort).map(function (d) {
        return makeArchy_(data.dependencies[d], long, dir, depth + 1, data, d)
      })
  }

  if (out.nodes.length === 0 && data.path === dir) {
    out.nodes = ['(empty)']
  }

  return out
}

function getExtras (data) {
  var extras = []

  if (data.description) extras.push(data.description)
  if (data.repository) extras.push(data.repository.url)
  if (data.homepage) extras.push(data.homepage)
  if (data._from) {
    var from = data._from
    if (from.indexOf(data.name + '@') === 0) {
      from = from.substr(data.name.length + 1)
    }
    var u = url.parse(from)
    if (u.protocol) extras.push(from)
  }
  return extras.join('\n')
}

function makeParseable (data, long, dir, depth, parent, d) {
  depth = depth || 0

  return [ makeParseable_(data, long, dir, depth, parent, d) ]
  .concat(Object.keys(data.dependencies || {})
    .sort(alphasort).map(function (d) {
      return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d)
    }))
  .filter(function (x) { return x })
  .join('\n')
}

function makeParseable_ (data, long, dir, depth, parent, d) {
  if (data.hasOwnProperty('_found') && data._found !== true) return ''

  if (typeof data === 'string') {
    if (data.depth < npm.config.get('depth')) {
      data = npm.config.get('long')
           ? path.resolve(parent.path, 'node_modules', d) +
             ':' + d + '@' + JSON.stringify(data) + ':INVALID:MISSING'
           : ''
    } else {
      data = path.resolve(data.path || '', 'node_modules', d || '') +
             (npm.config.get('long')
             ? ':' + d + '@' + JSON.stringify(data) +
               ':' + // no realpath resolved
               ':MAXDEPTH'
             : '')
    }

    return data
  }

  if (!npm.config.get('long')) return data.path

  return data.path +
         ':' + (data._id || '') +
         ':' + (data.realPath !== data.path ? data.realPath : '') +
         (data.extraneous ? ':EXTRANEOUS' : '') +
         (data.invalid ? ':INVALID' : '') +
         (data.peerInvalid ? ':PEERINVALID' : '')
         (data.peerMissing ? ':PEERINVALID:MISSING' : '')
}
<commit_msg>ls: don't error out if our folder can't be read<commit_after>// show the installed versions of packages
//
// --parseable creates output like this:
// <fullpath>:<name@ver>:<realpath>:<flags>
// Flags are a :-separated list of zero or more indicators

module.exports = exports = ls

var path = require('path')
var url = require('url')
var readPackageTree = require('read-package-tree')
var log = require('npmlog')
var archy = require('archy')
var semver = require('semver')
var color = require('ansicolors')
var npa = require('npm-package-arg')
var iferr = require('iferr')
var npm = require('./npm.js')
var logicalTree = require('./install/logical-tree.js')
var recalculateMetadata = require('./install/deps.js').recalculateMetadata

ls.usage = "npm ls [[<@scope>/]<pkg> ...]" +
    "\n\naliases: list, la, ll"

ls.completion = require('./utils/completion/installed-deep.js')

function ls (args, silent, cb) {
  if (typeof cb !== 'function') {
    cb = silent
    silent = false
  }
  var dir = path.resolve(npm.dir, '..')
  readPackageTree(dir, andRecalculateMetadata(iferr(cb, function (physicalTree) {
    lsFromTree(dir, physicalTree, args, silent, cb)
  })))
}

function andRecalculateMetadata (next) {
  return function (er, tree) {
    recalculateMetadata(tree || {}, log, next)
  }
}

var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
  if (typeof cb !== 'function') {
    cb = silent
    silent = false
  }

  // npm ls 'foo@~1.3' bar 'baz@<2'
  if (!args) {
    args = []
  } else {
    args = args.map(function (a) {
      var p = npa(a)
      var name = p.name
      var ver = semver.validRange(p.rawSpec) || ''

      return [ name, ver ]
    })
  }

  var data = logicalTree.asReadInstalled(physicalTree)
  pruneNestedExtraneous(data)
  filterByEnv(data)
  var bfs = filterFound(bfsify(data), args)
  var lite = getLite(bfs)

  if (silent) return cb(null, data, lite)

  var long = npm.config.get('long')
  var json = npm.config.get('json')
  var out
  if (json) {
    var seen = []
    var d = long ? bfs : lite
    // the raw data can be circular
    out = JSON.stringify(d, function (k, o) {
      if (typeof o === 'object') {
        if (seen.indexOf(o) !== -1) return '[Circular]'
        seen.push(o)
      }
      return o
    }, 2)
  } else if (npm.config.get('parseable')) {
    out = makeParseable(bfs, long, dir)
  } else if (data) {
    out = makeArchy(bfs, long, dir)
  }
  console.log(out)

  if (args.length && !data._found) process.exitCode = 1

  var er
  // if any errors were found, then complain and exit status 1
  if (lite.problems && lite.problems.length) {
    er = lite.problems.join('\n')
  }
  cb(er, data, lite)
}

function pruneNestedExtraneous (data, visited) {
  visited = visited || []
  visited.push(data)
  for (var i in data.dependencies) {
    if (data.dependencies[i].extraneous) {
      data.dependencies[i].dependencies = {}
    } else if (visited.indexOf(data.dependencies[i]) === -1) {
      pruneNestedExtraneous(data.dependencies[i], visited)
    }
  }
}

function filterByEnv (data) {
  var dev = npm.config.get('dev')
  var production = npm.config.get('production')
  var dependencies = {}
  var devDependencies = data.devDependencies || []
  Object.keys(data.dependencies).forEach(function (name) {
    var keys = Object.keys(devDependencies)
    if (production && !dev && keys.indexOf(name) !== -1) return
    if (dev && !production && keys.indexOf(name) === -1) return
    if (!dev && keys.indexOf(name) !== -1 && typeof data.dependencies[name] === 'string') return
    dependencies[name] = data.dependencies[name]
  })
  data.dependencies = dependencies
}

function alphasort (a, b) {
  a = a.toLowerCase()
  b = b.toLowerCase()
  return a > b ? 1
       : a < b ? -1 : 0
}

function getLite (data, noname) {
  var lite = {}
  var maxDepth = npm.config.get('depth')

  if (!noname && data.name) lite.name = data.name
  if (data.version) lite.version = data.version
  if (data.extraneous) {
    lite.extraneous = true
    lite.problems = lite.problems || []
    lite.problems.push('extraneous: ' +
                       data.name + '@' + data.version +
                       ' ' + (data.path || ''))
  }

  if (data._from) {
    lite.from = data._from
  }

  if (data._resolved) {
    lite.resolved = data._resolved
  }

  if (data.invalid) {
    lite.invalid = true
    lite.problems = lite.problems || []
    lite.problems.push('invalid: ' +
                       data.name + '@' + data.version +
                       ' ' + (data.path || ''))
  }

  if (data.peerInvalid) {
    lite.peerInvalid = true
    lite.problems = lite.problems || []
    lite.problems.push('peer dep not met: ' +
                       data.name + '@' + data.version +
                       ' ' + (data.path || ''))
  }

  var deps = (data.dependencies && Object.keys(data.dependencies)) || []
  if (deps.length) {
    lite.dependencies = deps.map(function (d) {
      var dep = data.dependencies[d]
      if (typeof dep === 'string') {
        lite.problems = lite.problems || []
        var p
        if (data.depth > maxDepth) {
          p = 'max depth reached: '
        } else {
          p = 'missing: '
        }
        p += d + '@' + dep +
            ', required by ' +
            data.name + '@' + data.version
        lite.problems.push(p)
        return [d, { required: dep, missing: true }]
      } else if (dep.peerMissing) {
        lite.problems = lite.problems || []
        var p = 'peer dep missing: ' +
            d + '@' + dep.version +
            ', required by ' +
            data.name + '@' + data.version
        lite.problems.push(p)
        return [d, { required: dep, peerMissing: true }]
      }
      return [d, getLite(dep, true)]
    }).reduce(function (deps, d) {
      if (d[1].problems) {
        lite.problems = lite.problems || []
        lite.problems.push.apply(lite.problems, d[1].problems)
      }
      deps[d[0]] = d[1]
      return deps
    }, {})
  }
  return lite
}

function bfsify (root) {
  // walk over the data, and turn it from this:
  // +-- a
  // |   `-- b
  // |       `-- a (truncated)
  // `--b (truncated)
  // into this:
  // +-- a
  // `-- b
  // which looks nicer
  var queue = [root]
  var seen = [root]

  while (queue.length) {
    var current = queue.shift()
    var deps = current.dependencies = current.dependencies || {}
    Object.keys(deps).forEach(function (d) {
      var dep = deps[d]
      if (typeof dep !== 'object') return
      if (seen.indexOf(dep) !== -1) {
        if (npm.config.get('parseable') || !npm.config.get('long')) {
          delete deps[d]
          return
        } else {
          dep = deps[d] = Object.create(dep)
          dep.dependencies = {}
        }
      }
      queue.push(dep)
      seen.push(dep)
    })
  }

  return root
}

function filterFound (root, args) {
  if (!args.length) return root
  var deps = root.dependencies
  if (deps) {
    Object.keys(deps).forEach(function (d) {
      var dep = filterFound(deps[d], args)
      if (dep.peerMissing) return

      // see if this one itself matches
      var found = false
      for (var i = 0; !found && i < args.length; i++) {
        if (d === args[i][0]) {
          found = semver.satisfies(dep.version, args[i][1], true)
        }
      }
      // included explicitly
      if (found) dep._found = true
      // included because a child was included
      if (dep._found && !root._found) root._found = 1
      // not included
      if (!dep._found) delete deps[d]
    })
  }
  if (!root._found) root._found = false
  return root
}

function makeArchy (data, long, dir) {
  var out = makeArchy_(data, long, dir, 0)
  return archy(out, '', { unicode: npm.config.get('unicode') })
}

function makeArchy_ (data, long, dir, depth, parent, d) {
  if (typeof data === 'string') {
    if (depth - 1 <= npm.config.get('depth')) {
      // just missing
      var unmet = 'UNMET DEPENDENCY'
      if (npm.color) {
        unmet = color.bgBlack(color.red(unmet))
      }
      data = unmet + ' ' + d + '@' + data
    } else {
      data = d + '@' + data
    }
    return data
  }

  var out = {}
  // the top level is a bit special.
  out.label = data._id || ''
  if (data._found === true && data._id) {
    if (npm.color) {
      out.label = color.bgBlack(color.yellow(out.label.trim())) + ' '
    } else {
      out.label = out.label.trim() + ' '
    }
  }
  if (data.link) out.label += ' -> ' + data.link

  if (data.invalid) {
    if (data.realName !== data.name) out.label += ' (' + data.realName + ')'
    var invalid = 'invalid'
    if (npm.color) invalid = color.bgBlack(color.red(invalid))
    out.label += ' ' + invalid
  }

  if (data.peerInvalid) {
    var peerInvalid = 'peer invalid'
    if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
    out.label += ' ' + peerInvalid
  }

  if (data.peerMissing) {
    var peerMissing = 'UNMET PEER DEPENDENCY'
    if (npm.color) peerMissing = color.bgBlack(color.red(peerMissing))
    out.label = peerMissing + ' ' + out.label
  }

  if (data.extraneous && data.path !== dir) {
    var extraneous = 'extraneous'
    if (npm.color) extraneous = color.bgBlack(color.green(extraneous))
    out.label += ' ' + extraneous
  }

  // add giturl to name@version
  if (data._resolved) {
    try {
      var type = npa(data._resolved).type
      var isGit = type === 'git' || type === 'hosted'
      if (isGit) {
        out.label += ' (' + data._resolved + ')'
      }
    } catch (ex) {
      // npa threw an exception then it ain't git so whatev
    }
  }

  if (long) {
    if (dir === data.path) out.label += '\n' + dir
    out.label += '\n' + getExtras(data, dir)
  } else if (dir === data.path) {
    if (out.label) out.label += ' '
    out.label += dir
  }

  // now all the children.
  out.nodes = []
  if (depth <= npm.config.get('depth')) {
    out.nodes = Object.keys(data.dependencies || {})
      .sort(alphasort).map(function (d) {
        return makeArchy_(data.dependencies[d], long, dir, depth + 1, data, d)
      })
  }

  if (out.nodes.length === 0 && data.path === dir) {
    out.nodes = ['(empty)']
  }

  return out
}

function getExtras (data) {
  var extras = []

  if (data.description) extras.push(data.description)
  if (data.repository) extras.push(data.repository.url)
  if (data.homepage) extras.push(data.homepage)
  if (data._from) {
    var from = data._from
    if (from.indexOf(data.name + '@') === 0) {
      from = from.substr(data.name.length + 1)
    }
    var u = url.parse(from)
    if (u.protocol) extras.push(from)
  }
  return extras.join('\n')
}

function makeParseable (data, long, dir, depth, parent, d) {
  depth = depth || 0

  return [ makeParseable_(data, long, dir, depth, parent, d) ]
  .concat(Object.keys(data.dependencies || {})
    .sort(alphasort).map(function (d) {
      return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d)
    }))
  .filter(function (x) { return x })
  .join('\n')
}

function makeParseable_ (data, long, dir, depth, parent, d) {
  if (data.hasOwnProperty('_found') && data._found !== true) return ''

  if (typeof data === 'string') {
    if (data.depth < npm.config.get('depth')) {
      data = npm.config.get('long')
           ? path.resolve(parent.path, 'node_modules', d) +
             ':' + d + '@' + JSON.stringify(data) + ':INVALID:MISSING'
           : ''
    } else {
      data = path.resolve(data.path || '', 'node_modules', d || '') +
             (npm.config.get('long')
             ? ':' + d + '@' + JSON.stringify(data) +
               ':' + // no realpath resolved
               ':MAXDEPTH'
             : '')
    }

    return data
  }

  if (!npm.config.get('long')) return data.path

  return data.path +
         ':' + (data._id || '') +
         ':' + (data.realPath !== data.path ? data.realPath : '') +
         (data.extraneous ? ':EXTRANEOUS' : '') +
         (data.invalid ? ':INVALID' : '') +
         (data.peerInvalid ? ':PEERINVALID' : '')
         (data.peerMissing ? ':PEERINVALID:MISSING' : '')
}
<|endoftext|>


<commit_before>var ARGV = process.argv,
    MD_DIR = ARGV[2],
    MD_FILE = ARGV[3];

var fs = require('fs');

var Ezlog = require('Ezlog'),
    log = new Ezlog({ p: {t: '[ma]', c: 'green'} }),
    logWarn = new Ezlog({ p: {t: '[ma]', c: 'green'}, t: {c: 'yellow'} });
    logErr = new Ezlog({ p: {t: '[ma]', c: 'green'}, t: {c: 'red'} });

try { fs.readdirSync(MD_DIR); }
catch(e){ logWarn('Could not read the directory ma is called from'); }

if(!MD_FILE)
  logWarn('No markdown file to edit given, '
    + 'will try to find any markdown file now');

var files = fs.readdirSync(MD_DIR);

if(files.length === 0)
  return logErr('There are no files in the directory ma is called from, '
    + 'will exit now');<commit_msg>Can start working on conversion progress, gaurd are ready<commit_after>var ARGV = process.argv,
    MD_DIR = ARGV[2],
    MD_FILE = ARGV[3];

var fs = require('fs');

var Ezlog = require('Ezlog'),
    log = new Ezlog({ p: {t: '[ma]', c: 'green'} }),
    logWarn = new Ezlog({ p: {t: '[ma]', c: 'green'}, t: {c: 'yellow'} });
    logErr = new Ezlog({ p: {t: '[ma]', c: 'green'}, t: {c: 'red'} });

var files;
try { files = fs.readdirSync(MD_DIR); }
catch(e){
  logErr('Could not read the directory ma is called from, '
    + 'will exit now');

  process.exit();
}

if(!MD_FILE)
  logWarn('No markdown file to edit given, '
    + 'will try to find any markdown file now');

if(files.length === 0)
  return logErr('There are no files in the directory ma is called from, '
    + 'will exit now');

var md_files = [];

files.forEach(function (file){
  if(file.indexOf('.md') !== -1 || file.indexOf('.markdown') !== -1){
    log('Found a markdown file: ' + file);
    md_files.push(file);
  }
});

if(md_files.length === 0)
  logErr('No markdown files found in the directory ma is called from, '
    + 'will exit now');<|endoftext|>