<commit_before>"use strict";

var jsdom = require("../..").jsdom;

// These tests are mostly random regression tests, not systematic parsing tests. They are compiled from the bug tracker.

exports["unclosed  (GH-605)"] = function (t) {
  var doc = jsdom("

first tdsecond td

");

  var tds = doc.getElementsByTagName("td");

  t.equal(tds.length, 2);
  t.equal(tds[0].innerHTML, 'first td');
  t.equal(tds[1].innerHTML, 'second td');

  t.done();
};

exports["multiline attributes (GH-585)"] = function (t) {
  var doc = jsdom("<a data-title='hello \nworld' href='example.org'>link");

  var as = doc.getElementsByTagName("a");

  t.equal(as.length, 1);
  t.equal(as[0].innerHTML, "link");
  t.equal(as[0].getAttribute("data-title"), "hello \nworld");
  t.equal(as[0].getAttribute("href"), "example.org");

  t.done();
};

exports["innerHTML of ");

  var scripts = doc.getElementsByTagName("script");

  t.equal(scripts.length, 1);
  t.equal(scripts[0].innerHTML, "script innerHTML");

  t.done();
};

exports["attributes containing '<' and '>' (GH-494)"] = function (t) {
  var doc = jsdom("<p title='<'>stuff

'>more

just testing
");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 3);
  t.equal(ps[0].getAttribute("title"), "<");
  t.equal(ps[1].getAttribute("title"), ">");

  t.done();
};

exports["empty attributes (GH-488)"] = function (t) {
  var doc = jsdom("

");

  var divs = doc.getElementsByTagName("div");

  t.equal(divs.length, 1);
  t.equal(divs[0].getAttribute("ng-app"), "");

  t.done();
};

exports["omitting optional closing tags (GH-482)"] = function (t) {
  var doc = jsdom("
First
Second
Third");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 3);
  t.equal(ps[0].innerHTML, "First");
  t.equal(ps[1].innerHTML, "Second");
  t.equal(ps[2].innerHTML, "Third");

  t.done();
};

exports["crazy attribute names (GH-368)"] = function (t) {
  var doc = jsdom("<p <='' FAIL>stuff
");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 1);
  t.equal(ps[0].innerHTML, "stuff");
  t.equal(ps[0].getAttribute("<"), "");
  t.equal(ps[0].getAttribute("fail"), "");

  t.done();
};

exports["attribute named 'constructor' (GH-625)"] = function (t) {
  var doc = jsdom("<element constructor='Hello'></element>");

  var els = doc.getElementsByTagName("element");

  t.equal(els.length, 1);
  t.equal(els[0].getAttribute("constructor"), "Hello");
  t.equal(els[0].attributes.length, 1);
  t.equal(els[0].outerHTML, "<element constructor=\"Hello\"></element>");

  t.done();
};

exports["CDATA should parse as bogus comments (GH-618)"] = function (t) {
  var doc = jsdom("
<![CDATA[test]]>
");

  var div = doc.getElementsByTagName("div")[0];

  t.ok(div);
  t.equal(div.childNodes.length, 1);

  var comment = div.childNodes[0];
  t.equal(comment.nodeType, comment.COMMENT_NODE);
  t.equal(comment.nodeValue, "[CDATA[test]]");

  t.equal(doc.documentElement.outerHTML, "

");

  t.done();
};
<commit_msg>Add (now-passing) test for #652.<commit_after>"use strict";

var jsdom = require("../..").jsdom;

// These tests are mostly random regression tests, not systematic parsing tests. They are compiled from the bug tracker.

exports["unclosed  (GH-605)"] = function (t) {
  var doc = jsdom("

first tdsecond td

");

  var tds = doc.getElementsByTagName("td");

  t.equal(tds.length, 2);
  t.equal(tds[0].innerHTML, 'first td');
  t.equal(tds[1].innerHTML, 'second td');

  t.done();
};

exports["multiline attributes (GH-585)"] = function (t) {
  var doc = jsdom("<a data-title='hello \nworld' href='example.org'>link");

  var as = doc.getElementsByTagName("a");

  t.equal(as.length, 1);
  t.equal(as[0].innerHTML, "link");
  t.equal(as[0].getAttribute("data-title"), "hello \nworld");
  t.equal(as[0].getAttribute("href"), "example.org");

  t.done();
};

exports["innerHTML of ");

  var scripts = doc.getElementsByTagName("script");

  t.equal(scripts.length, 1);
  t.equal(scripts[0].innerHTML, "script innerHTML");

  t.done();
};

exports["attributes containing '<' and '>' (GH-494)"] = function (t) {
  var doc = jsdom("<p title='<'>stuff

'>more

just testing
");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 3);
  t.equal(ps[0].getAttribute("title"), "<");
  t.equal(ps[1].getAttribute("title"), ">");

  t.done();
};

exports["empty attributes (GH-488)"] = function (t) {
  var doc = jsdom("

");

  var divs = doc.getElementsByTagName("div");

  t.equal(divs.length, 1);
  t.equal(divs[0].getAttribute("ng-app"), "");

  t.done();
};

exports["omitting optional closing tags (GH-482)"] = function (t) {
  var doc = jsdom("
First
Second
Third");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 3);
  t.equal(ps[0].innerHTML, "First");
  t.equal(ps[1].innerHTML, "Second");
  t.equal(ps[2].innerHTML, "Third");

  t.done();
};

exports["crazy attribute names (GH-368)"] = function (t) {
  var doc = jsdom("<p <='' FAIL>stuff
");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 1);
  t.equal(ps[0].innerHTML, "stuff");
  t.equal(ps[0].getAttribute("<"), "");
  t.equal(ps[0].getAttribute("fail"), "");

  t.done();
};

exports["attribute named 'constructor' (GH-625)"] = function (t) {
  var doc = jsdom("<element constructor='Hello'></element>");

  var els = doc.getElementsByTagName("element");

  t.equal(els.length, 1);
  t.equal(els[0].getAttribute("constructor"), "Hello");
  t.equal(els[0].attributes.length, 1);
  t.equal(els[0].outerHTML, "<element constructor=\"Hello\"></element>");

  t.done();
};

exports["CDATA should parse as bogus comments (GH-618)"] = function (t) {
  var doc = jsdom("
<![CDATA[test]]>
");

  var div = doc.getElementsByTagName("div")[0];

  t.ok(div);
  t.equal(div.childNodes.length, 1);

  var comment = div.childNodes[0];
  t.equal(comment.nodeType, comment.COMMENT_NODE);
  t.equal(comment.nodeValue, "[CDATA[test]]");

  t.equal(doc.documentElement.outerHTML, "

");

  t.done();
};

exports["innerHTML behavior in


<commit_before>/*
Vanilla JavaScript for TicTacToe-AI
Author: Khalid Omar Ali
*/

(function() {

    'use strict';

    // var socket = io.connect("http://localhost:3000");
    if (!Function.prototype.bind) { // credit to Crockford for this bind function
        Function.prototype.bind = function(oThis) {
            if (typeof this !== "function") {
                // closest thing possible to the ECMAScript 5 internal IsCallable function
                throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
            }
            var aArgs = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP = function() {},
                fBound = function() {
                    return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
                };
            fNOP.prototype = this.prototype;
            fBound.prototype = new fNOP();
            return fBound;
        };
    }

    var TicTacToe = function() { // current function constructor
        this.init(); // constructor invocation method - this bound to the new object
    };
    // this is the prototype object associated with the above function constructor
    TicTacToe.prototype = {
        // set initial game state
        boardElem: null,
        resetElem: null,
        resultElem: null,
        statusElem: null,
        moves: 0,
        xTurn: true,
        gameOver: false,
        boardArr: [],
        winCombo: [
            [0, 1, 2],
            [3, 4, 5],
            [6, 7, 8],
            [0, 3, 6],
            [1, 4, 7],
            [2, 5, 8],
            [0, 4, 8],
            [2, 4, 6]
        ],

        resetGame: function() {
            var elem, i, j;
            for (i = 0; i <= 8; i += 1) {
                this.boardArr[i] = null; // set all values of board array to null
            }
            for (j = 0; j <= 8; j += 1) {
                this.squares[j].value = ""; // clear the board UI
            }
            this.moves = 0; // current move incrementor
            this.gameOver = false;
            this.xTurn = true; // X is first player
            this.statusElem.innerHTML = "";
        },
        // initialise game
        init: function() {
            // get elements
            var src;
            this.resetElem = document.getElementById("start-button");
            this.boardElem = document.getElementById("game-board");
            this.resultElem = document.getElementById("results");
            this.statusElem = document.getElementById("status");
            this.squares = document.getElementsByTagName("input");
            // reset game when first started
            this.resetGame();
            // bind main event handlers to reset game and click board
            this.resetElem.onclick = function() {
                this.resetGame();
            }.bind(this); // inner function's this now bounded to the this variable of the outer function
            // use bind method instead of writing var that = this
            this.boardElem.onclick = function(e) {
                // event sometimes available through the global variable event for IE
                e = e || event;
                // event target is the object which the event is associated with
                src = e.boardElem || e.target;
                // pass an object containing the element and its data-position to renderMove method
                this.renderMove(this.boardArr, {
                    position: src.getAttribute("data-position"),
                    elem: src
                });
            }.bind(this);
        },

        turnStatus: function() {
            this.statusElem.innerHTML = "It's the turn of " + (this.xTurn ? "X" : "O");
        },

        isSquareAvailable: function(board, position) {
            return board[position] === null; // returns true if space is available
        },

        checkForWinningMove: function() { // check 3 rows, 3 columns and 2 diagonals using winCombo array that holds all winning combinations of the game
            var i;
            for (i = 0; i < this.winCombo.length; i += 1) {
                if (this.boardArr[this.winCombo[i][0]] === this.boardArr[this.winCombo[i][1]] && this.boardArr[this.winCombo[i][1]] ===
                    this.boardArr[this.winCombo[i][2]] && this.boardArr[this.winCombo[i][1]] !== null) {
                    return true;
                }
            }
        },

        validMoves: function() {
            if (this.moves === 9) {
                return this.moves;
            }
        },

        drawn: function() {
            // indices begin at 0
            if (this.validMoves() === 9) {
                alert("Draw game!");
                this.resetGame();
            }
        },

        won: function(result) {
            result = this.checkForWinningMove();
            if (result === true) { // if there is a winner
                if (this.xTurn === false) { // decide if X won or else O won
                    alert("X wins!");
                    this.resetGame();
                } else {
                    alert("O wins!");
                    this.resetGame();
                }
            }
        },

        isGameOver: function() {
            return this.won() || this.drawn(); // decide the game's outcome
        },

        // condition for the AI turn
        aiTurn: function() {
            return this.xTurn === false && this.gameOver === false;
        },

        // unique random number generator for naive AI
        aiRandomNo: function() {
            var numberpool = [0, 1, 2, 3, 4, 5, 6, 7, 8],
                randomIndex = Math.floor(Math.random() * 9),
                pickNo;
            if (numberpool.length === 0) {
                throw "no random numbers left";
            }
            // pic a random number and remove from the pool
            pickNo = numberpool.splice(randomIndex, 1);
            return pickNo[0];
        },

        aiMove: function (board, randPos) {
            this.moves += 1;
            board[randPos] = "O";
            this.squares[randPos].value = "O";
            this.xTurn = true;
            this.turnStatus();
        },

        // dealing with conflict between naive AI and Human player
        aiConflict: function(board, randPos) {
            // if there is no space for AI
            var condition = !this.isSquareAvailable(board, randPos);
            while(condition) {
                // generate a new random position
                randPos = this.aiRandomNo();
                // if there is a space available for AI, exit loop
                if(this.isSquareAvailable(board, randPos)) {
                    this.aiMove(board, randPos);
                    condition = false;
                }
            }
        },
        // AI render's move
        aiRandomPlay: function(board, randPos) {
            // play random move by generating random numbers and checking space is available!
            randPos = this.aiRandomNo();
                // if space is available, play AI move
                if (this.isSquareAvailable(this.boardArr, randPos)) {
                    this.aiMove(board, randPos);
                } else {
                    // find random position that doesn't conficlt with human player
                    this.aiConflict(board, randPos);
                }
        },
        // AI method returns an updated board
        ai: function() {
            if (this.aiTurn()) {
                return this.aiRandomPlay(this.boardArr);
            }
        },
        // actual game play
        renderMove: function(board, square, squareElem, squarePos) {
            // acess object that hold each square elem clicked and it's position
            squareElem = square.elem;
            squarePos = square.position;
            // stop drawing on a square that's already taken
            if (squareElem.value !== "") {
                return;
            }
            // if it is the turn of the player and a square is available and game is not over
            if (this.xTurn !== false && this.isSquareAvailable(board, squarePos) &&
                this.gameOver !== true) {
                this.moves += 1;
                board[squarePos] = "X";
                squareElem.value = board[squarePos];
                this.xTurn = false;
                this.turnStatus();
            }
            // get AI move as long as all squares haven't been taken
            if(this.moves !== 9) {
                this.ai();
            }
            // check if the game is over
            this.isGameOver();
        }
    };
    var playGame = new TicTacToe();
})();<commit_msg>fix this.won method<commit_after>/*
Vanilla JavaScript for TicTacToe-AI
Author: Khalid Omar Ali
*/

(function() {

    'use strict';

    // var socket = io.connect("http://localhost:3000");
    if (!Function.prototype.bind) { // credit to Crockford for this bind function
        Function.prototype.bind = function(oThis) {
            if (typeof this !== "function") {
                // closest thing possible to the ECMAScript 5 internal IsCallable function
                throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
            }
            var aArgs = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP = function() {},
                fBound = function() {
                    return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
                };
            fNOP.prototype = this.prototype;
            fBound.prototype = new fNOP();
            return fBound;
        };
    }

    var TicTacToe = function() { // current function constructor
        this.init(); // constructor invocation method - this bound to the new object
    };
    // this is the prototype object associated with the above function constructor
    TicTacToe.prototype = {
        // set initial game state
        boardElem: null,
        resetElem: null,
        resultElem: null,
        statusElem: null,
        moves: 0,
        xTurn: true,
        gameOver: false,
        boardArr: [],
        winCombo: [
            [0, 1, 2],
            [3, 4, 5],
            [6, 7, 8],
            [0, 3, 6],
            [1, 4, 7],
            [2, 5, 8],
            [0, 4, 8],
            [2, 4, 6]
        ],

        resetGame: function() {
            var elem, i, j;
            for (i = 0; i <= 8; i += 1) {
                this.boardArr[i] = null; // set all values of board array to null
            }
            for (j = 0; j <= 8; j += 1) {
                this.squares[j].value = ""; // clear the board UI
            }
            this.moves = 0; // current move incrementor
            this.gameOver = false;
            this.xTurn = true; // X is first player
            this.statusElem.innerHTML = "";
        },
        // initialise game
        init: function() {
            // get elements
            var src;
            this.resetElem = document.getElementById("start-button");
            this.boardElem = document.getElementById("game-board");
            this.resultElem = document.getElementById("results");
            this.statusElem = document.getElementById("status");
            this.squares = document.getElementsByTagName("input");
            // reset game when first started
            this.resetGame();
            // bind main event handlers to reset game and click board
            this.resetElem.onclick = function() {
                this.resetGame();
            }.bind(this); // inner function's this now bounded to the this variable of the outer function
            // use bind method instead of writing var that = this
            this.boardElem.onclick = function(e) {
                // event sometimes available through the global variable event for IE
                e = e || event;
                // event target is the object which the event is associated with
                src = e.boardElem || e.target;
                // pass an object containing the element and its data-position to renderMove method
                this.renderMove(this.boardArr, {
                    position: src.getAttribute("data-position"),
                    elem: src
                });
            }.bind(this);
        },

        turnStatus: function() {
            this.statusElem.innerHTML = "It's the turn of " + (this.xTurn ? "X" : "O");
        },

        isSquareAvailable: function(board, position) {
            return board[position] === null; // returns true if space is available
        },

        checkForWinningMove: function() { // check 3 rows, 3 columns and 2 diagonals using winCombo array that holds all winning combinations of the game
            var i;
            for (i = 0; i < this.winCombo.length; i += 1) {
                if (this.boardArr[this.winCombo[i][0]] === this.boardArr[this.winCombo[i][1]] && this.boardArr[this.winCombo[i][1]] ===
                    this.boardArr[this.winCombo[i][2]] && this.boardArr[this.winCombo[i][1]] !== null) {
                    return true;
                }
            }
        },

        validMoves: function() {
            if (this.moves === 9) {
                return this.moves;
            }
        },

        drawn: function() {
            // indices begin at 0
            if (this.validMoves() === 9) {
                alert("Draw game!");
                this.resetGame();
            }
        },

        won: function(result) {
            result = this.checkForWinningMove();
            if (result === true) { // if there is a winner
                if (this.xTurn === false) { // decide if X won or else O won
                    alert("X wins!");
                    this.resetGame();
                } else {
                    alert("O wins!");
                    this.resetGame();
                }
            }
        },

        isGameOver: function() {
            return this.won() || this.drawn(); // decide the game's outcome
        },

        // condition for the AI turn
        aiTurn: function() {
            return this.xTurn === false && this.gameOver === false;
        },

        // unique random number generator for naive AI
        aiRandomNo: function() {
            var numberpool = [0, 1, 2, 3, 4, 5, 6, 7, 8],
                randomIndex = Math.floor(Math.random() * 9),
                pickNo;
            if (numberpool.length === 0) {
                throw "no random numbers left";
            }
            // pic a random number and remove from the pool
            pickNo = numberpool.splice(randomIndex, 1);
            return pickNo[0];
        },

        aiMove: function (board, randPos) {
            this.moves += 1;
            board[randPos] = "O";
            this.squares[randPos].value = "O";
            this.xTurn = true;
            this.turnStatus();
        },

        // dealing with conflict between naive AI and Human player
        aiConflict: function(board, randPos) {
            // if there is no space for AI
            var condition = !this.isSquareAvailable(board, randPos);
            while(condition) {
                // generate a new random position
                randPos = this.aiRandomNo();
                // if there is a space available for AI, exit loop
                if(this.isSquareAvailable(board, randPos)) {
                    this.aiMove(board, randPos);
                    condition = false;
                }
            }
        },
        // AI render's move
        aiRandomPlay: function(board, randPos) {
            // play random move by generating random numbers and checking space is available!
            randPos = this.aiRandomNo();
                // if space is available, play AI move
                if (this.isSquareAvailable(this.boardArr, randPos)) {
                    this.aiMove(board, randPos);
                } else {
                    // find random position that doesn't conficlt with human player
                    this.aiConflict(board, randPos);
                }
        },
        // AI method returns an updated board
        ai: function() {
            if (this.aiTurn()) {
                return this.aiRandomPlay(this.boardArr);
            }
        },
        // actual game play
        renderMove: function(board, square, squareElem, squarePos) {
            // acess object that hold each square elem clicked and it's position
            squareElem = square.elem;
            squarePos = square.position;
            // stop drawing on a square that's already taken
            if (squareElem.value !== "") {
                return;
            }
            // if it is the turn of the player and a square is available and game is not over
            if (this.xTurn !== false && this.isSquareAvailable(board, squarePos) &&
                this.gameOver !== true) {
                this.moves += 1;
                board[squarePos] = "X";
                squareElem.value = board[squarePos];
                this.xTurn = false;
                this.turnStatus();
            }
            // get AI move as long as all squares haven't been taken
            // and there hasn't been a winning move
            if(this.moves !== 9 && !this.checkForWinningMove()) {
                this.ai();
            }
            // check if the game is over
            this.isGameOver();
        }
    };
    var playGame = new TicTacToe();
})();<|endoftext|>


<commit_before>// Karma configuration
// Generated on Fri Feb 05 2016 12:36:08 GMT+0100 (CET)
/*globals module*/
module.exports = function (config) {
    'use strict';
    config.set({
        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['mocha', 'chai'],

        // list of files to exclude
        exclude: [],

        files: [{
            pattern: 'build/osmd-debug.js'
        }, {
            pattern: 'src/**/*.ts',
            included: false
        }, {
            pattern: 'test/**/*.ts',
            included: false
        }, {
            pattern: 'test/data/*.xml',
            included: true
        }, {
            pattern: 'test/data/*.mxl.base64',
            included: true
        }, {
            pattern: 'test/data/*.mxl',
            included: false,
            watched: false,
            served: true
        }],

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
            'test/data/*.xml': ['xml2js'],
            'test/data/*.mxl.base64': ['base64-to-js']
        },

        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['mocha'],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_WARN,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: false,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: [], // Will be overruled by karma grunt task options

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: true,

        // Concurrency level
        // how many browser should be started simultaneous
        concurrency: Infinity
    });
};
<commit_msg>Fix Logging pt4<commit_after>// Karma configuration
// Generated on Fri Feb 05 2016 12:36:08 GMT+0100 (CET)
/*globals module*/
module.exports = function (config) {
    'use strict';
    config.set({
        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['mocha', 'chai'],

        // list of files to exclude
        exclude: [],

        files: [{
            pattern: 'build/osmd-debug.js'
        }, {
            pattern: 'src/**/*.ts',
            included: false
        }, {
            pattern: 'test/**/*.ts',
            included: false
        }, {
            pattern: 'test/data/*.xml',
            included: true
        }, {
            pattern: 'test/data/*.mxl.base64',
            included: true
        }, {
            pattern: 'test/data/*.mxl',
            included: false,
            watched: false,
            served: true
        }],

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
            'test/data/*.xml': ['xml2js'],
            'test/data/*.mxl.base64': ['base64-to-js']
        },

        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['mocha'],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        client: {
            captureConsole: true,
            mocha: {
                bail: true
            }
        },

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: false,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: [], // Will be overruled by karma grunt task options

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: true,

        // Concurrency level
        // how many browser should be started simultaneous
        concurrency: Infinity
    });
};
<|endoftext|>


<commit_before>'use strict';

let log = require('intel'),
  Cookie = require('tough-cookie').Cookie,
  urlParser = require('url'),
  util = require('util'),
  moment = require('moment');

module.exports = {
  eventFromLogEntry: function(logEntry) {
    const logContent = JSON.parse(logEntry.message);

    return {
      timestamp: logEntry.timestamp,
      method: logContent.message.method,
      data: logContent.message.params,
      webview: logContent.webview
    }
  },
  harFromEvents: function(events) {
    let pages = [],
      entries = [],
      currentPageId,
      ongoingDataRequests = new Set();

    for (let event of events) {
      const data = event.data;
      switch (event.method) {
        case 'Page.frameStartedLoading':
        {
          currentPageId = 'page_' + (pages.length + 1);
          let page = {
            id: currentPageId,
            startedDateTime: moment(event.timestamp).toISOString(),
            title: null,
            pageTimings: {}
          };
          pages.push(page);
        }
          break;

        case 'Network.requestWillBeSent':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.request.url.startsWith('data:')) {
            ongoingDataRequests.add(data.requestId);
            continue;
          }

          const cookieHeader = getHeaderValue(data.request.headers, 'Cookie');
          const cookies = parseCookies(cookieHeader.split(';'));

          let req = {
            method: data.request.method,
            url: data.request.url,
            _timestamp: data.timestamp,
            queryString: parsePostParams(data.request.url),
            headersSize: -1,
            bodySize: -1,
            cookies: cookies,
            headers: parseHeaders(data.request.headers)
          };

          let entry = {
            cache: {},
            startedDateTime: moment(data.wallTime * 1000).toISOString(), //epoch float64, eg 1440589909.59248
            _requestId: data.requestId,
            pageref: currentPageId,
            request: req,
            time: 0
          };

          if (data.redirectResponse) {
            let previousEntry = entries.find((entry) => {
              return entry._requestId === data.requestId;
            });
            previousEntry._requestId = previousEntry._requestId + 'r';
            populateEntryFromResponse(previousEntry, data.redirectResponse, data.timestamp);
            previousEntry.response.redirectURL = data.request.url;
          }

          entries.push(entry);

          let page = pages[pages.length - 1];
          // this is the first request for this page, so set timestamp of page.
          if (!page._timestamp) {
            page._timestamp = data.timestamp;
            page.startedDateTime = moment(data.wallTime * 1000).toISOString();
          }
        }
          break;

        case 'Network.responseReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.response.url.startsWith('data:')) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network response for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          let page = pages.find((page) => {
            return page.id === currentPageId;
          });
          if (!page.title) {
            // URL is better than blank, and it's what devtools uses.
            page.title = data.response.url;
          }

          try {
            populateEntryFromResponse(entry, data.response, data.timestamp);
          } catch (e) {
            log.error('Error parsing response: ' + JSON.stringify(data, null, 2));
            throw e;
          }
        }
          break;

        case 'Network.dataReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network data for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.content.size += data.dataLength;
        }
          break;

        case 'Network.loadingFinished':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            ongoingDataRequests.delete(data.requestId);
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Network loading finished for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.bodySize = entry.response.content.size;
          //if (entry.response.headersSize > -1) {
          //  entry.response.bodySize -= entry.response.headersSize;
          //}

          // encodedDataLength will be -1 sometimes
          if (data.encodedDataLength > 0) {
            // encodedDataLength seems to be larger than body size sometimes. Perhaps it's because full packets are
            // listed even though the actual data might be very small.
            // I've seen dataLength: 416, encodedDataLength: 1016,

            const compression = Math.max(0, entry.response.bodySize - data.encodedDataLength);
            if (compression > 0) {
              entry.response.content.compression = compression;
            }
          }
          entry.time = (data.timestamp - entry.request._timestamp) * 1000;
          //entry.timings.receive = (entry.timings.receive + data.timestamp*1000);
        }
          break;

        case 'Page.loadEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.domContentEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onContentLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.frameAttached':
          // FIXME should check for attachments to the main frame (e.g. fonts on izettle.com)
          // and not create a new page for those.
          break;

        case 'Page.frameScheduledNavigation':
        case 'Page.frameNavigated':
        case 'Page.frameStoppedLoading':
        case 'Page.frameClearedScheduledNavigation':
          // ignore
          break;

        case 'Network.requestServedFromCache':
        case 'Network.loadingFailed':
          // ignore
          break;

        default:
          log.warn('Unhandled event: ' + event.method);
          break;
      }
    }

    entries = entries.filter((entry) => {
      // Page doesn't wait for favicon to load, and that's ok (for now).
      if (!entry.response && !entry.request.url.endsWith('.ico')) {
        log.warn('Entry without response!: ' + JSON.stringify(entry));
      }
      return entry.response;
    });

    return {
      log: {
        version: "1.2",
        creator: {"name": "Browsertime", "version": "1.0"},
        pages,
        entries
      }
    }
  }
};

function calculateRequestHeaderSize(request) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %s %s\r\n', request.method, request.url, request.httpVersion));
  Object.keys(request.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, request.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function calculateResponseHeaderSize(response) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %d %s\r\n', response.protocol, response.status, response.statusText));
  Object.keys(response.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, response.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function populateEntryFromResponse(entry, response, timestamp) {
  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0
    },
    headersSize: -1,
    bodySize: -1,
    cookies: [],
    headers: parseHeaders(response.headers)
  };

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true) {
    entry.response.headersSize = calculateResponseHeaderSize(response);
  } else {
    if (response.headersText) {
      entry.response.headersSize = response.headersText.length;
    } else {
      entry.response.headersSize = calculateResponseHeaderSize(response);
    }

    entry.request.headers = parseHeaders(response.requestHeaders);

    if (response.requestHeadersText) {
      entry.request.headersSize = response.requestHeadersText.length;
    } else {
      entry.request.headersSize = calculateRequestHeaderSize(entry.request);
    }
    if (response.requestHeaders) {
      const cookieHeader = getHeaderValue(response.requestHeaders, 'Cookie');
      entry.request.cookies = parseCookies(cookieHeader.split(';'));
    }
  }

  let blocked = response.timing["dnsStart"];
  if (blocked < 0.0) {
    blocked = 0.0
  }
  let dns = response.timing["dnsEnd"] - response.timing["dnsStart"];
  if (dns < 0.0) {
    dns = 0.0
  }
  let connect = response.timing["connectEnd"] - response.timing["connectStart"];
  if (connect < 0.0) {
    connect = 0.0
  }
  let send = response.timing["sendEnd"] - response.timing["sendStart"];
  if (send < 0.0) {
    send = 0.0
  }
  let wait = response.timing["receiveHeadersEnd"] - response.timing["sendEnd"];
  if (wait < 0.0) {
    wait = 0.0
  }
  let ssl = response.timing["sslEnd"] - response.timing["sslStart"];
  if (ssl < 0.0) {
    ssl = 0.0
  }

  entry.timings = {
    blocked,
    dns,
    connect,
    send,
    wait,
    receive: timestamp - response.timing["requestTime"],
    ssl
  };

  entry.time = (timestamp - response.timing.requestTime) * 1000;

}

function parseCookies(cookieStrings) {
  return cookieStrings.filter(Boolean).map((cookieString) => {
    let cookie = Cookie.parse(cookieString);
    return {
      'name': cookie.key,
      'value': cookie.value,
      'path': cookie.path || undefined, // must be undefined, not null, to exclude empty path
      'domain': cookie.domain || undefined,  // must be undefined, not null, to exclude empty domain
      'expires': cookie.expires === 'Infinity' ? undefined : moment(cookie.expires).toISOString(),
      'httpOnly': cookie.httpOnly,
      'secure': cookie.secure
    };
  });
}

function parseHeaders(headers) {
  if (!headers) {
    return [];
  }
  return Object.keys(headers).map((key) => {
    return {
      name: key,
      value: headers[key]
    }
  });
}

function getHeaderValue(headers, header) {
  if (!headers) {
    return '';
  }
  // http header names are case insensitive
  const lowerCaseHeader = header.toLowerCase();
  const headerNames = Object.keys(headers);
  return headerNames.filter((key) => key.toLowerCase() === lowerCaseHeader)
    .map((key) => headers[key]).shift() || '';
}

function parsePostParams(text) {
  var paramList = [];
  var queryString = urlParser.parse(text, true).query;
  Object.keys(queryString).forEach(function(name) {
    var value = queryString[name];
    if (!Array.isArray(value))
      value = [value];
    value.forEach(function(v) {
      paramList.push(
        {
          "name": name,
          "value": v
          //                    "fileName": "example.pdf",
          //                    "contentType": "application/pdf",
        });
    });
  });
  return paramList;
}

<commit_msg>Extract and rename variables for readability.<commit_after>'use strict';

let log = require('intel'),
  Cookie = require('tough-cookie').Cookie,
  urlParser = require('url'),
  util = require('util'),
  moment = require('moment');

module.exports = {
  eventFromLogEntry: function(logEntry) {
    const logContent = JSON.parse(logEntry.message);

    return {
      timestamp: logEntry.timestamp,
      method: logContent.message.method,
      data: logContent.message.params,
      webview: logContent.webview
    }
  },
  harFromEvents: function(events) {
    let pages = [],
      entries = [],
      currentPageId,
      ongoingDataRequests = new Set();

    for (let event of events) {
      const data = event.data;
      switch (event.method) {
        case 'Page.frameStartedLoading':
        {
          currentPageId = 'page_' + (pages.length + 1);
          let page = {
            id: currentPageId,
            startedDateTime: moment(event.timestamp).toISOString(),
            title: null,
            pageTimings: {}
          };
          pages.push(page);
        }
          break;

        case 'Network.requestWillBeSent':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          const request = data.request;
          if (request.url.startsWith('data:')) {
            ongoingDataRequests.add(data.requestId);
            continue;
          }

          const cookieHeader = getHeaderValue(request.headers, 'Cookie');
          const cookies = parseCookies(cookieHeader.split(';'));

          let req = {
            method: request.method,
            url: request.url,
            _timestamp: data.timestamp,
            queryString: parsePostParams(request.url),
            headersSize: -1,
            bodySize: -1,
            cookies: cookies,
            headers: parseHeaders(request.headers)
          };

          let entry = {
            cache: {},
            startedDateTime: moment(data.wallTime * 1000).toISOString(), //epoch float64, eg 1440589909.59248
            _requestId: data.requestId,
            pageref: currentPageId,
            request: req,
            time: 0
          };

          if (data.redirectResponse) {
            let previousEntry = entries.find((entry) => {
              return entry._requestId === data.requestId;
            });
            previousEntry._requestId = previousEntry._requestId + 'r';
            populateEntryFromResponse(previousEntry, data.redirectResponse, data.timestamp);
            previousEntry.response.redirectURL = request.url;
          }

          entries.push(entry);

          let page = pages[pages.length - 1];
          // this is the first request for this page, so set timestamp of page.
          if (!page._timestamp) {
            page._timestamp = data.timestamp;
            page.startedDateTime = moment(data.wallTime * 1000).toISOString();
          }
        }
          break;

        case 'Network.responseReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.response.url.startsWith('data:')) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network response for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          let page = pages.find((page) => {
            return page.id === currentPageId;
          });
          if (!page.title) {
            // URL is better than blank, and it's what devtools uses.
            page.title = data.response.url;
          }

          try {
            populateEntryFromResponse(entry, data.response, data.timestamp);
          } catch (e) {
            log.error('Error parsing response: ' + JSON.stringify(data, null, 2));
            throw e;
          }
        }
          break;

        case 'Network.dataReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network data for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.content.size += data.dataLength;
        }
          break;

        case 'Network.loadingFinished':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            ongoingDataRequests.delete(data.requestId);
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Network loading finished for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.bodySize = entry.response.content.size;
          //if (entry.response.headersSize > -1) {
          //  entry.response.bodySize -= entry.response.headersSize;
          //}

          // encodedDataLength will be -1 sometimes
          if (data.encodedDataLength > 0) {
            // encodedDataLength seems to be larger than body size sometimes. Perhaps it's because full packets are
            // listed even though the actual data might be very small.
            // I've seen dataLength: 416, encodedDataLength: 1016,

            const compression = Math.max(0, entry.response.bodySize - data.encodedDataLength);
            if (compression > 0) {
              entry.response.content.compression = compression;
            }
          }
          entry.time = (data.timestamp - entry.request._timestamp) * 1000;
          //entry.timings.receive = (entry.timings.receive + data.timestamp*1000);
        }
          break;

        case 'Page.loadEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.domContentEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onContentLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.frameAttached':
          // FIXME should check for attachments to the main frame (e.g. fonts on izettle.com)
          // and not create a new page for those.
          break;

        case 'Page.frameScheduledNavigation':
        case 'Page.frameNavigated':
        case 'Page.frameStoppedLoading':
        case 'Page.frameClearedScheduledNavigation':
          // ignore
          break;

        case 'Network.requestServedFromCache':
        case 'Network.loadingFailed':
          // ignore
          break;

        default:
          log.warn('Unhandled event: ' + event.method);
          break;
      }
    }

    entries = entries.filter((entry) => {
      // Page doesn't wait for favicon to load, and that's ok (for now).
      if (!entry.response && !entry.request.url.endsWith('.ico')) {
        log.warn('Entry without response!: ' + JSON.stringify(entry));
      }
      return entry.response;
    });

    return {
      log: {
        version: "1.2",
        creator: {"name": "Browsertime", "version": "1.0"},
        pages,
        entries
      }
    }
  }
};

