<commit_before>/**
Copyright 2010 Joel Hockey (joel.hockey@gmail.com).  MIT Licence

JST is a parser / generator / compiler.  It parses templates and generates
JavaScript that can be compiled (eval'ed using JavaScript runtime).

JST ensures that the generated JavaScript code will have the same line numbers
as the input template so that error messages from 'eval' will have
line numbers that match the input template.

Each compiled template produces a JavaScript object that is stored
in JST.templates and contains method 'render(out, cx)'
 * 'out' must be an object that implements 'write(str)'
 * 'cx' is an object that is used as the context to provide
   variables used in the template.
JST provides a helper method 'JST.render(name, cx)' that returns
the rendered template as a string.

The most basic usage is to call:
 * JST.compile(name, template);
 * var output = JST.render(name, cx);

    js> load("jst.js");
    js> var template = "{for each (var name in names)}\nHello ${name}\n{/for}";
    js> JST.compile("hello", template);
    js> print(JST.render("hello", { names: ["John", "Paul", "George", "Ringo"] }));
    Hello John
    Hello Paul
    Hello George
    Hello Ringo

    js>

The code below shows parsing and compiling done separately, and
templates writing to a stream.

    js> var generated = JST.parse("hello", template);
    js> print(generated);
    JST.templates["hello"] = {}; if (!JST.templates["hello"].hasOwnProperty("render")) { JST.templates["hello"].render = function (out, cx) { with (cx) { var forcounter = 0; for each (var name in names) { forcounter++;
    out.write("Hello "); h(name, out); out.write("\n\
    "); } }}};
    js> eval(generated);
    js> var stream = { buf: [], write: function(s) { this.buf.push(s); } }
    js> JST.templates.hello.render(stream, { names: ["John", "Paul", "George", "Ringo"] });
    js> print(stream.buf.join(""));
    Hello John
    Hello Paul
    Hello George
    Hello Ringo

    js>
*/

var JST = {
    templates : {},
    compile: function(name, body) { eval(this.parse(name, body)); },
    render: function(name, cx) {
        var out = { buf: [], write: function(s) { this.buf.push(s); }};
        JST.templates[name].render(out, cx);
        return out.buf.join("");
    },
    get: function(templateName, protoName) {
        if (protoName) {
            function F() {}
            F.prototype = this.get(protoName);
            JST.templates[templateName] = new F();
        } else {
            JST.templates[templateName] = JST.templates[templateName] || {};
        }
        return JST.templates[templateName];
    },
    parse : function (name, body) {

        // parser variables
        var line = 1;       // used by parser for error messages
        var linepos = 1;    // used by parser for error messages
        var toks = [];      // parser puts results in here
        var groups;         // used by parser to store regexp matches

        // parser puts tokens in 'toks' array.
        // Each token is an object containing information used by generator
        // Format of token:
        //  - type: newline|value|opentag|closetag|text|comment
        //  - tok: exact matched text, e.g. '{for (var item in items)}'
        //  - value: value of token, e.g. 'for', 'function foo'
        //  - words: value.split(), used only in opentag and closetag,
        //     e.g. ['for','(var','item','in','items)'], ['function', 'foo']

        // parse
        while (body.length > 0) {
            // newline
            if (groups = /^[ \t]*(\r?\n|\r)/.exec(body)) {
                toks.push({type: "newline", tok: groups[0], value: groups[0]});

            // comment
            } else if (groups = /^\s*{\*(.*?)\*}/.exec(body)) {
                toks.push({type: "comment", tok: groups[0], value: groups[1]});
            // value, opentag, closetag
            } else if (groups = /^(\$|\s*){(\/?)([^\r\n{}]+)}/.exec(body)) {
                var type = "opentag";
                if (groups[1] === "$") { type = "value";
                } else if (groups[2] === "/") { type = "closetag"; }
                var value = groups[3].replace(/^\s+|\s+$/g, ""); // trim space
                toks.push({type: type, tok: groups[0],
                    value: value, words: value.split(/\s+/)});

            // text
            } else if ((groups = /^[^\r\n${]+/.exec(body)) ||
                    (groups = /^[^\r\n]+/.exec(body))) {
                toks.push({type: "text", tok: groups[0], value: groups[0]});
            } else {
                // impossible
                throw new Error("Could not parse: " + body);
            }
            body = body.substring(groups[0].length);
        }

        // generator variables
        var tagstack = [];  // used by generator to match open/close tags
        var inEval = false; // gen handles tokens differently in eval-mode
        var inText = false; // similar to 'inEval'
        var textparts = []; // text parts are grouped for better render perf
        var fcount = 0;     // number of functions on tagstack
        // src is output of generator
        // First line of 'src' creates template object within 'JST.templates'
        // If template declares prototype, src[0] is replaced with:
        //   JST.get(name, proto);
        var src = ['JST.get("' + name + '"); '];

        // add specified string to src.
        // first put text parts into single 'out.write' statement
        var addsrc = function(s) {
            // first push text onto src
            if (textparts.length) {
                src.push('out.write("' + textparts.join('')
                        .replace(/\r?\n|\r|"/g, function(str) {
                            switch(str) {
                            case "\r\n": return "\\r\\n\\\n";
                            case '"': return '\\"';
                            case "\n": return "\\n\\\n";
                            case "\r": return "\\r\\\n";
                            }
                        }) + '"); ');
                textparts = [];
            }
            src.push(s);
        };

        // errors during generator
        var error = function(desc) {
            throw new Error(desc + ", line: " + line + "." + linepos +
                    ", tagstack: [" + tagstack.join(" > ") + "]");
        };

        // Generate JS template that can be eval'ed
        // Take care to match line numbers of generated JS with
        // line numbers of source.  We then rely on JS interpreter to
        // provide good error messages

        // skip blank lines at start
        while (toks.length > 0 && toks[0].type === "newline") {
            toks.shift();
            src.push("\n");
        }

        // either set up 'prototype' or wrap toks with 'function render'
        if (toks.length > 0 && (toks[0].type === "opentag"
                    && toks[0].words[0] === "prototype")) {
                var tok = toks.shift();
                if (tok.words.length !== 2) {
                    error("invalid prototype tag format: " + tok.tok);
                }
                // create empty prototype if it doesn't yet exist
                src[0] = 'JST.get("' + name + '", "' + tok.words[1] + '"); ';
        } else {
            // wrap with render (front and back)
            toks.unshift({type: "opentag", tok: "{function render}",
                value: "function render", words: ["function", "render"]});
            toks.push({type: "closetag", tok: "{/function render}",
                value: "function render", words: ["function", "render"]});
        }

        for (var i = 0; i < toks.length; i++) {
            var tok = toks[i];
            var toptag = tagstack[tagstack.length - 1];
            // newline
            if (tok.type === "newline") {
                // update line, pos is increased at bottom of for-loop
                line++;
                linepos = 1 - tok.value.length;
                // check if newline is formatting only, or needs to be rendered
                // formatting only if we are in {eval}...{/eval},
                // or if line contains only open/close tags, or if fcount === 0
                var j = i;
                while (j > 0 && /^(opentag|closetag|comment)$/.test(toks[--j].type));
                var tags = j < i-1 && (j === 0 || toks[j].type === "newline");
                if (inEval || tags || fcount === 0) {
                    addsrc("\n"); // formatting only
                } else {
                    textparts.push(tok.value); // needs to be rendered
                }

            // are we at end of {text?}...{/text?} or {eval}...{/eval}
            } else if ((inText || inEval) && tok.type === "closetag"
                    && tok.value === toptag) {
                addsrc("; ");
                // we are now finished 'text' or 'eval' section
                inText = inEval = false;
                tagstack.pop();
            } else if (inText) { // still in text
                textparts.push(tok.tok);
            } else if (inEval) { // still in eval
                addsrc(tok.value);

            // comment
            } else if (tok.type === "comment") {
                // do nothing - ignore comment

            // value substitution
            } else if (tok.type === "value") {
                addsrc('h(' + tok.value + ', out); '); // html-escape

            // open tag
            } else if (tok.type === "opentag") {
                if (tok.words[0] === "function") {
                    if (tok.words.length !== 2) {
                        error("invalid function tag format: " + tok.tok);
                    }
                    if (tok.words[1] === "render" && i !== 0) {
                        error("function 'render' not allowed");
                    }
                    // inner functions are wrapped within
                    // 'if (!JST.templates[name].hasOwnProperty(name)) { ...'
                    if (fcount > 0) {
                        addsrc('if (!JST.templates["' + name
                            + '"].hasOwnProperty("' + tok.words[1] + '")) { ');
                    }
                    addsrc('JST.templates["' + name + '"].' + tok.words[1]
                        + ' = function (out, cx) { with (cx) { ');
                    fcount++;
                    tagstack.push(tok.value); // push 'function <fname>'
                } else if (tok.words[0] === "render") {
                    if (tok.words.length !== 2) {
                        error("invalid render tag format: " + tok.tok);
                    }
                    addsrc('JST.templates["' + tok.words[1] + '"].render(out, cx); ');
                } else if (tok.words[0] === "if") {
                    addsrc(tok.value + " {");
                    tagstack.push("if");
                } else if (tok.words[0].match(/^els?e?(if)?$/)) {
                    if (!toptag || !toptag.match(/^(if|els?e?(if)?)$/)) {
                        error("unexpected else(if)? tag");
                    }
                    var ifword = tok.words[0] === "else" ? "" : " if ";
                    addsrc("} else " + ifword
                            + tok.words.slice(1).join(" ") + " {");
                } else if (tok.words[0] === "for") {
                    addsrc("var forcounter = 0; " +
                            tok.value + " { forcounter++; ");
                    tagstack.push("for");
                } else if (tok.words[0] === "forelse") {
                    if (toptag !== "for") { error("unexpected forelse tag"); }
                    addsrc("} if (forcounter === 0) { ");
                } else if (tok.words[0] === "eval") {
                    if (tok.words.length === 1) {
                         inEval = true;
                         tagstack.push("eval");
                    } else { // inline eval
                        addsrc(tok.words.slice(1).join(" ") + "; ");
                    }
                } else if (tok.words[0].match(/^text/)) {
                    inText = true;
                    tagstack.push(tok.value);
                } else { // treat as text
                    tok.type = "text";
                    textparts.push(tok.tok);
                }

            // close tag
            } else if (tok.type === "closetag") {
                if (toptag !== tok.value) {
                    error("unexepcted closetag " + tok.tok);
                }
                addsrc("} ");
                tagstack.pop();
                if (tok.words[0] === "function") {
                    addsrc("};");
                    fcount--;
                    // inner functions are wrapped within
                    // 'if (!JST.templates[name].hasOwnProperty(name)) { ...'
                    // and must invoke self
                    if (fcount > 0) {
                        addsrc(" }; this." + tok.words[1] + "(out, cx); ");
                    }
                }

            // text
            } else if (tok.type === "text") {
                // ignore text outside functions
                if (fcount > 0) {
                    textparts.push(tok.value);
                }

            } else {
                error("unrecognised token type: " + tok.type);
            }
            linepos += tok.value.length
        }

        return src.join("");
    }
}