function calculateRequestHeaderSize(request) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %s %s\r\n', request.method, request.url, request.httpVersion));
  Object.keys(request.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, request.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function calculateResponseHeaderSize(perflogResponse) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %d %s\r\n', perflogResponse.protocol, perflogResponse.status, perflogResponse.statusText));
  Object.keys(perflogResponse.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, perflogResponse.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function populateEntryFromResponse(entry, response, timestamp) {
  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0
    },
    headersSize: -1,
    bodySize: -1,
    cookies: [],
    headers: parseHeaders(response.headers)
  };

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true) {
    entry.response.headersSize = calculateResponseHeaderSize(response);
  } else {
    if (response.headersText) {
      entry.response.headersSize = response.headersText.length;
    } else {
      entry.response.headersSize = calculateResponseHeaderSize(response);
    }

    entry.request.headers = parseHeaders(response.requestHeaders);

    if (response.requestHeadersText) {
      entry.request.headersSize = response.requestHeadersText.length;
    } else {
      entry.request.headersSize = calculateRequestHeaderSize(entry.request);
    }
    if (response.requestHeaders) {
      const cookieHeader = getHeaderValue(response.requestHeaders, 'Cookie');
      entry.request.cookies = parseCookies(cookieHeader.split(';'));
    }
  }

  let blocked = response.timing["dnsStart"];
  if (blocked < 0.0) {
    blocked = 0.0
  }
  let dns = response.timing["dnsEnd"] - response.timing["dnsStart"];
  if (dns < 0.0) {
    dns = 0.0
  }
  let connect = response.timing["connectEnd"] - response.timing["connectStart"];
  if (connect < 0.0) {
    connect = 0.0
  }
  let send = response.timing["sendEnd"] - response.timing["sendStart"];
  if (send < 0.0) {
    send = 0.0
  }
  let wait = response.timing["receiveHeadersEnd"] - response.timing["sendEnd"];
  if (wait < 0.0) {
    wait = 0.0
  }
  let ssl = response.timing["sslEnd"] - response.timing["sslStart"];
  if (ssl < 0.0) {
    ssl = 0.0
  }

  entry.timings = {
    blocked,
    dns,
    connect,
    send,
    wait,
    receive: timestamp - response.timing["requestTime"],
    ssl
  };

  entry.time = (timestamp - response.timing.requestTime) * 1000;

}

function parseCookies(cookieStrings) {
  return cookieStrings.filter(Boolean).map((cookieString) => {
    let cookie = Cookie.parse(cookieString);
    return {
      'name': cookie.key,
      'value': cookie.value,
      'path': cookie.path || undefined, // must be undefined, not null, to exclude empty path
      'domain': cookie.domain || undefined,  // must be undefined, not null, to exclude empty domain
      'expires': cookie.expires === 'Infinity' ? undefined : moment(cookie.expires).toISOString(),
      'httpOnly': cookie.httpOnly,
      'secure': cookie.secure
    };
  });
}

function parseHeaders(headers) {
  if (!headers) {
    return [];
  }
  return Object.keys(headers).map((key) => {
    return {
      name: key,
      value: headers[key]
    }
  });
}

function getHeaderValue(headers, header) {
  if (!headers) {
    return '';
  }
  // http header names are case insensitive
  const lowerCaseHeader = header.toLowerCase();
  const headerNames = Object.keys(headers);
  return headerNames.filter((key) => key.toLowerCase() === lowerCaseHeader)
    .map((key) => headers[key]).shift() || '';
}

function parsePostParams(text) {
  var paramList = [];
  var queryString = urlParser.parse(text, true).query;
  Object.keys(queryString).forEach(function(name) {
    var value = queryString[name];
    if (!Array.isArray(value))
      value = [value];
    value.forEach(function(v) {
      paramList.push(
        {
          "name": name,
          "value": v
          //                    "fileName": "example.pdf",
          //                    "contentType": "application/pdf",
        });
    });
  });
  return paramList;
}