// JST relies on global method 'h' for html-escape
var h = h || function(s, out) {
    if (!s) return "";
    var result = s.toString().replace(/[&<>"']/g, function(str) {
       switch (str) {
       case "&": return "&";
       case "<": return "<";
       case ">": return ">";
       case '"': return "&quote;";
       case "'": return "'";
       default: return str;
       }
    });
    if (out) {
        out.write(result);
    } else {
        return result;
    }
}

// JST relies on Object.create
if (typeof Object.create !== "function") {
    Object.create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
}
<commit_msg>changed function F to var F = function<commit_after>/**
Copyright 2010 Joel Hockey (joel.hockey@gmail.com).  MIT Licence

JST is a parser / generator / compiler.  It parses templates and generates
JavaScript that can be compiled (eval'ed using JavaScript runtime).

JST ensures that the generated JavaScript code will have the same line numbers
as the input template so that error messages from 'eval' will have
line numbers that match the input template.

Each compiled template produces a JavaScript object that is stored
in JST.templates and contains method 'render(out, cx)'
 * 'out' must be an object that implements 'write(str)'
 * 'cx' is an object that is used as the context to provide
   variables used in the template.
JST provides a helper method 'JST.render(name, cx)' that returns
the rendered template as a string.

The most basic usage is to call:
 * JST.compile(name, template);
 * var output = JST.render(name, cx);

    js> load("jst.js");
    js> var template = "{for each (var name in names)}\nHello ${name}\n{/for}";
    js> JST.compile("hello", template);
    js> print(JST.render("hello", { names: ["John", "Paul", "George", "Ringo"] }));
    Hello John
    Hello Paul
    Hello George
    Hello Ringo

    js>

The code below shows parsing and compiling done separately, and
templates writing to a stream.

    js> var generated = JST.parse("hello", template);
    js> print(generated);
    JST.templates["hello"] = {}; if (!JST.templates["hello"].hasOwnProperty("render")) { JST.templates["hello"].render = function (out, cx) { with (cx) { var forcounter = 0; for each (var name in names) { forcounter++;
    out.write("Hello "); h(name, out); out.write("\n\
    "); } }}};
    js> eval(generated);
    js> var stream = { buf: [], write: function(s) { this.buf.push(s); } }
    js> JST.templates.hello.render(stream, { names: ["John", "Paul", "George", "Ringo"] });
    js> print(stream.buf.join(""));
    Hello John
    Hello Paul
    Hello George
    Hello Ringo

    js>
*/

var JST = {
    templates : {},
    compile: function(name, body) { eval(this.parse(name, body)); },
    render: function(name, cx) {
        var out = { buf: [], write: function(s) { this.buf.push(s); }};
        JST.templates[name].render(out, cx);
        return out.buf.join("");
    },
    get: function(templateName, protoName) {
        if (protoName) {
            var F = function() {}
            F.prototype = this.get(protoName);
            JST.templates[templateName] = new F();
        } else {
            JST.templates[templateName] = JST.templates[templateName] || {};
        }
        return JST.templates[templateName];
    },
    parse : function (name, body) {

        // parser variables
        var line = 1;       // used by parser for error messages
        var linepos = 1;    // used by parser for error messages
        var toks = [];      // parser puts results in here
        var groups;         // used by parser to store regexp matches

        // parser puts tokens in 'toks' array.
        // Each token is an object containing information used by generator
        // Format of token:
        //  - type: newline|value|opentag|closetag|text|comment
        //  - tok: exact matched text, e.g. '{for (var item in items)}'
        //  - value: value of token, e.g. 'for', 'function foo'
        //  - words: value.split(), used only in opentag and closetag,
        //     e.g. ['for','(var','item','in','items)'], ['function', 'foo']

        // parse
        while (body.length > 0) {
            // newline
            if (groups = /^[ \t]*(\r?\n|\r)/.exec(body)) {
                toks.push({type: "newline", tok: groups[0], value: groups[0]});

            // comment
            } else if (groups = /^\s*{\*(.*?)\*}/.exec(body)) {
                toks.push({type: "comment", tok: groups[0], value: groups[1]});
            // value, opentag, closetag
            } else if (groups = /^(\$|\s*){(\/?)([^\r\n{}]+)}/.exec(body)) {
                var type = "opentag";
                if (groups[1] === "$") { type = "value";
                } else if (groups[2] === "/") { type = "closetag"; }
                var value = groups[3].replace(/^\s+|\s+$/g, ""); // trim space
                toks.push({type: type, tok: groups[0],
                    value: value, words: value.split(/\s+/)});

            // text
            } else if ((groups = /^[^\r\n${]+/.exec(body)) ||
                    (groups = /^[^\r\n]+/.exec(body))) {
                toks.push({type: "text", tok: groups[0], value: groups[0]});
            } else {
                // impossible
                throw new Error("Could not parse: " + body);
            }
            body = body.substring(groups[0].length);
        }

        // generator variables
        var tagstack = [];  // used by generator to match open/close tags
        var inEval = false; // gen handles tokens differently in eval-mode
        var inText = false; // similar to 'inEval'
        var textparts = []; // text parts are grouped for better render perf
        var fcount = 0;     // number of functions on tagstack
        // src is output of generator
        // First line of 'src' creates template object within 'JST.templates'
        // If template declares prototype, src[0] is replaced with:
        //   JST.get(name, proto);
        var src = ['JST.get("' + name + '"); '];

        // add specified string to src.
        // first put text parts into single 'out.write' statement
        var addsrc = function(s) {
            // first push text onto src
            if (textparts.length) {
                src.push('out.write("' + textparts.join('')
                        .replace(/\r?\n|\r|"/g, function(str) {
                            switch(str) {
                            case "\r\n": return "\\r\\n\\\n";
                            case '"': return '\\"';
                            case "\n": return "\\n\\\n";
                            case "\r": return "\\r\\\n";
                            }
                        }) + '"); ');
                textparts = [];
            }
            src.push(s);
        };

        // errors during generator
        var error = function(desc) {
            throw new Error(desc + ", line: " + line + "." + linepos +
                    ", tagstack: [" + tagstack.join(" > ") + "]");
        };

        // Generate JS template that can be eval'ed
        // Take care to match line numbers of generated JS with
        // line numbers of source.  We then rely on JS interpreter to
        // provide good error messages

        // skip blank lines at start
        while (toks.length > 0 && toks[0].type === "newline") {
            toks.shift();
            src.push("\n");
        }

        // either set up 'prototype' or wrap toks with 'function render'
        if (toks.length > 0 && (toks[0].type === "opentag"
                    && toks[0].words[0] === "prototype")) {
                var tok = toks.shift();
                if (tok.words.length !== 2) {
                    error("invalid prototype tag format: " + tok.tok);
                }
                // create empty prototype if it doesn't yet exist
                src[0] = 'JST.get("' + name + '", "' + tok.words[1] + '"); ';
        } else {
            // wrap with render (front and back)
            toks.unshift({type: "opentag", tok: "{function render}",
                value: "function render", words: ["function", "render"]});
            toks.push({type: "closetag", tok: "{/function render}",
                value: "function render", words: ["function", "render"]});
        }

        for (var i = 0; i < toks.length; i++) {
            var tok = toks[i];
            var toptag = tagstack[tagstack.length - 1];
            // newline
            if (tok.type === "newline") {
                // update line, pos is increased at bottom of for-loop
                line++;
                linepos = 1 - tok.value.length;
                // check if newline is formatting only, or needs to be rendered
                // formatting only if we are in {eval}...{/eval},
                // or if line contains only open/close tags, or if fcount === 0
                var j = i;
                while (j > 0 && /^(opentag|closetag|comment)$/.test(toks[--j].type));
                var tags = j < i-1 && (j === 0 || toks[j].type === "newline");
                if (inEval || tags || fcount === 0) {
                    addsrc("\n"); // formatting only
                } else {
                    textparts.push(tok.value); // needs to be rendered
                }

            // are we at end of {text?}...{/text?} or {eval}...{/eval}
            } else if ((inText || inEval) && tok.type === "closetag"
                    && tok.value === toptag) {
                addsrc("; ");
                // we are now finished 'text' or 'eval' section
                inText = inEval = false;
                tagstack.pop();
            } else if (inText) { // still in text
                textparts.push(tok.tok);
            } else if (inEval) { // still in eval
                addsrc(tok.value);

            // comment
            } else if (tok.type === "comment") {
                // do nothing - ignore comment

            // value substitution
            } else if (tok.type === "value") {
                addsrc('h(' + tok.value + ', out); '); // html-escape

            // open tag
            } else if (tok.type === "opentag") {
                if (tok.words[0] === "function") {
                    if (tok.words.length !== 2) {
                        error("invalid function tag format: " + tok.tok);
                    }
                    if (tok.words[1] === "render" && i !== 0) {
                        error("function 'render' not allowed");
                    }
                    // inner functions are wrapped within
                    // 'if (!JST.templates[name].hasOwnProperty(name)) { ...'
                    if (fcount > 0) {
                        addsrc('if (!JST.templates["' + name
                            + '"].hasOwnProperty("' + tok.words[1] + '")) { ');
                    }
                    addsrc('JST.templates["' + name + '"].' + tok.words[1]
                        + ' = function (out, cx) { with (cx) { ');
                    fcount++;
                    tagstack.push(tok.value); // push 'function <fname>'
                } else if (tok.words[0] === "render") {
                    if (tok.words.length !== 2) {
                        error("invalid render tag format: " + tok.tok);
                    }
                    addsrc('JST.templates["' + tok.words[1] + '"].render(out, cx); ');
                } else if (tok.words[0] === "if") {
                    addsrc(tok.value + " {");
                    tagstack.push("if");
                } else if (tok.words[0].match(/^els?e?(if)?$/)) {
                    if (!toptag || !toptag.match(/^(if|els?e?(if)?)$/)) {
                        error("unexpected else(if)? tag");
                    }
                    var ifword = tok.words[0] === "else" ? "" : " if ";
                    addsrc("} else " + ifword
                            + tok.words.slice(1).join(" ") + " {");
                } else if (tok.words[0] === "for") {
                    addsrc("var forcounter = 0; " +
                            tok.value + " { forcounter++; ");
                    tagstack.push("for");
                } else if (tok.words[0] === "forelse") {
                    if (toptag !== "for") { error("unexpected forelse tag"); }
                    addsrc("} if (forcounter === 0) { ");
                } else if (tok.words[0] === "eval") {
                    if (tok.words.length === 1) {
                         inEval = true;
                         tagstack.push("eval");
                    } else { // inline eval
                        addsrc(tok.words.slice(1).join(" ") + "; ");
                    }
                } else if (tok.words[0].match(/^text/)) {
                    inText = true;
                    tagstack.push(tok.value);
                } else { // treat as text
                    tok.type = "text";
                    textparts.push(tok.tok);
                }

            // close tag
            } else if (tok.type === "closetag") {
                if (toptag !== tok.value) {
                    error("unexepcted closetag " + tok.tok);
                }
                addsrc("} ");
                tagstack.pop();
                if (tok.words[0] === "function") {
                    addsrc("};");
                    fcount--;
                    // inner functions are wrapped within
                    // 'if (!JST.templates[name].hasOwnProperty(name)) { ...'
                    // and must invoke self
                    if (fcount > 0) {
                        addsrc(" }; this." + tok.words[1] + "(out, cx); ");
                    }
                }

            // text
            } else if (tok.type === "text") {
                // ignore text outside functions
                if (fcount > 0) {
                    textparts.push(tok.value);
                }

            } else {
                error("unrecognised token type: " + tok.type);
            }
            linepos += tok.value.length
        }

        return src.join("");
    }
}