<|endoftext|>


<commit_before>import Widget from "./base_widget.js";
import { generate_route } from "./utils";

export default class ShortcutWidget extends Widget {
	constructor(opts) {
		super(opts);
	}

	get_config() {
		return {
			name: this.name,
			icon: this.icon,
			label: this.label,
			format: this.format,
			link_to: this.link_to,
			color: this.color,
			restrict_to_domain: this.restrict_to_domain,
			stats_filter: this.stats_filter,
			type: this.type,
		};
	}

	setup_events() {
		this.widget.click(() => {
			if (this.in_customize_mode) return;

			let route = generate_route({
				route: this.route,
				name: this.link_to,
				type: this.type,
			});

			frappe.set_route(route);
		});
	}

	set_actions() {
		if (this.in_customize_mode) return;

		this.widget.addClass("shortcut-widget-box");
		const get_filter = new Function(`return ${this.stats_filter}`);
		if (this.type == "DocType" && this.stats_filter) {
			frappe.db
				.count(this.link_to, {
					filters: get_filter(),
				})
				.then((count) => this.set_count(count));
		}
	}

	set_title() {
		if (this.icon) {
			this.title_field[0].innerHTML = `

				${this.label || this.name}

`;
		} else {
			super.set_title();
		}
	}

	set_count(count) {
		const get_label = () => {
			if (this.format) {
				return this.format.replace(/{}/g, count);
			}
			return count;
		};

		this.action_area.empty();
		const label = get_label();
		const buttons = $(`<div class="small pill">${label}</div>`);
		if (this.color) {
			buttons.css("background-color", this.color);
			buttons.css(
				"color",
				frappe.ui.color.get_contrast_color(this.color)
			);
		}

		buttons.appendTo(this.action_area);
	}
}
<commit_msg>fix: send is_query_report label to generating route<commit_after>import Widget from "./base_widget.js";
import { generate_route } from "./utils";