// JST relies on global method 'h' for html-escape
var h = h || function(s, out) {
    if (!s) return "";
    var result = s.toString().replace(/[&<>"']/g, function(str) {
       switch (str) {
       case "&": return "&";
       case "<": return "<";
       case ">": return ">";
       case '"': return "&quote;";
       case "'": return "'";
       default: return str;
       }
    });
    if (out) {
        out.write(result);
    } else {
        return result;
    }
}

// JST relies on Object.create
if (typeof Object.create !== "function") {
    Object.create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
}
<|endoftext|>


<commit_before>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  Imports and Exports provide implicit Context dependency management.

  A class can list which values it requires from the Context, and then
  these values will be added to the object itself so that it doesn't need
  to explicitly work with the Context.

  A class can list which values (properties, methods, or method-like axioms)
  that it exports, and these will automatically be added to the object's
  sub-Context. The object's sub-Context is the context that is used when
  new objects are created by the object.

  Ex.:

foam.CLASS({
  name: 'ImportsTest',

  imports: [ 'log', 'warn' ],

  methods: [ function foo() {
    this.log('log foo from ImportTest');
    this.warn('warn foo from ImportTest');
  } ]
});

foam.CLASS({
  name: 'ExportsTest',
  requires: [ 'ImportsTest' ],

  exports: [ 'log', 'log as warn' ],

  methods: [
    function init() {
      // ImportsTest will be created in ExportTest's
      // sub-Context, which will have 'log' and 'warn'
      // exported.
      this.ImportsTest.create().foo();
    },
    function log(msg) {
      console.log('log:', msg);
    }
  ]
});

  Aliasing:
    Bindings can be renamed or aliased when they're imported or exported using 'as alias'.

  Examples:
    // import 'userDAO' from the Context and make available as this.dao
    imports: [ 'userDAO as dao' ]

    // export my log method as 'warn'
    exports: [ 'log as warn' ]

    // If the axiom to be exported isn't named, but just aliased, then 'this' is exported
    // as the named alias.  This is how objects export themselves.
    exports: [ 'as Controller' ]

  See Context.js.
 */
foam.CLASS({
  package: 'foam.core',
  name: 'Imports',

  // documentation: 'Import Context Value Axiom',

  properties: [
    'name',
    'key',
    {
      name: 'slotName_',
      factory: function() { return this.name + '$'; }
    }
  ],

  methods: [
    function installInProto(proto) {
      var name     = this.name;
      var key      = this.key;
      var slotName = this.slotName_;

      Object.defineProperty(proto, slotName, {
        get: function importsGetter() {
          if ( ! this.hasOwnPrivate_(slotName) ) {
            var ctx = this.__context__ || foam.__context__;
            this.setPrivate_(slotName, ctx[key + '$']);
          }

          return this.getPrivate_(slotName);
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, name, {
        get: function importsGetter()  {
          var slot = this[slotName];
          if ( slot ) return slot.get();
          console.warn('Access missing import:', name);
          return undefined;
        },
        set: function importsSetter(v) {
          var slot = this[slotName];
          if ( slot ) slot.set(v);
          console.warn('Attempt to set missing import:', name);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      return obj[this.slotName_];
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'Exports',

  // documentation: 'Export Sub-Context Value Axiom',

  properties: [
    [ 'name', 'exports_' ],
    {
      name: 'bindings',
      adapt: function(_, bs) {
        var exps = [];
        for ( var i = 0 ; i < bs.length ; i++ ) {
          var b = bs[i];
          if ( typeof b === 'string' ) {
            var key, name, a = b.split(' ');
            switch ( a.length ) {
              case 1:
                key = name = a[0];
              break;
              case 2:
                console.assert(a[0] === 'as', 'Invalid export syntax: key [as value] | as value');
                name = a[1]; // signifies 'this'
                key  = null;
              break;
              case 3:
                console.assert(a[1] === 'as', 'Invalid export syntax: key [as value] | as value');
                name = a[2];
                key  = a[0];
              break;
              default:
                console.error('Invalid export syntax: key [as value] | as value');
            }
            exps[i] = { name: name, key: key };
          } else {
            exps[i] = bs[i];
          }
        }
        return exps;
      }
    }
  ],

  methods: [
    function installInProto(proto) {
      var bs = this.bindings;

      Object.defineProperty(proto, '__subContext__', {
        get: function YGetter() {
          if ( ! this.hasOwnPrivate_('__subContext__') ) {
            var ctx = this.__context__ || foam.__context__;
            var m = {};
            for ( var i = 0 ; i < bs.length ; i++ ) {
              var b = bs[i];

              if ( b.key ) {
                var a = this.cls_.getAxiomByName(b.key);

                if ( ! a ) {
                  console.error(
                    'Unknown export: "' +
                      b.key +
                      '" in model: ' +
                      this.cls_.id);
                }

                // Axioms have an option of wrapping a value for export.
                // This could be used to bind a method to 'this', for example.
                m[b.name] = a.exportAs ? a.exportAs(this) : this[b.key] ;
              } else {
                // Ex. 'as Bank', which exports an implicit 'this'
                m[b.name] = this;
              }
            }
            this.setPrivate_('__subContext__', ctx.createSubContext(m));
          }

          return this.getPrivate_('__subContext__');
        },
        configurable: true,
        enumerable: false
      });
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Model',
  properties: [
    {
      class: 'AxiomArray',
      of: 'Imports',
      name: 'imports',
      adaptArrayElement: function(o) {
        if ( typeof o === 'string' ) {
          var a = o.split(' as ');
          return foam.core.Imports.create({name: a[1] || a[0], key: a[0]});
        }

        return foam.core.Imports.create(o);
      }
    },
    {
      name: 'exports',
      postSet: function(_, xs) {
        this.axioms_.push.call(
          this.axioms_,
          foam.core.Exports.create({bindings: xs}));
      }
    }
  ]
});
<commit_msg>Formatting.<commit_after>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  Imports and Exports provide implicit Context dependency management.

  A class can list which values it requires from the Context, and then
  these values will be added to the object itself so that it doesn't need
  to explicitly work with the Context.

  A class can list which values (properties, methods, or method-like axioms)
  that it exports, and these will automatically be added to the object's
  sub-Context. The object's sub-Context is the context that is used when
  new objects are created by the object.

  Ex.:

foam.CLASS({
  name: 'ImportsTest',

  imports: [ 'log', 'warn' ],

  methods: [ function foo() {
    this.log('log foo from ImportTest');
    this.warn('warn foo from ImportTest');
  } ]
});

foam.CLASS({
  name: 'ExportsTest',
  requires: [ 'ImportsTest' ],

  exports: [ 'log', 'log as warn' ],

  methods: [
    function init() {
      // ImportsTest will be created in ExportTest's
      // sub-Context, which will have 'log' and 'warn'
      // exported.
      this.ImportsTest.create().foo();
    },
    function log(msg) {
      console.log('log:', msg);
    }
  ]
});

  Aliasing:
    Bindings can be renamed or aliased when they're imported or exported using
    'as alias'.

  Examples:
    // import 'userDAO' from the Context and make available as this.dao
    imports: [ 'userDAO as dao' ]

    // export my log method as 'warn'
    exports: [ 'log as warn' ]

    // If the axiom to be exported isn't named, but just aliased, then 'this'
    // is exported as the named alias.  This is how objects export themselves.
    exports: [ 'as Controller' ]

  See Context.js.
 */
foam.CLASS({
  package: 'foam.core',
  name: 'Imports',

  // documentation: 'Import Context Value Axiom',

  properties: [
    'name',
    'key',
    {
      name: 'slotName_',
      factory: function() { return this.name + '$'; }
    }
  ],

  methods: [
    function installInProto(proto) {
      var name     = this.name;
      var key      = this.key;
      var slotName = this.slotName_;

      Object.defineProperty(proto, slotName, {
        get: function importsGetter() {
          if ( ! this.hasOwnPrivate_(slotName) ) {
            var ctx = this.__context__ || foam.__context__;
            this.setPrivate_(slotName, ctx[key + '$']);
          }

          return this.getPrivate_(slotName);
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, name, {
        get: function importsGetter()  {
          var slot = this[slotName];
          if ( slot ) return slot.get();
          console.warn('Access missing import:', name);
          return undefined;
        },
        set: function importsSetter(v) {
          var slot = this[slotName];
          if ( slot ) slot.set(v);
          console.warn('Attempt to set missing import:', name);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      return obj[this.slotName_];
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'Exports',

  // documentation: 'Export Sub-Context Value Axiom',

  properties: [
    [ 'name', 'exports_' ],
    {
      name: 'bindings',
      adapt: function(_, bs) {
        var exps = [];
        for ( var i = 0 ; i < bs.length ; i++ ) {
          var b = bs[i];
          if ( typeof b === 'string' ) {
            var key, name, a = b.split(' ');
            switch ( a.length ) {
              case 1:
                key = name = a[0];
              break;
              case 2:
                console.assert(
                    a[0] === 'as',
                    'Invalid export syntax: key [as value] | as value');
                name = a[1]; // signifies 'this'
                key  = null;
              break;
              case 3:
                console.assert(
                    a[1] === 'as',
                    'Invalid export syntax: key [as value] | as value');
                name = a[2];
                key  = a[0];
              break;
              default:
                console.error(
                    'Invalid export syntax: key [as value] | as value');
            }
            exps[i] = { name: name, key: key };
          } else {
            exps[i] = bs[i];
          }
        }
        return exps;
      }
    }
  ],

  methods: [
    function installInProto(proto) {
      var bs = this.bindings;

      Object.defineProperty(proto, '__subContext__', {
        get: function YGetter() {
          if ( ! this.hasOwnPrivate_('__subContext__') ) {
            var ctx = this.__context__ || foam.__context__;
            var m = {};
            for ( var i = 0 ; i < bs.length ; i++ ) {
              var b = bs[i];

              if ( b.key ) {
                var a = this.cls_.getAxiomByName(b.key);

                if ( ! a ) {
                  console.error(
                    'Unknown export: "' +
                      b.key +
                      '" in model: ' +
                      this.cls_.id);
                }

                // Axioms have an option of wrapping a value for export.
                // This could be used to bind a method to 'this', for example.
                m[b.name] = a.exportAs ? a.exportAs(this) : this[b.key] ;
              } else {
                // Ex. 'as Bank', which exports an implicit 'this'
                m[b.name] = this;
              }
            }
            this.setPrivate_('__subContext__', ctx.createSubContext(m));
          }

          return this.getPrivate_('__subContext__');
        },
        configurable: true,
        enumerable: false
      });
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Model',
  properties: [
    {
      class: 'AxiomArray',
      of: 'Imports',
      name: 'imports',
      adaptArrayElement: function(o) {
        if ( typeof o === 'string' ) {
          var a = o.split(' as ');
          return foam.core.Imports.create({name: a[1] || a[0], key: a[0]});
        }

        return foam.core.Imports.create(o);
      }
    },
    {
      name: 'exports',
      postSet: function(_, xs) {
        this.axioms_.push.call(
          this.axioms_,
          foam.core.Exports.create({bindings: xs}));
      }
    }
  ]
});
<|endoftext|>


<commit_before>"use strict"

const mergeTestDescriptions = require("../../../testUtils/mergeTestDescriptions")

const messages = require("..").messages
const ruleName = require("..").ruleName
const rules = require("../../../rules")

const rule = rules[ruleName]

const basicAZTests = {
  accept: [ {
    code: "a {}",
  }, {
    code: "#foo {}",
  }, {
    code: "[foo='bar'] {}",
  }, {
    code: ".FOO {}",
  }, {
    code: "a #foo > [foo='bar'], .FOO {}",
  }, {
    code: "a /* .foo */ {}",
  }, {
    code: ":root { --custom-property-set: {} }",
  }, {
    code: "@keyframes a { 0%, 48.59% {} }",
    message: "Keyframes with decimal percentages",
  } ],

  reject: [ {
    code: "a .foo {}",
    message: messages.expected("foo"),
    line: 1,
    column: 3,
  }, {
    code: ".ABABA > .bar {}",
    message: messages.expected("bar"),
    line: 1,
    column: 10,
  } ],
}

const nestedAZTestsDefault = {
  accept: [ {
    code: ".AB { }",
  }, {
    code: ".A { &__B { }}",
  } ],
}

const nestedAZTests = {
  accept: [ {
    code: ".AB { }",
  }, {
    code: ".A { &B {}}",
  }, {
    code: ".A { & > B {}}",
  }, {
    code: ".A { &B {}, .C {}, &D {} }",
  }, {
    code: ".A, .B { &C {} &D, &E {} }",
  } ],

  reject: [{
    code: ".A { &__B { }}",
    message: messages.expected("A__B"),
    line: 0,
    column: 6,
  }],
}

testRule(rule, mergeTestDescriptions(basicAZTests, {
  ruleName,
  config: [/^[A-Z]+$/],
}))

testRule(rule, mergeTestDescriptions(basicAZTests, {
  ruleName,
  config: ["^[A-Z]+$"],
}))

testRule(rule, mergeTestDescriptions(nestedAZTestsDefault, {
  ruleName,
  config: [/^[A-Z]+$/],
}))

testRule(rule, mergeTestDescriptions(nestedAZTestsDefault, {
  ruleName,
  config: ["^[A-Z]+$"],
}))

testRule(rule, mergeTestDescriptions(nestedAZTests, {
  ruleName,
  config: [ /^[A-Z]+$/, { resolveNestedSelectors: true } ],
}))

testRule(rule, mergeTestDescriptions(nestedAZTests, {
  ruleName,
  config: [ "^[A-Z]+$", { resolveNestedSelectors: true } ],
}))

testRule(rule, {
  ruleName,
  config: [ /^B+$/, { resolveNestedSelectors: true } ],

  reject: [ {
    code: ".A { .B { } }",
    message: messages.expected("A"),
  }, {
    code: ".A { & .B { } }",
    message: messages.expected("A"),
  }, {
    code: ".A { &>.B { } }",
    message: messages.expected("A"),
  } ],
})

testRule(rule, {
  ruleName,
  syntax: "scss",
  config: [ /^[A-Z]+$/, { resolveNestedSelectors: true } ],

  accept: [ {
    code: "@for $n from 1 through 5 { .A#{$n} { } }",
    description: "ignore sass interpolation inside @for",
  }, {
    code: "@for $n from 1 through 5 { .A { &B#{$n} { } }}",
    description: "ignore sass interpolation of nested selector inside @for",
  }, {
    code: ".#{$a} {}",
    description: "ignore sass var interpolation",
  },
  {
    code: ".A#{$B} { &C {} &D, &E {} }",
    description: "ignore sass var interpolation for parents of nested selectors",
  } ],
})

testRule(rule, {
  ruleName,
  syntax: "less",
  config: [/^[A-Z]+$/],

  accept: [ {
    code: ".mixin-name() { }",
    description: "ignore non-ouputting Less class mixin definition",
  }, {
    code: ".A { .mixin-name; }",
    description: "ignore called Less class mixin",
  }, {
    code: ".A { .mixin-name(@var); }",
    description: "ignore called Less class parametric mixin",
  }, {
    code: "#mixin-name() { }",
    description: "ignore non-ouputting Less id mixin definition",
  }, {
    code: ".A { #mixin-name; }",
    description: "ignore called Less id mixin",
  }, {
    code: "#namespace { .mixin-name() { } }",
    description: "ignore namespaced non-ouputting Less class mixin definition",
  }, {
    code: ".A { #namespace > .mixin-name; }",
    description: "ignore called namespaced Less mixin (child)",
  }, {
    code: ".A { #namespace .mixin-name; }",
    description: "ignore called namespaced Less mixin (decendant)",
  }, {
    code: ".A { #namespace.mixin-name; }",
    description: "ignore called namespaced Less mixin (compounded)",
  } ],
})
<commit_msg>Fix brace position (#2347)<commit_after>"use strict"

const mergeTestDescriptions = require("../../../testUtils/mergeTestDescriptions")

const messages = require("..").messages
const ruleName = require("..").ruleName
const rules = require("../../../rules")

const rule = rules[ruleName]

const basicAZTests = {
  accept: [ {
    code: "a {}",
  }, {
    code: "#foo {}",
  }, {
    code: "[foo='bar'] {}",
  }, {
    code: ".FOO {}",
  }, {
    code: "a #foo > [foo='bar'], .FOO {}",
  }, {
    code: "a /* .foo */ {}",
  }, {
    code: ":root { --custom-property-set: {} }",
  }, {
    code: "@keyframes a { 0%, 48.59% {} }",
    message: "Keyframes with decimal percentages",
  } ],

  reject: [ {
    code: "a .foo {}",
    message: messages.expected("foo"),
    line: 1,
    column: 3,
  }, {
    code: ".ABABA > .bar {}",
    message: messages.expected("bar"),
    line: 1,
    column: 10,
  } ],
}

const nestedAZTestsDefault = {
  accept: [ {
    code: ".AB { }",
  }, {
    code: ".A { &__B { }}",
  } ],
}

const nestedAZTests = {
  accept: [ {
    code: ".AB { }",
  }, {
    code: ".A { &B {}}",
  }, {
    code: ".A { & > B {}}",
  }, {
    code: ".A { &B {}, .C {}, &D {} }",
  }, {
    code: ".A, .B { &C {} &D, &E {} }",
  } ],

  reject: [{
    code: ".A { &__B { }}",
    message: messages.expected("A__B"),
    line: 0,
    column: 6,
  }],
}

testRule(rule, mergeTestDescriptions(basicAZTests, {
  ruleName,
  config: [/^[A-Z]+$/],
}))

testRule(rule, mergeTestDescriptions(basicAZTests, {
  ruleName,
  config: ["^[A-Z]+$"],
}))

testRule(rule, mergeTestDescriptions(nestedAZTestsDefault, {
  ruleName,
  config: [/^[A-Z]+$/],
}))

testRule(rule, mergeTestDescriptions(nestedAZTestsDefault, {
  ruleName,
  config: ["^[A-Z]+$"],
}))

testRule(rule, mergeTestDescriptions(nestedAZTests, {
  ruleName,
  config: [ /^[A-Z]+$/, { resolveNestedSelectors: true } ],
}))

testRule(rule, mergeTestDescriptions(nestedAZTests, {
  ruleName,
  config: [ "^[A-Z]+$", { resolveNestedSelectors: true } ],
}))

testRule(rule, {
  ruleName,
  config: [ /^B+$/, { resolveNestedSelectors: true } ],

  reject: [ {
    code: ".A { .B { } }",
    message: messages.expected("A"),
  }, {
    code: ".A { & .B { } }",
    message: messages.expected("A"),
  }, {
    code: ".A { &>.B { } }",
    message: messages.expected("A"),
  } ],
})

testRule(rule, {
  ruleName,
  syntax: "scss",
  config: [ /^[A-Z]+$/, { resolveNestedSelectors: true } ],

  accept: [ {
    code: "@for $n from 1 through 5 { .A#{$n} { } }",
    description: "ignore sass interpolation inside @for",
  }, {
    code: "@for $n from 1 through 5 { .A { &B#{$n} { } }}",
    description: "ignore sass interpolation of nested selector inside @for",
  }, {
    code: ".#{$a} {}",
    description: "ignore sass var interpolation",
  }, {
    code: ".A#{$B} { &C {} &D, &E {} }",
    description: "ignore sass var interpolation for parents of nested selectors",
  } ],
})

testRule(rule, {
  ruleName,
  syntax: "less",
  config: [/^[A-Z]+$/],

  accept: [ {
    code: ".mixin-name() { }",
    description: "ignore non-ouputting Less class mixin definition",
  }, {
    code: ".A { .mixin-name; }",
    description: "ignore called Less class mixin",
  }, {
    code: ".A { .mixin-name(@var); }",
    description: "ignore called Less class parametric mixin",
  }, {
    code: "#mixin-name() { }",
    description: "ignore non-ouputting Less id mixin definition",
  }, {
    code: ".A { #mixin-name; }",
    description: "ignore called Less id mixin",
  }, {
    code: "#namespace { .mixin-name() { } }",
    description: "ignore namespaced non-ouputting Less class mixin definition",
  }, {
    code: ".A { #namespace > .mixin-name; }",
    description: "ignore called namespaced Less mixin (child)",
  }, {
    code: ".A { #namespace .mixin-name; }",
    description: "ignore called namespaced Less mixin (decendant)",
  }, {
    code: ".A { #namespace.mixin-name; }",
    description: "ignore called namespaced Less mixin (compounded)",
  } ],
})
<|endoftext|>


<commit_before>import update from 'react-addons-update'
import { getConfigProperty } from '../../common/util/config'
import { defaultSorter } from '../../common/util/util'

const projects = (state = {
  isFetching: false,
  all: null,
  active: null,
  filter: {
    searchText: null
  }
}, action) => {
  let projects, projectIndex, sourceIndex, versionIndex, activeProject, activeIndex, feeds, activeProjectId, project, newState, deployment
  switch (action.type) {
    case 'SET_PROJECT_VISIBILITY_SEARCH_TEXT':
      return update(state, {filter: {searchText: {$set: action.text}}})
    case 'SET_PROJECT_VISIBILITY_FILTER':
      return update(state, {filter: {filter: {$set: action.filter}}})
    case 'CREATE_PROJECT':
      projects = [{
        isCreating: true,
        name: '',
        ...action.project
      }, ...state.all]
      return update(state, { all: { $set: projects } })

    case 'CREATE_FEEDSOURCE':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)
      project = state.all[projectIndex]
      newState = null
      console.log('adding fs to project', state.all[projectIndex])

      // if project's feedSources array is undefined, add it
      if (!project.feedSources) {
        console.log('adding new fs array')
        newState = update(state, {all: {[projectIndex]: {$merge: {feedSources: []}}}})
      }

      // add new empty feed source to feedSources array
      const feedSource = {
        isCreating: true,
        name: '',
        projectId: project.id
      }
      return update(newState || state, {all: {[projectIndex]: {feedSources: {$unshift: [feedSource]}}}})

    case 'CREATE_DEPLOYMENT':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)
      project = state.all[projectIndex]
      newState = null
      // if project's deployment array is undefined, add it
      if (!project.deployments) {
        newState = update(state, {all: {[projectIndex]: {$merge: {deployments: []}}}})
      }
      // add new empty feed source to feedSources array
      deployment = {
        isCreating: true,
        name: '',
        project: project
      }
      return update(newState || state, {all: {[projectIndex]: {deployments: {$unshift: [deployment]}}}})

    case 'REQUESTING_FEEDSOURCE':
    case 'REQUESTING_FEEDSOURCES':
    case 'REQUEST_PROJECTS':
      return update(state, { isFetching: { $set: true } })
    case 'RECEIVE_PROJECTS':
      activeProjectId = state.active ? state.active.id : getConfigProperty('application.active_project')
      activeIndex = action.projects.findIndex(p => p.id === activeProjectId)
      if (activeIndex === -1) {
        activeIndex = 0
      }
      return {
        isFetching: false,
        all: action.projects,
        active: action.projects[activeIndex] && action.projects[activeIndex].id,
        filter: {
          searchText: null
        }
      }
    case 'SET_ACTIVE_PROJECT':
      return update(state, { active: { $set: action.project && action.project.id } })
    case 'RECEIVE_PROJECT':
      if (!action.project) {
        return state
      }
      if (!state.all) { // there are no current projects loaded
        projects = [action.project]
      } else { // projects already loaded
        projectIndex = state.all.findIndex(p => p.id === action.project.id)
        if (projectIndex === -1) { // projects loaded but not this one; add it
          projects = [
            ...state.all,
            action.project
          ]
        } else { // projects loaded including this one, replace it
          projects = [
            ...state.all.slice(0, projectIndex),
            action.project,
            ...state.all.slice(projectIndex + 1)
          ]
        }
      }
      activeProjectId = state.active ? state.active.id : getConfigProperty('application.active_project')
      activeProject = action.project.id === activeProjectId ? action.project : null
      if (state.active && !activeProject) { // active project already exists and received project does not match active project
        activeProject = state.active
      }
      return update(state, {active: { $set: activeProject }, all: { $set: projects }})

    case 'RECEIVE_FEEDSOURCES':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)

      // if lazy fetching the rest of the feed sources, don't overwrite current feed source
      if (state.all[projectIndex] && state.all[projectIndex].feedSources && state.all[projectIndex].feedSources.length === 1) {
        sourceIndex = action.feedSources.findIndex(fs => fs.id === state.all[projectIndex].feedSources[0].id)
        action.feedSources.splice(sourceIndex, 1)
        feeds = [
          ...state.all[projectIndex].feedSources,
          ...action.feedSources
        ]
      } else {
        feeds = action.feedSources
      }
      feeds = feeds.sort(defaultSorter)
      if (state.active && action.projectId === state.active.id) {
        return update(state, {
          isFetching: {$set: false},
          active: {$merge: {feedSources: feeds}},
          all: {
            [projectIndex]: {$merge: {feedSources: feeds}}
          }
        })
      } else if (feeds.length) { // if projectId does not match active project
        return update(state,
          {
            isFetching: {$set: false},
            all: {
              [projectIndex]: {$merge: {feedSources: feeds}}
            }
          }
        )
      } else {
        return update(state,
          {
            isFetching: {$set: false}
          }
        )
      }

    case 'RECEIVE_FEEDSOURCE':
      if (!action.feedSource) {
        return update(state, { isFetching: { $set: false } })
      }
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      const existingSources = state.all[projectIndex].feedSources || []
      let updatedSources
      sourceIndex = existingSources.findIndex(s => s.id === action.feedSource.id)
      if (sourceIndex === -1) { // source does not currently; add it
        updatedSources = [
          ...existingSources,
          action.feedSource
        ]
      } else { // existing feedsource array includes this one, replace it
        updatedSources = [
          ...existingSources.slice(0, sourceIndex),
          action.feedSource,
          ...existingSources.slice(sourceIndex + 1)
        ]
      }
      return update(state, {
        all: {
          [projectIndex]: {
            $merge: {
              feedSources: updatedSources
            }
          }
        },
        isFetching: { $set: false }
      })

    case 'RECEIVE_FEEDVERSIONS':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      const versionSort = (a, b) => {
        if (a.version < b.version) return -1
        if (a.version > b.version) return 1
        return 0
      }
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  feedVersions: action.feedVersions.sort(versionSort)
                }
              }
            }
          }
        }
      })

    case 'RECEIVE_FEEDVERSION':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      console.log(sourceIndex)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions
        ? state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
        : -1
      const existingVersions = state.all[projectIndex].feedSources[sourceIndex].feedVersions || []
      let updatedVersions
      if (versionIndex === -1) { // version does not currently; add it
        updatedVersions = [
          ...existingVersions,
          action.feedVersion
        ]
      } else { // existing feedversion array includes this one, replace it
        updatedVersions = [
          ...existingVersions.slice(0, versionIndex),
          action.feedVersion,
          ...existingVersions.slice(versionIndex + 1)
        ]
      }
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  feedVersions: updatedVersions
                }
              }
            }
          }
        }
      })
    case 'PUBLISHED_FEEDVERSION':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                publishedVersionId: {
                  $set: action.feedVersion.id
                }
              }
            }
          }
        }
      })
    case 'RECEIVE_VALIDATION_RESULT':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      // let result = {}
      // action.validationResult.map(error => {
      //   if (!result[error.file]) {
      //     result[error.file] = []
      //   }
      //   result[error.file].push(error)
      // })
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                feedVersions: {
                  [versionIndex]: {
                    $merge: {
                      validationResult: action.validationResult
                    }
                  }
                }
              }
            }
          }
        }
      })
    case 'RECEIVE_FEEDVERSION_ISOCHRONES':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      const { fromLat, fromLon, date, fromTime, toTime } = action
      action.isochrones.properties = { fromLat, fromLon, date, fromTime, toTime }
      action.isochrones.features = action.isochrones.features.map(f => {
        f.type = 'Feature'
        return f
      })
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                feedVersions: {
                  [versionIndex]: {
                    $merge: {
                      isochrones: action.isochrones
                    }
                  }
                }
              }
            }
          }
        }
      })
    case 'RECEIVE_DEPLOYMENTS':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)
      if (state.active && action.projectId === state.active.id) {
        return update(state, {
          active: {
            $merge: {
              deployments: action.deployments
            }
          },
          all: {
            [projectIndex]: {
              $merge: {
                deployments: action.deployments
              }
            }
          }
        })
      } else { // if projectId does not match active project
        return update(state, {
          all: {
            [projectIndex]: {
              $merge: {
                deployments: action.deployments
              }
            }
          }
        })
      }
    case 'RECEIVE_DEPLOYMENT':
      projectIndex = state.all.findIndex(p => p.id === action.deployment.project.id)
      const existingDeployments = state.all[projectIndex].deployments || []
      let updatedDeployments
      sourceIndex = existingDeployments.findIndex(s => s.id === action.deployment.id)
      if (sourceIndex === -1) { // source does not currently; add it
        updatedDeployments = [
          ...existingDeployments,
          action.deployment
        ]
      } else { // existing feedsource array includes this one, replace it
        updatedDeployments = [
          ...existingDeployments.slice(0, sourceIndex),
          action.deployment,
          ...existingDeployments.slice(sourceIndex + 1)
        ]
      }
      return update(state, {
        all: {
          [projectIndex]: {
            $merge: {
              deployments: updatedDeployments
            }
          }
        }
      })
    case 'RECEIVE_NOTES_FOR_FEEDSOURCE':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  notes: action.notes
                }
              }
            }
          }
        }
      })

    case 'RECEIVE_NOTES_FOR_FEEDVERSION':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                feedVersions: {
                  [versionIndex]: {
                    $merge: {
                      notes: action.notes
                    }
                  }
                }
              }
            }
          }
        }
      })

    case 'RECEIVE_GTFSEDITOR_SNAPSHOTS':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  editorSnapshots: action.snapshots
                }
              }
            }
          }
        }
      })

    default:
      return state
  }
}