export default class ShortcutWidget extends Widget {
	constructor(opts) {
		super(opts);
	}

	get_config() {
		return {
			name: this.name,
			icon: this.icon,
			label: this.label,
			format: this.format,
			link_to: this.link_to,
			color: this.color,
			restrict_to_domain: this.restrict_to_domain,
			stats_filter: this.stats_filter,
			type: this.type,
		};
	}

	setup_events() {
		this.widget.click(() => {
			if (this.in_customize_mode) return;

			let route = generate_route({
				route: this.route,
				name: this.link_to,
				type: this.type,
				is_query_report: this.is_query_report,
			});

			frappe.set_route(route);
		});
	}

	set_actions() {
		if (this.in_customize_mode) return;

		this.widget.addClass("shortcut-widget-box");
		const get_filter = new Function(`return ${this.stats_filter}`);
		if (this.type == "DocType" && this.stats_filter) {
			frappe.db
				.count(this.link_to, {
					filters: get_filter(),
				})
				.then((count) => this.set_count(count));
		}
	}

	set_title() {
		if (this.icon) {
			this.title_field[0].innerHTML = `

				${this.label || this.name}

`;
		} else {
			super.set_title();
		}
	}

	set_count(count) {
		const get_label = () => {
			if (this.format) {
				return this.format.replace(/{}/g, count);
			}
			return count;
		};

		this.action_area.empty();
		const label = get_label();
		const buttons = $(`<div class="small pill">${label}</div>`);
		if (this.color) {
			buttons.css("background-color", this.color);
			buttons.css(
				"color",
				frappe.ui.color.get_contrast_color(this.color)
			);
		}

		buttons.appendTo(this.action_area);
	}
}
<|endoftext|>


<commit_before>import * as csp from 'plexus-csp';

import * as webworkers from '../common/webworkers';
import * as version from '../version';
import parseDSymbols from '../io/ds';

import { structures } from './builtinStructures';
import makeScene from './makeScene';

import { MainMenu } from '../elm/MainMenu';

const worker = webworkers.create('js/sceneWorker.js');
const callWorker = csp.nbind(worker, null);

const fileLoader = (accept, multiple=false, binary=false) => {
  const input = document.createElement('input');
  let callback = () => {};

  input.type = 'file';
  input.accept = accept;
  input.multiple = multiple;

  input.addEventListener('change', event => {
    const files = event.target.files;

    for (let i = 0; i < files.length; ++i) {
      const file = files[i];
      const reader = new FileReader();

      reader.onload = event => callback(file, event.target.result);

      if (binary)
        reader.readAsDataURL(file);
      else
        reader.readAsText(file);
    }
  });

  return (onData) => {
    callback = onData;
    input.click();
  };
};

const fileSaver = () => {
  const link = document.createElement('a');

  link.style.display = 'none';
  document.body.appendChild(link);

  return (blob, filename) => {
    link.download = filename;
    link.href = URL.createObjectURL(blob);
    link.click();
  }
};

const initialModel = {
  options: {},
  filename: null,
  structures,
  index: null,
  scene: null
};

const title = model => {
  if (model.structures && model.index != null) {
    const fname = model.filename;
    const index = model.index + 1;
    const len = model.structures.length;
    const name = model.structures[model.index].name;
    const prefix = fname ? `File "${fname}" ` : 'Builtin structure ';
    const postfix = name ? `: ${name}` : '';
    return `${prefix}#${index} (of ${len})${postfix}`;
  }
  else if (model.filename)
    return `File "${model.filename}"...`;
};

const toStructure = (config, model, i) => csp.go(function*() {
  try {
    const structures = model.structures;
    const n = structures.length;
    const index = i < 0 ? n + i % n : i % n;

    if (structures[index].isRaw) {
      config.log('Converting structure data...');
      structures[index] = yield callWorker({
        cmd: 'processCGD',
        val: structures[index]
      });
    }

    const scene = yield makeScene(
      structures[index], model.options, callWorker, config.log);
    const newModel = Object.assign({}, model, { structures, index, scene });

    config.sendTitle(title(newModel));
    config.sendScene(scene);

    return newModel;
  } catch (ex) {
    config.log(`ERROR processing structure ${i}!!!`);
    console.error(ex);
    return model;
  }
});

const setOptions = (model, options) => {
  const newOptions = Object.assign({}, model.options, options);
  return Object.assign({}, model, { options: newOptions });
};

const findStructureByName = (model, regexText) => {
  const pattern = new RegExp(`\\b${regexText}\\b`, 'i');
  return model.structures.findIndex(s => !!pattern.exec(s.name));
};

const currentIndex = model => model.index;
const currentStructure = model => model.structures[model.index];
const currentScene = model => model.scene;
const currentOptions = model => model.options;

const newFile = (config, model, file, data) => csp.go(function*() {
    try {
        const filename = file.name;
        let structures = [];

        if (filename.match(/\.(ds|tgs)$/)) {
            config.log('Parsing .ds data...');
            structures = Array.from(parseDSymbols(data));
        }
        else if (filename.match(/\.(cgd|pgr)$/)) {
            config.log('Parsing .cgd data...');
            structures = yield callWorker({ cmd: 'parseCGD', val: data });
        }

        const newModel =
              Object.assign({}, model, { filename, structures, index: null });

        config.sendTitle(title(newModel));
        return yield toStructure(config, newModel, 0);
    } catch (ex) {
        config.log(`ERROR loading from file "${file.name}"!!!`);
        console.error(ex);
    }
});

const saveStructure = (config, model) => {
  const structure = currentStructure(model);

  if (structure.type == 'tiling') {
    const text = structure.symbol.toString();
    const blob = new Blob([text], { type: 'text/plain' });
    config.saveFile(blob, 'gavrog.ds');
  }
  else
    throw new Error(`save not yet implemented for '${structure.type}'`);
};

const saveScreenshot = (config, model) => {
  const canvas = document.getElementById('main-3d-canvas');

  if (canvas) {
    config.sendCommand('redrawsOn');

    window.requestAnimationFrame(() => {
      if (canvas.toBlob)
        canvas.toBlob(blob => config.saveFile(blob, 'gavrog.png'));
      else {
        const binStr = atob(canvas.toDataURL().split(',')[1]);

        const len = binStr.length;
        const arr = new Uint8Array(len);
        for (let i = 0; i < len; i++ )
          arr[i] = binStr.charCodeAt(i);

        const blob = new Blob([arr], { type: 'image/png' });
        config.saveFile(blob, 'gavrog.png');
      }

      config.sendCommand('redrawsOff');
    });
  }
  else
    config.log('ERROR: could not save screenshot - no canvas element found');
};

class App {
  setStructure(i) {
    csp.go(function*() {
      this.model = yield toStructure(this.config, this.model, i);
    }.bind(this));
  }

  previousStructure() {
    this.setStructure(currentIndex(this.model) - 1);
  }

  nextStructure() {
    this.setStructure(currentIndex(this.model) + 1);
  }

  handleFileData(file, data) {
      csp.go(function*() {
          this.model = yield newFile(this.config, this.model, file, data);
      }.bind(this));
  }

  saveStructure() {
    saveStructure(this.config, this.model);
  }

  saveScreenshot() {
    saveScreenshot(this.config, this.model);
  }

  render(domNode) {
    const action = {
      ['Open...']: () => this.config.loadFile(this.handleFileData.bind(this)),
      ['Save Structure...']: () => this.saveStructure(),
      ['Save Screenshot...']: () => this.saveScreenshot(),
      ['First']: () => this.setStructure(0),
      ['Prev']: () => this.previousStructure(),
      ['Next']: () => this.nextStructure(),
      ['Last']: () => this.setStructure(-1)
    };

    const handleElmData = ({ mode, text, options: data }) => {
      if (mode == "jump") {
        const number = parseInt(text);
        if (!Number.isNaN(number))
          this.setStructure(number - (number > 0));
      }
      else if (mode == "search") {
        const i = text ? findStructureByName(this.model, text) : -1;
        if (i >= 0)
          this.setStructure(i);
        else
          this.config.log(`Name "${text}" not found.`);
      }
      else if (mode == "selected") {
        if (action[text])
          action[text]()
      }
      else if (mode == "options") {
        const options = {};
        for (const { key, value } of data)
          options[key] = value;

        this.model = setOptions(this.model, options);
        this.setStructure(currentIndex(this.model));
      }
    };

    const handleKeyPress = code => {
      const key = String.fromCharCode(code).toLowerCase();
      if (key == 'p')
        this.previousStructure();
      else if (key == 'n')
        this.nextStructure();
    };

    const app = MainMenu.embed(domNode, {
      revision: version.gitRev,
      timestamp: version.gitDate });

    this.config = {
      loadFile: fileLoader(),
      saveFile: fileSaver(),
      log: app.ports.log.send,
      sendTitle: app.ports.titles.send,
      sendScene: app.ports.scenes.send,
      sendCommand: app.ports.commands.send
    };

    app.ports.toJS.subscribe(handleElmData);
    app.ports.keyPresses.subscribe(handleKeyPress);

    this.model = initialModel;
    this.setStructure(0);
  }
}

const app = new App();
app.render(document.getElementById('main'));
<commit_msg>change file loader callback to take a single argument<commit_after>import * as csp from 'plexus-csp';

import * as webworkers from '../common/webworkers';
import * as version from '../version';
import parseDSymbols from '../io/ds';

import { structures } from './builtinStructures';
import makeScene from './makeScene';

import { MainMenu } from '../elm/MainMenu';

const worker = webworkers.create('js/sceneWorker.js');
const callWorker = csp.nbind(worker, null);