export default projects
<commit_msg>fix(project-reducer): check isochrone features before mapping<commit_after>import update from 'react-addons-update'
import { getConfigProperty } from '../../common/util/config'
import { defaultSorter } from '../../common/util/util'

const projects = (state = {
  isFetching: false,
  all: null,
  active: null,
  filter: {
    searchText: null
  }
}, action) => {
  let projects, projectIndex, sourceIndex, versionIndex, activeProject, activeIndex, feeds, activeProjectId, project, newState, deployment
  switch (action.type) {
    case 'SET_PROJECT_VISIBILITY_SEARCH_TEXT':
      return update(state, {filter: {searchText: {$set: action.text}}})
    case 'SET_PROJECT_VISIBILITY_FILTER':
      return update(state, {filter: {filter: {$set: action.filter}}})
    case 'CREATE_PROJECT':
      projects = [{
        isCreating: true,
        name: '',
        ...action.project
      }, ...state.all]
      return update(state, { all: { $set: projects } })

    case 'CREATE_FEEDSOURCE':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)
      project = state.all[projectIndex]
      newState = null
      console.log('adding fs to project', state.all[projectIndex])

      // if project's feedSources array is undefined, add it
      if (!project.feedSources) {
        console.log('adding new fs array')
        newState = update(state, {all: {[projectIndex]: {$merge: {feedSources: []}}}})
      }

      // add new empty feed source to feedSources array
      const feedSource = {
        isCreating: true,
        name: '',
        projectId: project.id
      }
      return update(newState || state, {all: {[projectIndex]: {feedSources: {$unshift: [feedSource]}}}})

    case 'CREATE_DEPLOYMENT':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)
      project = state.all[projectIndex]
      newState = null
      // if project's deployment array is undefined, add it
      if (!project.deployments) {
        newState = update(state, {all: {[projectIndex]: {$merge: {deployments: []}}}})
      }
      // add new empty feed source to feedSources array
      deployment = {
        isCreating: true,
        name: '',
        project: project
      }
      return update(newState || state, {all: {[projectIndex]: {deployments: {$unshift: [deployment]}}}})

    case 'REQUESTING_FEEDSOURCE':
    case 'REQUESTING_FEEDSOURCES':
    case 'REQUEST_PROJECTS':
      return update(state, { isFetching: { $set: true } })
    case 'RECEIVE_PROJECTS':
      activeProjectId = state.active ? state.active.id : getConfigProperty('application.active_project')
      activeIndex = action.projects.findIndex(p => p.id === activeProjectId)
      if (activeIndex === -1) {
        activeIndex = 0
      }
      return {
        isFetching: false,
        all: action.projects,
        active: action.projects[activeIndex] && action.projects[activeIndex].id,
        filter: {
          searchText: null
        }
      }
    case 'SET_ACTIVE_PROJECT':
      return update(state, { active: { $set: action.project && action.project.id } })
    case 'RECEIVE_PROJECT':
      if (!action.project) {
        return state
      }
      if (!state.all) { // there are no current projects loaded
        projects = [action.project]
      } else { // projects already loaded
        projectIndex = state.all.findIndex(p => p.id === action.project.id)
        if (projectIndex === -1) { // projects loaded but not this one; add it
          projects = [
            ...state.all,
            action.project
          ]
        } else { // projects loaded including this one, replace it
          projects = [
            ...state.all.slice(0, projectIndex),
            action.project,
            ...state.all.slice(projectIndex + 1)
          ]
        }
      }
      activeProjectId = state.active ? state.active.id : getConfigProperty('application.active_project')
      activeProject = action.project.id === activeProjectId ? action.project : null
      if (state.active && !activeProject) { // active project already exists and received project does not match active project
        activeProject = state.active
      }
      return update(state, {active: { $set: activeProject }, all: { $set: projects }})

    case 'RECEIVE_FEEDSOURCES':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)

      // if lazy fetching the rest of the feed sources, don't overwrite current feed source
      if (state.all[projectIndex] && state.all[projectIndex].feedSources && state.all[projectIndex].feedSources.length === 1) {
        sourceIndex = action.feedSources.findIndex(fs => fs.id === state.all[projectIndex].feedSources[0].id)
        action.feedSources.splice(sourceIndex, 1)
        feeds = [
          ...state.all[projectIndex].feedSources,
          ...action.feedSources
        ]
      } else {
        feeds = action.feedSources
      }
      feeds = feeds.sort(defaultSorter)
      if (state.active && action.projectId === state.active.id) {
        return update(state, {
          isFetching: {$set: false},
          active: {$merge: {feedSources: feeds}},
          all: {
            [projectIndex]: {$merge: {feedSources: feeds}}
          }
        })
      } else if (feeds.length) { // if projectId does not match active project
        return update(state,
          {
            isFetching: {$set: false},
            all: {
              [projectIndex]: {$merge: {feedSources: feeds}}
            }
          }
        )
      } else {
        return update(state,
          {
            isFetching: {$set: false}
          }
        )
      }

    case 'RECEIVE_FEEDSOURCE':
      if (!action.feedSource) {
        return update(state, { isFetching: { $set: false } })
      }
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      const existingSources = state.all[projectIndex].feedSources || []
      let updatedSources
      sourceIndex = existingSources.findIndex(s => s.id === action.feedSource.id)
      if (sourceIndex === -1) { // source does not currently; add it
        updatedSources = [
          ...existingSources,
          action.feedSource
        ]
      } else { // existing feedsource array includes this one, replace it
        updatedSources = [
          ...existingSources.slice(0, sourceIndex),
          action.feedSource,
          ...existingSources.slice(sourceIndex + 1)
        ]
      }
      return update(state, {
        all: {
          [projectIndex]: {
            $merge: {
              feedSources: updatedSources
            }
          }
        },
        isFetching: { $set: false }
      })

    case 'RECEIVE_FEEDVERSIONS':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      const versionSort = (a, b) => {
        if (a.version < b.version) return -1
        if (a.version > b.version) return 1
        return 0
      }
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  feedVersions: action.feedVersions.sort(versionSort)
                }
              }
            }
          }
        }
      })

    case 'RECEIVE_FEEDVERSION':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      console.log(sourceIndex)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions
        ? state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
        : -1
      const existingVersions = state.all[projectIndex].feedSources[sourceIndex].feedVersions || []
      let updatedVersions
      if (versionIndex === -1) { // version does not currently; add it
        updatedVersions = [
          ...existingVersions,
          action.feedVersion
        ]
      } else { // existing feedversion array includes this one, replace it
        updatedVersions = [
          ...existingVersions.slice(0, versionIndex),
          action.feedVersion,
          ...existingVersions.slice(versionIndex + 1)
        ]
      }
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  feedVersions: updatedVersions
                }
              }
            }
          }
        }
      })
    case 'PUBLISHED_FEEDVERSION':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                publishedVersionId: {
                  $set: action.feedVersion.id
                }
              }
            }
          }
        }
      })
    case 'RECEIVE_VALIDATION_RESULT':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      // let result = {}
      // action.validationResult.map(error => {
      //   if (!result[error.file]) {
      //     result[error.file] = []
      //   }
      //   result[error.file].push(error)
      // })
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                feedVersions: {
                  [versionIndex]: {
                    $merge: {
                      validationResult: action.validationResult
                    }
                  }
                }
              }
            }
          }
        }
      })
    case 'RECEIVE_FEEDVERSION_ISOCHRONES':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      const { fromLat, fromLon, date, fromTime, toTime } = action
      action.isochrones.properties = { fromLat, fromLon, date, fromTime, toTime }
      action.isochrones.features = action.isochrones && action.isochrones.features
        ? action.isochrones.features.map(f => {
          f.type = 'Feature'
          return f
        })
        : null
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                feedVersions: {
                  [versionIndex]: {
                    $merge: {
                      isochrones: action.isochrones
                    }
                  }
                }
              }
            }
          }
        }
      })
    case 'RECEIVE_DEPLOYMENTS':
      projectIndex = state.all.findIndex(p => p.id === action.projectId)
      if (state.active && action.projectId === state.active.id) {
        return update(state, {
          active: {
            $merge: {
              deployments: action.deployments
            }
          },
          all: {
            [projectIndex]: {
              $merge: {
                deployments: action.deployments
              }
            }
          }
        })
      } else { // if projectId does not match active project
        return update(state, {
          all: {
            [projectIndex]: {
              $merge: {
                deployments: action.deployments
              }
            }
          }
        })
      }
    case 'RECEIVE_DEPLOYMENT':
      projectIndex = state.all.findIndex(p => p.id === action.deployment.project.id)
      const existingDeployments = state.all[projectIndex].deployments || []
      let updatedDeployments
      sourceIndex = existingDeployments.findIndex(s => s.id === action.deployment.id)
      if (sourceIndex === -1) { // source does not currently; add it
        updatedDeployments = [
          ...existingDeployments,
          action.deployment
        ]
      } else { // existing feedsource array includes this one, replace it
        updatedDeployments = [
          ...existingDeployments.slice(0, sourceIndex),
          action.deployment,
          ...existingDeployments.slice(sourceIndex + 1)
        ]
      }
      return update(state, {
        all: {
          [projectIndex]: {
            $merge: {
              deployments: updatedDeployments
            }
          }
        }
      })
    case 'RECEIVE_NOTES_FOR_FEEDSOURCE':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  notes: action.notes
                }
              }
            }
          }
        }
      })

    case 'RECEIVE_NOTES_FOR_FEEDVERSION':
      projectIndex = state.all.findIndex(p => p.id === action.feedVersion.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedVersion.feedSource.id)
      versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                feedVersions: {
                  [versionIndex]: {
                    $merge: {
                      notes: action.notes
                    }
                  }
                }
              }
            }
          }
        }
      })

    case 'RECEIVE_GTFSEDITOR_SNAPSHOTS':
      projectIndex = state.all.findIndex(p => p.id === action.feedSource.projectId)
      sourceIndex = state.all[projectIndex].feedSources.findIndex(s => s.id === action.feedSource.id)
      return update(state, {
        all: {
          [projectIndex]: {
            feedSources: {
              [sourceIndex]: {
                $merge: {
                  editorSnapshots: action.snapshots
                }
              }
            }
          }
        }
      })

    default:
      return state
  }
}

export default projects
<|endoftext|>


<commit_before>console.log("contentscript.js invoked");

// declare CSS selectors
var classSelector = ".cssClassContainer";
var daySelector = ".cssTtbleColDay";
var subjectSelector = ".cssTtableSspNavContainer"

// build subject code -> subject name dictionary
var subjectMap = {};
console.log("Building subject code->name map");
$(".cssTtableSspNavContainer").each(function() {
	var cc = $(".cssTtableSspNavMasterSpkInfo2 span", this).text();
	var name =  $(".cssTtableSspNavMasterSpkInfo3 div:first", this).text().trim();
	console.log(cc+" -> "+name);
	subjectMap[cc]=name;
});

// gather page data
var message = {
	"greeting": "pageData",
	"classCount": document.querySelectorAll(classSelector).length,
	"subjectCount": document.querySelectorAll(subjectSelector).length,
	"subjectMap": subjectMap
};

// send page data to the popup
chrome.runtime.sendMessage(message, function(response) {
	console.log(response.farewell);
});

var makeIcs = function(startDate, endDate, weekEvents) {
	// parameter defaults
	if (weekEvents !== true) weekEvents = false;

	// set up semester boundaries
	var startingDate = new Date(startDate);
	var endingDate = new Date(endDate);
	var breakStartDate = new Date("6 April, 2015");

	var dateFormat = function(rawtime, day) {
		// date fields on the timetable page don't match dateSting format for JavaScript
		// need to add a space before am/pm and seconds field
		// rawtime is attached to the given day (date) and returned as a string
		var slicePosition = rawtime.length - 2;
		var newdate = rawtime.slice(0,slicePosition)+':00 '+ rawtime.slice(slicePosition,rawtime.length);
		return day.toDateString() + " " + newdate;
	};

	// initialise icalendar library
	var cal = ics();

	// If there is a Sunday in the timetable, it appears first.
	// It's easier to assume Monday is the first day of the week
	// so this lookup table is used to deal with the day order.

	var daysIndex = {
		"Monday": 0,
		"Tuesday": 1,
		"Wednesday": 2,
		"Thursday": 3,
		"Friday": 4,
		"Saturday": 5,
		"Sunday": 6
	};

	$(daySelector).each(function(index, element){
		// iterating over each weekday present in the timetable

		// find out what day we're iterating over
		var day = $(element).find(".cssTtbleColHeaderInner").text().trim();
		var dayIndex = daysIndex[day];

		if (dayIndex == undefined) {
			// something went wrong
			console.log("Error! Invalid day '"+day+"' found in timetable. Skipping");
			return;
		}

		console.log("Processing day #"+dayIndex+" ("+day+")");

		// set up variable for the day of the week
		var weekDay = new Date(startingDate.getTime() + dayIndex*(24 * 60 * 60 * 1000));

		// set up variable for the mid-semester break offset (used for exlusion)
		var exludeDay = new Date(breakStartDate.getTime() + dayIndex*(24 * 60 * 60 * 1000));

		$(classSelector, this).each(function(index){
			// iterating over each subject in a weekday
			// collect data
			var subjectCode = $(".cssTtableHeaderPanel", this).text().replace(/[\n\t\r]/g,"");
			// add subject name if found
			if (subjectMap[subjectCode]!==undefined) subjectCode += " "+subjectMap[subjectCode];
			var name = $(".cssTtableClsSlotWhat", this).text().trim();
			var start = dateFormat($(".cssHiddenStartTm", this).val(),weekDay);
			var end = dateFormat($(".cssHiddenEndTm", this).val(),weekDay);
			var location = $(".cssTtableClsSlotWhere", this).text();
			// determine exlusion date
			var exludeDate = dateFormat($(".cssHiddenStartTm", this).val(),exludeDay);
			// log and process into event
			console.log(subjectCode + " " + name + " @ " + location + " \n" + start + " -> " + end + " except "+exludeDate);
			cal.addEvent(subjectCode+" "+name, name, location, start, end, endingDate, exludeDate);
		});
	});

	if (weekEvents) {
		// create an event for each week #
		var passedBreak = false;

		for(var weeknum=1;weeknum<=13;weeknum++) {
			// break week doesn't count as a week
			// need to adjust if we're past it
			var actualWeekNum = weeknum;
			if (passedBreak) {
				actualWeekNum--;
			}

			var eventName = "Week #"+actualWeekNum.toString();
			var eventDescription = "University calendar week "+actualWeekNum.toString()

			var weekStartDate = new Date(startingDate.toDateString());
			weekStartDate.setDate(weekStartDate.getDate()+7*(weeknum-1));

			if (weekStartDate.toDateString() == breakStartDate.toDateString()) {
				//this week is the break week
				passedBreak = true;
				eventName = "Mid-semester break";
				eventDescription = "University non-teaching period";
			}

			var weekEndDate = new Date(weekStartDate.toDateString());
			weekEndDate.setDate(weekStartDate.getDate()+5);

			cal.addEvent(eventName, eventDescription, "", weekStartDate, weekEndDate);
		}
	}

	// download ics
	cal.download("yourCal");
};