const fileLoader = (accept, multiple=false, binary=false) => {
  const input = document.createElement('input');
  let callback = () => {};

  input.type = 'file';
  input.accept = accept;
  input.multiple = multiple;

  input.addEventListener('change', event => {
    const files = event.target.files;

    for (let i = 0; i < files.length; ++i) {
      const file = files[i];
      const reader = new FileReader();

      reader.onload = event => callback({ file, data: event.target.result });

      if (binary)
        reader.readAsDataURL(file);
      else
        reader.readAsText(file);
    }
  });

  return (onData) => {
    callback = onData;
    input.click();
  };
};

const fileSaver = () => {
  const link = document.createElement('a');

  link.style.display = 'none';
  document.body.appendChild(link);

  return (blob, filename) => {
    link.download = filename;
    link.href = URL.createObjectURL(blob);
    link.click();
  }
};

const initialModel = {
  options: {},
  filename: null,
  structures,
  index: null,
  scene: null
};

const title = model => {
  if (model.structures && model.index != null) {
    const fname = model.filename;
    const index = model.index + 1;
    const len = model.structures.length;
    const name = model.structures[model.index].name;
    const prefix = fname ? `File "${fname}" ` : 'Builtin structure ';
    const postfix = name ? `: ${name}` : '';
    return `${prefix}#${index} (of ${len})${postfix}`;
  }
  else if (model.filename)
    return `File "${model.filename}"...`;
};

const toStructure = (config, model, i) => csp.go(function*() {
  try {
    const structures = model.structures;
    const n = structures.length;
    const index = i < 0 ? n + i % n : i % n;

    if (structures[index].isRaw) {
      config.log('Converting structure data...');
      structures[index] = yield callWorker({
        cmd: 'processCGD',
        val: structures[index]
      });
    }

    const scene = yield makeScene(
      structures[index], model.options, callWorker, config.log);
    const newModel = Object.assign({}, model, { structures, index, scene });

    config.sendTitle(title(newModel));
    config.sendScene(scene);

    return newModel;
  } catch (ex) {
    config.log(`ERROR processing structure ${i}!!!`);
    console.error(ex);
    return model;
  }
});

const setOptions = (model, options) => {
  const newOptions = Object.assign({}, model.options, options);
  return Object.assign({}, model, { options: newOptions });
};

const findStructureByName = (model, regexText) => {
  const pattern = new RegExp(`\\b${regexText}\\b`, 'i');
  return model.structures.findIndex(s => !!pattern.exec(s.name));
};

const currentIndex = model => model.index;
const currentStructure = model => model.structures[model.index];
const currentScene = model => model.scene;
const currentOptions = model => model.options;

const newFile = (config, model, { file, data }) => csp.go(function*() {
    try {
        const filename = file.name;
        let structures = [];

        if (filename.match(/\.(ds|tgs)$/)) {
            config.log('Parsing .ds data...');
            structures = Array.from(parseDSymbols(data));
        }
        else if (filename.match(/\.(cgd|pgr)$/)) {
            config.log('Parsing .cgd data...');
            structures = yield callWorker({ cmd: 'parseCGD', val: data });
        }

        const newModel =
              Object.assign({}, model, { filename, structures, index: null });

        config.sendTitle(title(newModel));
        return yield toStructure(config, newModel, 0);
    } catch (ex) {
        config.log(`ERROR loading from file "${file.name}"!!!`);
        console.error(ex);
    }
});

const saveStructure = (config, model) => {
  const structure = currentStructure(model);

  if (structure.type == 'tiling') {
    const text = structure.symbol.toString();
    const blob = new Blob([text], { type: 'text/plain' });
    config.saveFile(blob, 'gavrog.ds');
  }
  else
    throw new Error(`save not yet implemented for '${structure.type}'`);
};

const saveScreenshot = (config, model) => {
  const canvas = document.getElementById('main-3d-canvas');

  if (canvas) {
    config.sendCommand('redrawsOn');

    window.requestAnimationFrame(() => {
      if (canvas.toBlob)
        canvas.toBlob(blob => config.saveFile(blob, 'gavrog.png'));
      else {
        const binStr = atob(canvas.toDataURL().split(',')[1]);

        const len = binStr.length;
        const arr = new Uint8Array(len);
        for (let i = 0; i < len; i++ )
          arr[i] = binStr.charCodeAt(i);

        const blob = new Blob([arr], { type: 'image/png' });
        config.saveFile(blob, 'gavrog.png');
      }

      config.sendCommand('redrawsOff');
    });
  }
  else
    config.log('ERROR: could not save screenshot - no canvas element found');
};

class App {
  setStructure(i) {
    csp.go(function*() {
      this.model = yield toStructure(this.config, this.model, i);
    }.bind(this));
  }

  previousStructure() {
    this.setStructure(currentIndex(this.model) - 1);
  }

  nextStructure() {
    this.setStructure(currentIndex(this.model) + 1);
  }

  handleFileData({ file, data }) {
      csp.go(function*() {
          this.model = yield newFile(this.config, this.model, { file, data });
      }.bind(this));
  }

  saveStructure() {
    saveStructure(this.config, this.model);
  }

  saveScreenshot() {
    saveScreenshot(this.config, this.model);
  }

  render(domNode) {
    const action = {
      ['Open...']: () => this.config.loadFile(this.handleFileData.bind(this)),
      ['Save Structure...']: () => this.saveStructure(),
      ['Save Screenshot...']: () => this.saveScreenshot(),
      ['First']: () => this.setStructure(0),
      ['Prev']: () => this.previousStructure(),
      ['Next']: () => this.nextStructure(),
      ['Last']: () => this.setStructure(-1)
    };

    const handleElmData = ({ mode, text, options: data }) => {
      if (mode == "jump") {
        const number = parseInt(text);
        if (!Number.isNaN(number))
          this.setStructure(number - (number > 0));
      }
      else if (mode == "search") {
        const i = text ? findStructureByName(this.model, text) : -1;
        if (i >= 0)
          this.setStructure(i);
        else
          this.config.log(`Name "${text}" not found.`);
      }
      else if (mode == "selected") {
        if (action[text])
          action[text]()
      }
      else if (mode == "options") {
        const options = {};
        for (const { key, value } of data)
          options[key] = value;

        this.model = setOptions(this.model, options);
        this.setStructure(currentIndex(this.model));
      }
    };

    const handleKeyPress = code => {
      const key = String.fromCharCode(code).toLowerCase();
      if (key == 'p')
        this.previousStructure();
      else if (key == 'n')
        this.nextStructure();
    };

    const app = MainMenu.embed(domNode, {
      revision: version.gitRev,
      timestamp: version.gitDate });

    this.config = {
      loadFile: fileLoader(),
      saveFile: fileSaver(),
      log: app.ports.log.send,
      sendTitle: app.ports.titles.send,
      sendScene: app.ports.scenes.send,
      sendCommand: app.ports.commands.send
    };

    app.ports.toJS.subscribe(handleElmData);
    app.ports.keyPresses.subscribe(handleKeyPress);

    this.model = initialModel;
    this.setStructure(0);
  }
}

const app = new App();
app.render(document.getElementById('main'));
<|endoftext|>


<commit_before>/* jshint undef: true, strict:false, trailing:false, unused:false, -W110 */
/* global $,_,document,window,console,escape,Backbone,exports,WebSocket,process,_NODE_AJAX,angular,jQuery */
///  @title indx-offline.js
///  @author Max Van Kleek
///  @since 2014
///  @see http://github.com/sociam/indx
///  @see http://indx.es
///
///  Javascript ORM client for INDX that makes it easy to
///  read and write objects from one or more INDX data store(s).

///  #### Copying
///  Copyright (C) 2011-2013 University of Southampton
///  Copyright (C) 2011-2013 Max Van Kleek
///
///  This program is free software: you can redistribute it and/or modify
///  it under the terms of the GNU Affero General Public License, version 3,
///  as published by the Free Software Foundation.
///
///  This program is distributed in the hope that it will be useful,
///  but WITHOUT ANY WARRANTY; without even the implied warranty of
///  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
///  GNU Affero General Public License for more details.
///
///  You should have received a copy of the GNU Affero General Public License
///  along with this program.  If not, see <http://www.gnu.org/licenses/>.
///

angular
	.module('indx')
	.factory('local',function(utils) {

		// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)
		var u = utils, log = utils.log, error = utils.error, debug = utils.debug, jQ = jQuery;

		function deleteDB(dbid) {
		    try {

		        var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

		        var dbreq = indexedDB.deleteDatabase(dbid);
		        dbreq.onsuccess = function (event) {
		            var db = event.result;
		            console.log("indexedDB: " + dbid + " deleted");
		        }
		        dbreq.onerror = function (event) {
		            console.error("indexedDB.delete Error: " + event.message);
		        }
		    }
		    catch (e) {
		        console.error("Error: " + e.message);
		        //prefer change id of database to start ont new instance
		        dbid = dbid + "." + fallBackDBGuid;
		        console.log("fallback to new database name :" + dbid)
		    }
		}

		var Store = Backbone.Model.extend({
			getBox:function(bid) {
				u.assert(bid !== undefined, "box id has to be defined");

				var db = {
			 		id:bid,
			 		description:'indx local store',
				 	migrations: [
				 		{
				 			version:1,
				 			migrate:function(transaction, next){
				 				console.log('migrate >> ', transaction);
				 				var store = transaction.db.createObjectStore(bid);
				 				next();
				 			}
				 		}
				 	]
				 };

			 	var DBModel = Backbone.Model.extend({
		 			database: db,
			 		storeName:bid
		 		}),
			 	Collection = Backbone.Collection.extend({
			 		database:db,
			 		storeName:bid,
			 		model: DBModel
			 	}),
			 	d = u.deferred(),
			 	c = new Collection();

			 	c.fetch().then(function() { d.resolve(c); }).fail(d.reject);

			 	return d.promise();
			}
		});

		return {
			getStore : function() {
				return new Store();
			},
			test : function() {
				var s = this.getStore();
				s.getBox('a').then(function(box) {
					console.log('box');
					window.box = box;
					console.log('box has ', box.size());
					var ds = [];
					u.range(10).map(function(x) {
						var uid = u.guid(10);
						// var result = box.create({id:uid, test:u.guid(5), message:'hello'});
						var obj = new box.model({id:uid, test:u.guid(5), message:'hello'});
						ds.push(obj.save());
					});
					u.when(ds)
						.then(function(x) { console.log('all done!'); })
						.fail(function(x) { console.error(e); });
				}).fail(function(err) {
					console.error('couldnt get box ');
				});
			},
			deleteDB:deleteDB
		};

	});
	<commit_msg>indexeddb progress<commit_after>/* jshint undef: true, strict:false, trailing:false, unused:false, -W110 */