// attach listener from popup
var attached;

if ( typeof(attached) === 'undefined' ) {
	attached = true;
	chrome.runtime.onMessage.addListener(
		function(request, sender, sendResponse) {
			console.log("got message from popup");
			if (request.greeting == "makeIcs") {
				makeIcs(
					request.startDate,
					request.endDate,
					request.weeklyEvents
				);
				sendResponse({farewell: "executed makeIcs()"});
			}
		}
	);
}<commit_msg>Quick hack to update semester break for 2016sem1<commit_after>console.log("contentscript.js invoked");

// declare CSS selectors
var classSelector = ".cssClassContainer";
var daySelector = ".cssTtbleColDay";
var subjectSelector = ".cssTtableSspNavContainer"

// build subject code -> subject name dictionary
var subjectMap = {};
console.log("Building subject code->name map");
$(".cssTtableSspNavContainer").each(function() {
	var cc = $(".cssTtableSspNavMasterSpkInfo2 span", this).text();
	var name =  $(".cssTtableSspNavMasterSpkInfo3 div:first", this).text().trim();
	console.log(cc+" -> "+name);
	subjectMap[cc]=name;
});

// gather page data
var message = {
	"greeting": "pageData",
	"classCount": document.querySelectorAll(classSelector).length,
	"subjectCount": document.querySelectorAll(subjectSelector).length,
	"subjectMap": subjectMap
};