/* global $,_,document,window,console,escape,Backbone,exports,WebSocket,process,_NODE_AJAX,angular,jQuery */

///  @title indx-offline.js
///  @author Max Van Kleek
///  @since 2014
///  @see http://github.com/sociam/indx
///  @see http://indx.es
///
///  Javascript ORM client for INDX that makes it easy to
///  read and write objects from one or more INDX data store(s).

///  #### Copying
///  Copyright (C) 2011-2014 Max Van Kleek
///
///  This program is free software: you can redistribute it and/or modify
///  it under the terms of the GNU Affero General Public License, version 3,
///  as published by the Free Software Foundation.
///
///  This program is distributed in the hope that it will be useful,
///  but WITHOUT ANY WARRANTY; without even the implied warranty of
///  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
///  GNU Affero General Public License for more details.
///
///  You should have received a copy of the GNU Affero General Public License
///  along with this program.  If not, see <http://www.gnu.org/licenses/>.
///

angular
	.module('indx')
	.factory('local',function(utils) {

		// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)
		var u = utils, log = utils.log, error = utils.error, debug = utils.debug, jQ = jQuery;

		function deleteDB(dbid) {
		    try {
		        var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
		        var dbreq = indexedDB.deleteDatabase(dbid);
		        dbreq.onsuccess = function (event) {
		            var db = event.result;
		        };
		        dbreq.onerror = function (event) {
		            console.error("indexedDB.delete Error: " + event.message);
		        };
		    }
		    catch (e) {
		        console.error("Error: " + e.message);
		        //prefer change id of database to start ont new instance
		        console.log("fallback to new database name :" + dbid);
		    }
		}

		var Store = Backbone.Model.extend({
			initialize:function() {
				this.collections_by_boxid = {};
				this.deferreds_by_boxid = {};
			},
			_makeBox:function(bid, make_indexes) {
				u.assert(bid !== undefined, "box id has to be defined");
				var db = {
			 		id:bid,
			 		description:'indx local store',
				 	migrations: [
				 		{
				 			version:1,
				 			migrate:function(transaction, next){
				 				var store = transaction.db.createObjectStore(bid);
				 				next();
				 			}
				 		}
				 	]
				};
				if (make_indexes !== undefined) {
					db.migrations.push({
						version:2,
						migrate:function(transaction, next) {
							try {
								var store = transaction.objectStore(bid);
								make_indexes.map(function(field) {
									store.createIndex(field + "index", field, { unique: false });
						        });
						        next();
						    } catch(e) { console.error(e); }
						}
					});
				}
			 	var DBModel = Backbone.Model.extend({ database: db, storeName:bid	}),
				 	Collection = Backbone.Collection.extend({
				 		database:db,
				 		storeName:bid,
				 		model: DBModel,
				 		getObj:function(id) {
				 			if (this.get(id) !== undefined) {
				 				return u.dresolve(this.get(id));
				 			}
				 			console.log('doesnt exist, making new ');
				 			return u.dresolve(new this.model({id:id}));
				 		}
				 	});
				this.collections_by_boxid[bid] = Collection;

			 	var collection = new Collection(), d = u.deferred();
			 	collection.fetch().then(function() {
			 		d.resolve(collection);
			 	}).fail(d.reject);
			 	return d.promise();
			},
			getBox:function(bid, make_indexes) {
				if (this.deferreds_by_boxid[bid]) {
					return this.deferreds_by_boxid[bid];
				}
				this.deferreds_by_boxid[bid] = this._makeBox(bid, make_indexes);
				return this.deferreds_by_boxid[bid];
			}
		});

		var s,
		getStore = function() {
				if (!s) { s = new Store(); }
				return s;
		},
		test3 = function(id) {
			var s = getStore();
			var boxid = id || u.guid(6);
			s.getBox(boxid,['name']).then(function(box) {
				var a = window.as = [];
				var ds = u.range(100).map(function(i) {
					var guid = u.guid(6), d_ = u.deferred();
					box.getObj(guid).then(function(x) {
						a.push(x);
						console.log('got obj >> ', x.id);
						x.set({name:['hello','hi', 'goodbye'][i%3], date:i});
						x.save().then(d_.resolve);
					});
					return d_.promise();
				});
				u.when(ds).then(function() {
					var query = new s.collections_by_boxid[boxid]();
					query.fetch({
						conditions: {name:'hello'},
						success: function () {
							console.log('wins ', query.models.length, query.models);
							console.log(query.models.map(function(x) { return x.id; }));
                            // equal(query.models.length, 2, "Should have 2 elements");
                            // deepEqual(theater.pluck("title"), ["Bonjour", "Ciao"], "Should have [\"Bonjour\", \"Ciao\"]");
                        },
						error: function (object, error) {
                            console.error('couldnt query -- ', object, error);
                        }
					}).then(function() {
						console.log('hello and hi hits ', query.length);
					}).fail(function(r) {
						console.error(r);
					});
				}).fail(function(x) { console.error('sad '); });
			}).fail(function(err) {	console.error('couldnt get box '); 	});
		};
		setTimeout(function() { test3(); }, 100);

		return {
			getStore : getStore,
			test : function(boxid) {
				var s = this.getStore();
				s.getBox(boxid || 'a').then(function(box) {
					window.box = box;
					var ds = [];
					u.range(10).map(function(x) {
						var uid = u.guid(10);
						// var result = box.create({id:uid, test:u.guid(5), message:'hello'});
						var obj = new box.model({id:uid, test:u.guid(5), message:'hello'});
						ds.push(obj.save());
					});
					u.when(ds)
						.then(function(x) { console.log('all done!'); })
						.fail(function(e) { console.error(e); });
				}).fail(function(err) {
					console.error('couldnt get box ');
				});
			},
			test2 : function(boxid) {
				var s = this.getStore();
				s.getBox(boxid || 'a').then(function(box) {
					var guid = u.guid();
					box.getObj(guid).then(function(x) {
						console.log('created x >> ');
						u.assert(x.id == guid, 'ids didnt match');
						x.set({baby:true});
						x.save();
					});
				}).fail(function(err) {
					console.error('couldnt get box ');
				});
			},
			test3 : test3,
			deleteDB:deleteDB
		};

	});
	<|endoftext|>


<commit_before>var path = require('path'),
    util = require('util'),
    glob = require('glob'),
    fs = require('fs');

/**
* Utilities Class
* @class utils
* @module yuidoc
*/