// send page data to the popup
chrome.runtime.sendMessage(message, function(response) {
	console.log(response.farewell);
});

var makeIcs = function(startDate, endDate, weekEvents) {
	// parameter defaults
	if (weekEvents !== true) weekEvents = false;

	// set up semester boundaries
	var startingDate = new Date(startDate);
	var endingDate = new Date(endDate);
	var breakStartDate = new Date("28 March, 2016");

	var dateFormat = function(rawtime, day) {
		// date fields on the timetable page don't match dateSting format for JavaScript
		// need to add a space before am/pm and seconds field
		// rawtime is attached to the given day (date) and returned as a string
		var slicePosition = rawtime.length - 2;
		var newdate = rawtime.slice(0,slicePosition)+':00 '+ rawtime.slice(slicePosition,rawtime.length);
		return day.toDateString() + " " + newdate;
	};

	// initialise icalendar library
	var cal = ics();

	// If there is a Sunday in the timetable, it appears first.
	// It's easier to assume Monday is the first day of the week
	// so this lookup table is used to deal with the day order.

	var daysIndex = {
		"Monday": 0,
		"Tuesday": 1,
		"Wednesday": 2,
		"Thursday": 3,
		"Friday": 4,
		"Saturday": 5,
		"Sunday": 6
	};

	$(daySelector).each(function(index, element){
		// iterating over each weekday present in the timetable

		// find out what day we're iterating over
		var day = $(element).find(".cssTtbleColHeaderInner").text().trim();
		var dayIndex = daysIndex[day];

		if (dayIndex == undefined) {
			// something went wrong
			console.log("Error! Invalid day '"+day+"' found in timetable. Skipping");
			return;
		}

		console.log("Processing day #"+dayIndex+" ("+day+")");

		// set up variable for the day of the week
		var weekDay = new Date(startingDate.getTime() + dayIndex*(24 * 60 * 60 * 1000));

		// set up variable for the mid-semester break offset (used for exlusion)
		var exludeDay = new Date(breakStartDate.getTime() + dayIndex*(24 * 60 * 60 * 1000));

		$(classSelector, this).each(function(index){
			// iterating over each subject in a weekday
			// collect data
			var subjectCode = $(".cssTtableHeaderPanel", this).text().replace(/[\n\t\r]/g,"");
			// add subject name if found
			if (subjectMap[subjectCode]!==undefined) subjectCode += " "+subjectMap[subjectCode];
			var name = $(".cssTtableClsSlotWhat", this).text().trim();
			var start = dateFormat($(".cssHiddenStartTm", this).val(),weekDay);
			var end = dateFormat($(".cssHiddenEndTm", this).val(),weekDay);
			var location = $(".cssTtableClsSlotWhere", this).text();
			// determine exlusion date
			var exludeDate = dateFormat($(".cssHiddenStartTm", this).val(),exludeDay);
			// log and process into event
			console.log(subjectCode + " " + name + " @ " + location + " \n" + start + " -> " + end + " except "+exludeDate);
			cal.addEvent(subjectCode+" "+name, name, location, start, end, endingDate, exludeDate);
		});
	});

	if (weekEvents) {
		// create an event for each week #
		var passedBreak = false;

		for(var weeknum=1;weeknum<=13;weeknum++) {
			// break week doesn't count as a week
			// need to adjust if we're past it
			var actualWeekNum = weeknum;
			if (passedBreak) {
				actualWeekNum--;
			}

			var eventName = "Week #"+actualWeekNum.toString();
			var eventDescription = "University calendar week "+actualWeekNum.toString()

			var weekStartDate = new Date(startingDate.toDateString());
			weekStartDate.setDate(weekStartDate.getDate()+7*(weeknum-1));

			if (weekStartDate.toDateString() == breakStartDate.toDateString()) {
				//this week is the break week
				passedBreak = true;
				eventName = "Mid-semester break";
				eventDescription = "University non-teaching period";
			}

			var weekEndDate = new Date(weekStartDate.toDateString());
			weekEndDate.setDate(weekStartDate.getDate()+5);

			cal.addEvent(eventName, eventDescription, "", weekStartDate, weekEndDate);
		}
	}

	// download ics
	cal.download("yourCal");
};

// attach listener from popup
var attached;

if ( typeof(attached) === 'undefined' ) {
	attached = true;
	chrome.runtime.onMessage.addListener(
		function(request, sender, sendResponse) {
			console.log("got message from popup");
			if (request.greeting == "makeIcs") {
				makeIcs(
					request.startDate,
					request.endDate,
					request.weeklyEvents
				);
				sendResponse({farewell: "executed makeIcs()"});
			}
		}
	);
}<|endoftext|>