YUI.add('utils', function(Y) {

var HTML_CHARS = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ''',
    '/': '/',
    '`': '`'
};

    /**
    Escapes HTML characters in _html_.

    @method escapeHTML
    @param {String} html String to escape.
    @return {String} Escaped string.
    **/
    Y.escapeHTML = function(html) {
        return html.replace(/[&<>"'\/`]/g, function (match) {
            return HTML_CHARS[match];
        });
    };

    /**
    Normalizes the initial indentation of the given _content_ so that the first line
    is unindented, and all other lines are unindented to the same degree as the
    first line. So if the first line has four spaces at the beginning, then all
    lines will be unindented four spaces.

    @method unindent
    @param {String} content Text to unindent.
    @return {String} Unindented text.
    @private
    **/
    Y.unindent = function(content) {
        var indent = content.match(/^(\s+)/);

        if (indent) {
            content = content.replace(new RegExp('^' + indent[1], 'gm'), '');
        }

        return content;
    };

/**
Like `getPages()`, but returns only the files under the `layout/` subdirectory
of the specified _dir_.

@method getLayouts
@param {String} dir Directory path.
@return {Object} Mapping of layout names to layout content.
**/
function getLayouts(dir) {
    return getPages(path.join(dir, 'layouts'));
}
Y.getLayouts = getLayouts;

/**
Loads and returns the content of the specified page file.

@method getPage
@param {String} pagePath Path to a single `.handlebars` page.
@return {String|null} Page content, or `null` if not found.
**/
function getPage(pagePath) {
    if (!Y.Files.isFile(pagePath)) { return null; }
    return fs.readFileSync(pagePath, 'utf8');
}
Y.getPage = getPage;

/**
Loads pages (files with a `.handlebars` extension) in the specified directory and
returns an object containing a mapping of page names (the part of the filename)
preceding the `.handlebars` extension) to page content.

@method getPages
@param {String} dir Directory path.
@return {Object} Mapping of page names to page content.
**/
var cache = {};
function getPages(dir) {
    if (cache[dir]) {
        return cache[dir];
    }
    var pages = {};

    if (!Y.Files.isDirectory(dir)) { return pages; }

    fs.readdirSync(dir).forEach(function (filename) {
        var filePath = path.join(dir, filename);

        if (path.extname(filename) === '.handlebars' && Y.Files.isFile(filePath)) {
            pages[path.basename(filename, '.handlebars')] = fs.readFileSync(filePath, 'utf8');
        }
    });
    cache[dir] = pages;

    return pages;
}
Y.getPages = getPages;

/**
Like `getPages()`, but returns only the files under the `partial/` subdirectory
of the specified _dir_.

@method getPartials
@param {String} dir Directory path.
@return {Object} Mapping of partial names to partial content.
**/
function getPartials(dir) {
    return getPages(path.join(dir, 'partials'));
}
Y.getPartials = getPartials;

/**
Mix/merge/munge data into the template.
@method prepare
@param {String} inDir The starting directory
@param {Object} options The `options` for the meta data.
@param {callback}
@param {Error} callback.err
@param {Object} callback.options Merged options.
**/
function prepare(inDir, options, callback) {
    var compiled = {},
        meta     = {},
        type     = 'project';

    if (options && options.skipLoad) {
        // Skip loading layouts, metadata, pages, and partials and assume that
        // the caller has provided them if they want them.
        options = Y.merge({
            layouts  : {},
            meta     : {},
            pages    : {},
            partials : {},
            viewClass: Y.DocView
        }, options);
    } else {

        // Gather layouts, metadata, pages, and partials from the specified
        // input directory, then merge them into the provided options (if any).
        //
        // Gathered data will override provided data if there are conflicts, in
        // order to support a use case where global data are provided by the
        // caller and overridden by more specific component-level data gathered
        // from the input directory.
        //
        // The metadata inheritance chain looks like this:
        //
        //   - override metadata specified via CLI (highest precedence)
        //   - component metadata (if this is a component)
        //   - project-level component default metadata (if specified and this is a component)
        //   - theme-level component default metadata (if specified and this is a component)
        //   - project metadata
        //   - theme metadata (lowest precedence)

        try {
            options = Y.merge(
                {viewClass: Y.DocView},
                options || {},
                {
                    layouts : getLayouts(inDir),
                    meta    : options.meta,
                    partials: getPartials(inDir)
                }
            );
        } catch (ex) {
            return callback(ex);
        }
    }

    // Pages are never merged.
    options.pages = getPages(inDir);

    // Mix in the override metadata, if any. It takes precedence over everything
    // else.
    Y.mix(options.meta, options.overrideMeta);

    // Set a default asset path if one isn't specified in the metadata.
    if (!options.meta.projectAssets) {
        options.meta.projectAssets = options.component ? '../assets' : 'assets';
    }

    if (!options.meta.componentAssets && options.component) {
        options.meta.componentAssets = '../assets/' + options.meta.name;
    }

    if (typeof options.meta.layout === 'undefined') {
        options.meta.layout = options.layouts[type] ? type : 'main';
    }

    callback(null, options);
}

Y.prepare = prepare;

var projectData;
/**
* Walk the directory tree to locate the yuidoc.json file.
* @method getProjectData
* @param {Path} [dir=process.cwd()] The directory to start from
*/
var getProjectData = function(dir) {
    if (!dir) {
        dir = process.cwd();
    }
    var dirs = fs.readdirSync(dir);
    dirs.forEach(function(d) {
        var p = path.join(dir, d);
        if (projectData) {
            return;
        }
        if (Y.Files.isFile(p)) {
            if (d === 'yuidoc.json') {
                projectData = Y.Files.getJSON(p);
            }
        }
        if (!projectData && Y.Files.isDirectory(p)) {
            projectData = getProjectData(p);
        }
    });
    return projectData;
};

Y.getProjectData = getProjectData;

/**
* Make sure all the paths passed are directories and that they are not in the ignore list.
* @method validatePaths
* @param {Array} paths The array of paths to validate
* @param {String} [ignore=false] A string to call `.indexOf` on a path to determine if it should be ignored
*/

var validatePaths = function(paths, ignore) {
    if (!paths || !paths.forEach) {
        paths = glob.globSync('.', process.cwd());
    }
    if (paths.forEach) {
        paths.forEach(function(p) {
            if (!Y.Files.isDirectory(p)) {
                throw('Path not a direactory: ' + p);
            }
        });
    }
    if (!paths || !paths.forEach) {
        throw('Paths should be an array of paths');
    }
    if (ignore) {
        var p = [];
        paths.forEach(function(v) {
            if (v.indexOf(ignore) === -1) {
                p.push(v);
            }
        });
        paths = p;
    }
    return paths;
};

Y.validatePaths = validatePaths;

});
<commit_msg>Fixed blob parsing as well as ignore handling<commit_after>var path = require('path'),
    util = require('util'),
    glob = require('glob'),
    fs = require('fs');

/**
* Utilities Class
* @class utils
* @module yuidoc
*/

YUI.add('utils', function(Y) {

var HTML_CHARS = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ''',
    '/': '/',
    '`': '`'
};

    /**
    Escapes HTML characters in _html_.

    @method escapeHTML
    @param {String} html String to escape.
    @return {String} Escaped string.
    **/
    Y.escapeHTML = function(html) {
        return html.replace(/[&<>"'\/`]/g, function (match) {
            return HTML_CHARS[match];
        });
    };

    /**
    Normalizes the initial indentation of the given _content_ so that the first line
    is unindented, and all other lines are unindented to the same degree as the
    first line. So if the first line has four spaces at the beginning, then all
    lines will be unindented four spaces.

    @method unindent
    @param {String} content Text to unindent.
    @return {String} Unindented text.
    @private
    **/
    Y.unindent = function(content) {
        var indent = content.match(/^(\s+)/);

        if (indent) {
            content = content.replace(new RegExp('^' + indent[1], 'gm'), '');
        }

        return content;
    };

/**
Like `getPages()`, but returns only the files under the `layout/` subdirectory
of the specified _dir_.

@method getLayouts
@param {String} dir Directory path.
@return {Object} Mapping of layout names to layout content.
**/
function getLayouts(dir) {
    return getPages(path.join(dir, 'layouts'));
}
Y.getLayouts = getLayouts;

/**
Loads and returns the content of the specified page file.

@method getPage
@param {String} pagePath Path to a single `.handlebars` page.
@return {String|null} Page content, or `null` if not found.
**/
function getPage(pagePath) {
    if (!Y.Files.isFile(pagePath)) { return null; }
    return fs.readFileSync(pagePath, 'utf8');
}
Y.getPage = getPage;

/**
Loads pages (files with a `.handlebars` extension) in the specified directory and
returns an object containing a mapping of page names (the part of the filename)
preceding the `.handlebars` extension) to page content.

@method getPages
@param {String} dir Directory path.
@return {Object} Mapping of page names to page content.
**/
var cache = {};
function getPages(dir) {
    if (cache[dir]) {
        return cache[dir];
    }
    var pages = {};

    if (!Y.Files.isDirectory(dir)) { return pages; }

    fs.readdirSync(dir).forEach(function (filename) {
        var filePath = path.join(dir, filename);

        if (path.extname(filename) === '.handlebars' && Y.Files.isFile(filePath)) {
            pages[path.basename(filename, '.handlebars')] = fs.readFileSync(filePath, 'utf8');
        }
    });
    cache[dir] = pages;

    return pages;
}
Y.getPages = getPages;

/**
Like `getPages()`, but returns only the files under the `partial/` subdirectory
of the specified _dir_.

@method getPartials
@param {String} dir Directory path.
@return {Object} Mapping of partial names to partial content.
**/
function getPartials(dir) {
    return getPages(path.join(dir, 'partials'));
}
Y.getPartials = getPartials;

/**
Mix/merge/munge data into the template.
@method prepare
@param {String} inDir The starting directory
@param {Object} options The `options` for the meta data.
@param {callback}
@param {Error} callback.err
@param {Object} callback.options Merged options.
**/
function prepare(inDir, options, callback) {
    var compiled = {},
        meta     = {},
        type     = 'project';

    if (options && options.skipLoad) {
        // Skip loading layouts, metadata, pages, and partials and assume that
        // the caller has provided them if they want them.
        options = Y.merge({
            layouts  : {},
            meta     : {},
            pages    : {},
            partials : {},
            viewClass: Y.DocView
        }, options);
    } else {

        // Gather layouts, metadata, pages, and partials from the specified
        // input directory, then merge them into the provided options (if any).
        //
        // Gathered data will override provided data if there are conflicts, in
        // order to support a use case where global data are provided by the
        // caller and overridden by more specific component-level data gathered
        // from the input directory.
        //
        // The metadata inheritance chain looks like this:
        //
        //   - override metadata specified via CLI (highest precedence)
        //   - component metadata (if this is a component)
        //   - project-level component default metadata (if specified and this is a component)
        //   - theme-level component default metadata (if specified and this is a component)
        //   - project metadata
        //   - theme metadata (lowest precedence)

        try {
            options = Y.merge(
                {viewClass: Y.DocView},
                options || {},
                {
                    layouts : getLayouts(inDir),
                    meta    : options.meta,
                    partials: getPartials(inDir)
                }
            );
        } catch (ex) {
            return callback(ex);
        }
    }

    // Pages are never merged.
    options.pages = getPages(inDir);

    // Mix in the override metadata, if any. It takes precedence over everything
    // else.
    Y.mix(options.meta, options.overrideMeta);

    // Set a default asset path if one isn't specified in the metadata.
    if (!options.meta.projectAssets) {
        options.meta.projectAssets = options.component ? '../assets' : 'assets';
    }

    if (!options.meta.componentAssets && options.component) {
        options.meta.componentAssets = '../assets/' + options.meta.name;
    }

    if (typeof options.meta.layout === 'undefined') {
        options.meta.layout = options.layouts[type] ? type : 'main';
    }

    callback(null, options);
}

Y.prepare = prepare;

var projectData;
/**
* Walk the directory tree to locate the yuidoc.json file.
* @method getProjectData
* @param {Path} [dir=process.cwd()] The directory to start from
*/
var getProjectData = function(dir) {
    if (!dir) {
        dir = process.cwd();
    }
    var dirs = fs.readdirSync(dir);
    dirs.forEach(function(d) {
        var p = path.join(dir, d);
        if (projectData) {
            return;
        }
        if (Y.Files.isFile(p)) {
            if (d === 'yuidoc.json') {
                projectData = Y.Files.getJSON(p);
            }
        }
        if (!projectData && Y.Files.isDirectory(p)) {
            projectData = getProjectData(p);
        }
    });
    return projectData;
};

Y.getProjectData = getProjectData;

/**
* Make sure all the paths passed are directories and that they are not in the ignore list.
* @method validatePaths
* @param {Array} paths The array of paths to validate
* @param {String} [ignore=false] A string to call `.indexOf` on a path to determine if it should be ignored
*/

var validatePaths = function(paths, ignore) {
    if (!paths || !paths.forEach) {
        if (!paths) {
            paths = '.';
        }
        paths = glob.globSync(paths, process.cwd());
    }
    if (paths.forEach) {
        paths.forEach(function(p) {
            if (!Y.Files.isDirectory(p)) {
                throw('Path not a direactory: ' + p);
            }
        });
    }
    if (!paths || !paths.forEach) {
        throw('Paths should be an array of paths');
    }
    if (ignore) {
        if (!(ignore instanceof Array)) {
            ignore = [ignore];
        }
        var p = [];
        paths.forEach(function(v) {
            ignore.forEach(function(i) {
                if (v.indexOf(i) === -1) {
                    p.push(v);
                }
            });
        });
        paths = p;
    }
    return paths;
};

Y.validatePaths = validatePaths;

});
<|endoftext|>