<commit_before>// domo.js 0.5.5

// (c) 2012 Jed Schmidt
// domo.js is distributed under the MIT license.
// For more details, see http://domo-js.com

!function() {
  // Determine the global object.
  var global = Function("return this")()

  // Valid HTML5 tag names used to generate DOM functions.
  var tags = [
    "A", "ABBR", "ACRONYM", "ADDRESS", "AREA", "ARTICLE", "ASIDE", "AUDIO",
    "B", "BDI", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BR", "BUTTON",
    "CANVAS", "CAPTION", "CITE", "CODE", "COL", "COLGROUP", "COMMAND",
    "DATALIST", "DD", "DEL", "DETAILS", "DFN", "DIV", "DL", "DT", "EM",
    "EMBED", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "FRAME",
    "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HEADER",
    "HGROUP", "HR", "HTML", "I", "IFRAME", "IMG", "INPUT", "INS", "KBD",
    "KEYGEN", "LABEL", "LEGEND", "LI", "LINK", "MAP", "MARK", "META",
    "METER", "NAV", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP", "OPTION",
    "OUTPUT", "P", "PARAM", "PRE", "PROGRESS", "Q", "RP", "RT", "RUBY",
    "SAMP", "SCRIPT", "SECTION", "SELECT", "SMALL", "SOURCE", "SPAN",
    "SPLIT", "STRONG", "STYLE", "SUB", "SUMMARY", "SUP", "TABLE", "TBODY",
    "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TIME", "TITLE", "TR",
    "TRACK", "TT", "UL", "VAR", "VIDEO", "WBR"
  ]

  // Turn a camelCase string into a hyphenated one.
  // Used for CSS property names and DOM element attributes.
  function hyphenify(text) {
    return text.replace(/[A-Z]/g, "-$&").toLowerCase()
  }

  // Cache select Array/Object methods
  var unshift = Array.prototype.unshift
  var concat = Array.prototype.concat
  var has = Object.prototype.hasOwnProperty

  // Export the Domo constructor for a CommonJS environment,
  // or create a new Domo namespace otherwise.
  typeof module == "object"
    ? module.exports = Domo
    : new Domo(global.document).global(true)

  // Create a new domo namespace, scoped to the given document.
  function Domo(document) {
    if (!document) throw new Error("No document provided.")

    this.domo = this

    // Create a DOM comment
    this.COMMENT = function(nodeValue) {
      return document.createComment(nodeValue)
    }

    // Create a DOM text node
    this.TEXT = function(nodeValue) {
      return document.createTextNode(nodeValue)
    }

    // Create a DOM fragment
    this.FRAGMENT = function() {
      var fragment = document.createDocumentFragment()
      var childNodes = concat.apply([], arguments)
      var length = childNodes.length
      var i = 0
      var child

      while (i < length) {
        child = childNodes[i++]

        while (typeof child == "function") child = child()

        if (child == null) child = this.COMMENT(child)

        else if (!child.nodeType) child = this.TEXT(child)

        fragment.appendChild(child)
      }

      return fragment
    }

    // Create a DOM element
    this.ELEMENT = function() {
      var childNodes = concat.apply([], arguments)
      var nodeName = childNodes.shift()
      var element = document.createElement(nodeName)
      var attributes = childNodes[0]

      if (attributes) {
        if (typeof attributes == "object" && !attributes.nodeType) {
          for (var name in attributes) if (has.call(attributes, name)) {
            element.setAttribute(hyphenify(name), attributes[name])
          }

          childNodes.shift()
        }
      }

      element.appendChild(
        this.FRAGMENT.apply(this, childNodes)
      )

      switch (nodeName) {
        case "HTML":
        case "HEAD":
        case "BODY":
          var replaced = document.getElementsByTagName(nodeName)[0]

          if (replaced) replaced.parentNode.replaceChild(element, replaced)
      }

      return element
    }

    // Convenience functions to create each HTML5 element
    var i = tags.length
    while (i--) !function(domo, nodeName) {
      domo[nodeName] =
      domo[nodeName.toLowerCase()] =

      function() {
        unshift.call(arguments, nodeName)
        return domo.ELEMENT.apply(domo, arguments)
      }
    }(this, tags[i])

    // Create a CSS style rule
    this.STYLE.on = function() {
      var rules = concat.apply([], arguments)
      var selector = rules[0]
      var css = selector + "{"
      var i = 1
      var l = rules.length
      var key
      var block

      while (i < l) {
        block = rules[i++]

        switch (typeof block) {
          case "object":
            for (key in block) {
              css += hyphenify(key) + ":" + block[key]

              if (typeof block[key] == "number") css += "px"

              css += ";"
            }
            break

          case "string":
            css = selector + " " + block + css
            break
        }
      }

      css += "}\n"

      return css
    }

    // Pollute the global scope for convenience.
    this.global = function(on) {
      var values = this.global.values
      var key
      var code

      if (on !== false) {
        global.domo = this

        for (key in this) {
          code = key.charCodeAt(0)

          if (code < 65 || code > 90) continue

          if (this[key] == global[key]) continue

          if (key in global) values[key] = global[key]

          global[key] = this[key]
        }
      }

      else {
        delete global.domo

        for (key in this) {
          if (key in values) {
            if (global[key] == this[key]) global[key] = values[key]
          }

          else delete global[key]
        }
      }

      return this
    }

    // A place to store previous global properties
    this.global.values = {}
  }
}()
<commit_msg>bump version<commit_after>// domo.js 0.5.6

// (c) 2012 Jed Schmidt
// domo.js is distributed under the MIT license.
// For more details, see http://domo-js.com

!function() {
  // Determine the global object.
  var global = Function("return this")()

  // Valid HTML5 tag names used to generate DOM functions.
  var tags = [
    "A", "ABBR", "ACRONYM", "ADDRESS", "AREA", "ARTICLE", "ASIDE", "AUDIO",
    "B", "BDI", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BR", "BUTTON",
    "CANVAS", "CAPTION", "CITE", "CODE", "COL", "COLGROUP", "COMMAND",
    "DATALIST", "DD", "DEL", "DETAILS", "DFN", "DIV", "DL", "DT", "EM",
    "EMBED", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "FRAME",
    "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HEADER",
    "HGROUP", "HR", "HTML", "I", "IFRAME", "IMG", "INPUT", "INS", "KBD",
    "KEYGEN", "LABEL", "LEGEND", "LI", "LINK", "MAP", "MARK", "META",
    "METER", "NAV", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP", "OPTION",
    "OUTPUT", "P", "PARAM", "PRE", "PROGRESS", "Q", "RP", "RT", "RUBY",
    "SAMP", "SCRIPT", "SECTION", "SELECT", "SMALL", "SOURCE", "SPAN",
    "SPLIT", "STRONG", "STYLE", "SUB", "SUMMARY", "SUP", "TABLE", "TBODY",
    "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TIME", "TITLE", "TR",
    "TRACK", "TT", "UL", "VAR", "VIDEO", "WBR"
  ]

  // Turn a camelCase string into a hyphenated one.
  // Used for CSS property names and DOM element attributes.
  function hyphenify(text) {
    return text.replace(/[A-Z]/g, "-$&").toLowerCase()
  }

  // Cache select Array/Object methods
  var unshift = Array.prototype.unshift
  var concat = Array.prototype.concat
  var has = Object.prototype.hasOwnProperty

  // Export the Domo constructor for a CommonJS environment,
  // or create a new Domo namespace otherwise.
  typeof module == "object"
    ? module.exports = Domo
    : new Domo(global.document).global(true)

  // Create a new domo namespace, scoped to the given document.
  function Domo(document) {
    if (!document) throw new Error("No document provided.")

    this.domo = this

    // Create a DOM comment
    this.COMMENT = function(nodeValue) {
      return document.createComment(nodeValue)
    }

    // Create a DOM text node
    this.TEXT = function(nodeValue) {
      return document.createTextNode(nodeValue)
    }

    // Create a DOM fragment
    this.FRAGMENT = function() {
      var fragment = document.createDocumentFragment()
      var childNodes = concat.apply([], arguments)
      var length = childNodes.length
      var i = 0
      var child

      while (i < length) {
        child = childNodes[i++]

        while (typeof child == "function") child = child()

        if (child == null) child = this.COMMENT(child)

        else if (!child.nodeType) child = this.TEXT(child)

        fragment.appendChild(child)
      }

      return fragment
    }

    // Create a DOM element
    this.ELEMENT = function() {
      var childNodes = concat.apply([], arguments)
      var nodeName = childNodes.shift()
      var element = document.createElement(nodeName)
      var attributes = childNodes[0]

      if (attributes) {
        if (typeof attributes == "object" && !attributes.nodeType) {
          for (var name in attributes) if (has.call(attributes, name)) {
            element.setAttribute(hyphenify(name), attributes[name])
          }

          childNodes.shift()
        }
      }

      element.appendChild(
        this.FRAGMENT.apply(this, childNodes)
      )

      switch (nodeName) {
        case "HTML":
        case "HEAD":
        case "BODY":
          var replaced = document.getElementsByTagName(nodeName)[0]

          if (replaced) replaced.parentNode.replaceChild(element, replaced)
      }

      return element
    }

    // Convenience functions to create each HTML5 element
    var i = tags.length
    while (i--) !function(domo, nodeName) {
      domo[nodeName] =
      domo[nodeName.toLowerCase()] =

      function() {
        unshift.call(arguments, nodeName)
        return domo.ELEMENT.apply(domo, arguments)
      }
    }(this, tags[i])

    // Create a CSS style rule
    this.STYLE.on = function() {
      var rules = concat.apply([], arguments)
      var selector = rules[0]
      var css = selector + "{"
      var i = 1
      var l = rules.length
      var key
      var block

      while (i < l) {
        block = rules[i++]

        switch (typeof block) {
          case "object":
            for (key in block) {
              css += hyphenify(key) + ":" + block[key]

              if (typeof block[key] == "number") css += "px"

              css += ";"
            }
            break

          case "string":
            css = selector + " " + block + css
            break
        }
      }

      css += "}\n"

      return css
    }

    // Pollute the global scope for convenience.
    this.global = function(on) {
      var values = this.global.values
      var key
      var code

      if (on !== false) {
        global.domo = this

        for (key in this) {
          code = key.charCodeAt(0)

          if (code < 65 || code > 90) continue

          if (this[key] == global[key]) continue

          if (key in global) values[key] = global[key]

          global[key] = this[key]
        }
      }

      else {
        delete global.domo

        for (key in this) {
          if (key in values) {
            if (global[key] == this[key]) global[key] = values[key]
          }

          else delete global[key]
        }
      }

      return this
    }

    // A place to store previous global properties
    this.global.values = {}
  }
}()
<|endoftext|>


<commit_before>"use strict";

var Q = require('q');
var async = require('async');
var marked = require('marked');
var mjAPI = require("mathjax-node");
var pangunode = require('pangunode');
var ejs = require('ejs');
var ncp = require('ncp');
var recursive = require('recursive-readdir');
var mkdirp = require('mkdirp');
var fs = require('fs');
var path = require('path');
var cwd = process.cwd();
var exec = require('child_process').exec;
var config = require('./config')();

var ncpOpts = {
    filter: function(filepath) {
        var filename = path.basename(filepath);
        return filename.indexOf('.') !== 0;
    }
};

mjAPI.start();

module.exports = function() {

    var pages = [];
    var categories = [];
    var fprefix = path.resolve(cwd + '/' + config['dir'].source);

    var gitlog = require('gitlog'),
        options = {
            repo: config['dir'].source,
            number: 20,
            fields: [
                'hash',
                'abbrevHash',
                'subject',
                'committerName',
                'committerDate'
            ]
        };

    recursive(fprefix, ['.*'], function(err, files) {

        async.each(files, function(filename, cb) {
            // ignore non-md files
            if (path.extname(filename) === '.md') {
                var filelink = path.dirname(filename).replace(fprefix, '') + '/' + path.basename(filename, '.md');
                parseFile(filename, fs.readFileSync(path.resolve(filename), 'utf8'), filelink, function(page) {
                    pages.push(page);
                    var ci = findWithAttr(categories, 'name', page.category);
                    if (typeof ci === 'number') {
                        categories[ci].pages.push(page);
                    } else {
                        categories.push({
                            name: page.category,
                            pages: [
                                page
                            ]
                        });
                    }
                    cb();
                });
            }
        }, function() {
            render(
                path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/index.ejs'),
                path.resolve(cwd + '/' + config['dir'].site), {
                    config: config,
                    categories: categories,
                    page: {}
                });

            categories.forEach(function(category) {
                category.pages.forEach(function(page) {
                    render(
                        path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/page.ejs'),
                        path.resolve(cwd + '/' + config['dir'].site + '/page' + page.link),
                        {
                            config: config,
                            categories: categories,
                            page: page
                        }
                    );
                });
            });
        });

        if (config['deploy'].cname) {
            fs.writeFile(path.resolve(cwd + '/' + config['dir'].site + '/CNAME'), config['deploy'].cname + '\n');
        }

        console.log('Pages generated.');
    });

    syncAssets();
    syncSrc();

    if (config['wiki'].log) {
        gitlog(options, function(error, commits) {
            render(
                path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/changes.ejs'),
                path.resolve(cwd + '/' + config['dir'].site + '/changelog'),
                {
                    page: {},
                    config: config,
                    commits: commits
                }
            );
        });
    }
};

var parseFile = function(filename, fd, filelink, callback) {

    try {
        var meta = fd.slice(0, fd.indexOf('\n---\n')).split('\n');
        var title = meta[0].split(/title:\s?/i)[1];
        var category = meta[1].split(/category:\s?/i)[1];
        var content = fd.slice(fd.indexOf('\n---\n') + 5);
    } catch (e) {
        console.error('Error when reading content properties, please check file: ' + filename);
        return {};
    }

    content = lastUpdate(filename, content);

    var maths = content.match(/\^{3}math(.*?\n*?)+?\^{3}/gm);
    if (maths && maths.length !== 0) {
        if (!config['custom'].mathjax) {
            console.warn('WARNING: MathJax detected white it is disabled.');
            callback({
                title: title,
                category: category,
                link: filelink,
                content: mdParse(content)
            });
        } else {

            async.eachSeries(maths, function(m, cb) {

                var c = m.split(/(\^{3}math|\^{3})/g)[2];
                mjParse(c, function(mathxml) {
                    content = content.replace(m, mathxml);
                    cb();
                });

            }, function(err) {
                callback({
                    title: title,
                    category: category,
                    link: filelink,
                    content: mdParse(content)
                })
            });
        }

    } else {
        callback({
            title: title,
            category: category,
            link: filelink,
            content: mdParse(content)
        });
    }

};

var syncAssets = function() {
    var themePath = path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/assets');
    var assetsPath = path.resolve(cwd + '/' + config['dir'].site);
    mkdirp.sync(path.resolve(assetsPath + '/assets'), { mode: '0755' });
    mkdirp.sync(path.resolve(assetsPath + '/static'), { mode: '0755' });
    ncp(themePath, path.resolve(assetsPath + '/assets'));
    ncp(path.resolve(cwd + '/static'), path.resolve(assetsPath + '/static'));
    if (config['wiki'].favicon) {
        fs.stat('./config.yml', function(e, stat) {
            if (e && e.code === 'ENOENT') {
                console.log('WARNING: favicon configured but not found.');
            } else if (e) {
                console.error('WARNING: Error ' + e.code);
            } else {
                ncp(path.resolve(cwd + '/favicon.ico'), path.resolve(assetsPath + '/favicon.ico'));
            }
        });
    }
};

var syncSrc = function() {
    var srcPath = path.resolve(cwd + '/' + config['dir'].source);
    var dest = path.resolve(cwd + '/' + config['dir'].site + '/' + config['dir'].raw);
    mkdirp.sync(dest, { mode: '0755' });
    ncp(srcPath, dest, ncpOpts, function(err) {
        console.log('Source files synced.');
    });
};

var render = function(templatePath, destPath, data) {
    mkdirp.sync(destPath, { mode: '0755' });
    var templateFileData = fs.readFileSync(templatePath, 'utf8');
    fs.writeFile(path.resolve(destPath + '/index.html'), ejs.render(templateFileData, data, {
        filename: templatePath,
        rmWhitespace: true
    }), function(err) {
        if (err) {
            console.error('Error when compile page: ' + destPath);
            console.error(err);
            process.exit(1);
        }
        console.log('Rendered: ' + path.resolve(destPath + '/index.html'));
    });
};

var mjParse = function(data, callback) {

    mjAPI.typeset({
        html: data,
        inputs: ["TeX", "MathML"],
        mml: true
    }, function(result) {
        callback(result.html);
    });

};

var mdParse = function(data) {

    marked.setOptions(config['custom'].markdown);
    var renderer = new marked.Renderer();
    renderer.heading = function (text, level) {
        var escapedText = text.replace(/\s/g, "_"); // remove all characters

        return '<h' + level + '># ' +
            text + '</h' + level + '>';
    };

    return config['custom'].autospacing ? pangunode(marked(data, { renderer: renderer })) : marked(data, { renderer: renderer });
};

var lastUpdate = function(file, content) {
    var fstat = fs.statSync(file);
    var sourcepath = file.replace(path.resolve(cwd + '/' + config['dir'].source), config['dir'].raw);
    return content + '\n\n_Last Update: ' + fstat.mtime + '_' +
            '  [Source File](/' + sourcepath + ')\n';
};

// http://stackoverflow.com/questions/7176908/how-to-get-index-of-object-by-its-property-in-javascript
function findWithAttr(array, attr, value) {
    for(var i = 0; i < array.length; i += 1) {
        if(array[i][attr] === value) {
            return i;
        }
    }
}
<commit_msg>fix source file link in subdir<commit_after>"use strict";

var Q = require('q');
var async = require('async');
var marked = require('marked');
var mjAPI = require("mathjax-node");
var pangunode = require('pangunode');
var ejs = require('ejs');
var ncp = require('ncp');
var recursive = require('recursive-readdir');
var mkdirp = require('mkdirp');
var fs = require('fs');
var path = require('path');
var cwd = process.cwd();
var exec = require('child_process').exec;
var config = require('./config')();

var ncpOpts = {
    filter: function(filepath) {
        var filename = path.basename(filepath);
        return filename.indexOf('.') !== 0;
    }
};

mjAPI.start();

module.exports = function() {

    var pages = [];
    var categories = [];
    var fprefix = path.resolve(cwd + '/' + config['dir'].source);

    var gitlog = require('gitlog'),
        options = {
            repo: config['dir'].source,
            number: 20,
            fields: [
                'hash',
                'abbrevHash',
                'subject',
                'committerName',
                'committerDate'
            ]
        };

    recursive(fprefix, ['.*'], function(err, files) {

        async.each(files, function(filename, cb) {
            // ignore non-md files
            if (path.extname(filename) === '.md') {
                var filelink = path.dirname(filename).replace(fprefix, '') + '/' + path.basename(filename, '.md');
                parseFile(filename, fs.readFileSync(path.resolve(filename), 'utf8'), filelink, function(page) {
                    pages.push(page);
                    var ci = findWithAttr(categories, 'name', page.category);
                    if (typeof ci === 'number') {
                        categories[ci].pages.push(page);
                    } else {
                        categories.push({
                            name: page.category,
                            pages: [
                                page
                            ]
                        });
                    }
                    cb();
                });
            }
        }, function() {
            render(
                path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/index.ejs'),
                path.resolve(cwd + '/' + config['dir'].site), {
                    config: config,
                    categories: categories,
                    page: {}
                });

            categories.forEach(function(category) {
                category.pages.forEach(function(page) {
                    render(
                        path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/page.ejs'),
                        path.resolve(cwd + '/' + config['dir'].site + '/page' + page.link),
                        {
                            config: config,
                            categories: categories,
                            page: page
                        }
                    );
                });
            });
        });

        if (config['deploy'].cname) {
            fs.writeFile(path.resolve(cwd + '/' + config['dir'].site + '/CNAME'), config['deploy'].cname + '\n');
        }

        console.log('Pages generated.');
    });

    syncAssets();
    syncSrc();

    if (config['wiki'].log) {
        gitlog(options, function(error, commits) {
            render(
                path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/changes.ejs'),
                path.resolve(cwd + '/' + config['dir'].site + '/changelog'),
                {
                    page: {},
                    config: config,
                    commits: commits
                }
            );
        });
    }
};

var parseFile = function(filename, fd, filelink, callback) {

    try {
        var meta = fd.slice(0, fd.indexOf('\n---\n')).split('\n');
        var title = meta[0].split(/title:\s?/i)[1];
        var category = meta[1].split(/category:\s?/i)[1];
        var content = fd.slice(fd.indexOf('\n---\n') + 5);
    } catch (e) {
        console.error('Error when reading content properties, please check file: ' + filename);
        return {};
    }

    content = lastUpdate(filename, content);

    var maths = content.match(/\^{3}math(.*?\n*?)+?\^{3}/gm);
    if (maths && maths.length !== 0) {
        if (!config['custom'].mathjax) {
            console.warn('WARNING: MathJax detected white it is disabled.');
            callback({
                title: title,
                category: category,
                link: filelink,
                content: mdParse(content)
            });
        } else {

            async.eachSeries(maths, function(m, cb) {

                var c = m.split(/(\^{3}math|\^{3})/g)[2];
                mjParse(c, function(mathxml) {
                    content = content.replace(m, mathxml);
                    cb();
                });

            }, function(err) {
                callback({
                    title: title,
                    category: category,
                    link: filelink,
                    content: mdParse(content)
                })
            });
        }

    } else {
        callback({
            title: title,
            category: category,
            link: filelink,
            content: mdParse(content)
        });
    }

};

var syncAssets = function() {
    var themePath = path.resolve(cwd + '/themes' + '/' + config['wiki'].theme + '/assets');
    var assetsPath = path.resolve(cwd + '/' + config['dir'].site);
    mkdirp.sync(path.resolve(assetsPath + '/assets'), { mode: '0755' });
    mkdirp.sync(path.resolve(assetsPath + '/static'), { mode: '0755' });
    ncp(themePath, path.resolve(assetsPath + '/assets'));
    ncp(path.resolve(cwd + '/static'), path.resolve(assetsPath + '/static'));
    if (config['wiki'].favicon) {
        fs.stat('./config.yml', function(e, stat) {
            if (e && e.code === 'ENOENT') {
                console.log('WARNING: favicon configured but not found.');
            } else if (e) {
                console.error('WARNING: Error ' + e.code);
            } else {
                ncp(path.resolve(cwd + '/favicon.ico'), path.resolve(assetsPath + '/favicon.ico'));
            }
        });
    }
};

var syncSrc = function() {
    var srcPath = path.resolve(cwd + '/' + config['dir'].source);
    var dest = path.resolve(cwd + '/' + config['dir'].site + '/' + config['dir'].raw);
    mkdirp.sync(dest, { mode: '0755' });
    ncp(srcPath, dest, ncpOpts, function(err) {
        console.log('Source files synced.');
    });
};

var render = function(templatePath, destPath, data) {
    mkdirp.sync(destPath, { mode: '0755' });
    var templateFileData = fs.readFileSync(templatePath, 'utf8');
    fs.writeFile(path.resolve(destPath + '/index.html'), ejs.render(templateFileData, data, {
        filename: templatePath,
        rmWhitespace: true
    }), function(err) {
        if (err) {
            console.error('Error when compile page: ' + destPath);
            console.error(err);
            process.exit(1);
        }
        console.log('Rendered: ' + path.resolve(destPath + '/index.html'));
    });
};

var mjParse = function(data, callback) {

    mjAPI.typeset({
        html: data,
        inputs: ["TeX", "MathML"],
        mml: true
    }, function(result) {
        callback(result.html);
    });

};

var mdParse = function(data) {

    marked.setOptions(config['custom'].markdown);
    var renderer = new marked.Renderer();
    renderer.heading = function (text, level) {
        var escapedText = text.replace(/\s/g, "_"); // remove all characters

        return '<h' + level + '># ' +
            text + '</h' + level + '>';
    };

    return config['custom'].autospacing ? pangunode(marked(data, { renderer: renderer })) : marked(data, { renderer: renderer });
};

var lastUpdate = function(file, content) {
    var fstat = fs.statSync(file);
    var sourcepath = file.replace(path.resolve(cwd + '/' + config['dir'].source), config['base'].path + '/' + config['dir'].raw);
    return content + '\n\n_Last Update: ' + fstat.mtime + '_' +
            '  [Source File](/' + sourcepath + ')\n';
};

// http://stackoverflow.com/questions/7176908/how-to-get-index-of-object-by-its-property-in-javascript
function findWithAttr(array, attr, value) {
    for(var i = 0; i < array.length; i += 1) {
        if(array[i][attr] === value) {
            return i;
        }
    }
}
<|endoftext|>


<commit_before>(function (root, module, global, define) {

  "use strict";

  /**
   * Represents an Item of an Enum.
   * @param {String} key   The Enum key.
   * @param {Number} value The Enum value.
   */
  function EnumItem(key, value) {
    this.key = key;
    this.value = value;
  }

  EnumItem.prototype = {

    /*constructor reference so that, this.constructor===EnumItem//=>true */
    constructor: EnumItem,

    /**
     * Checks if the flagged EnumItem has the passing object.
     * @param  {EnumItem || String || Number} value The object to check with.
     * @return {Boolean}                            The check result.
     */
    has: function(value) {
      if (value instanceof EnumItem || (typeof(value) === 'object' && value.key !== undefined && value.value !== undefined)) {
        return (this.value & value.value) !== 0;
      } else if (typeof(value) === 'string') {
        return this.key.indexOf(value) >= 0;
      } else {
        return (this.value & value) !== 0;
      }
    },

    /**
     * Checks if the EnumItem is the same as the passing object.
     * @param  {EnumItem || String || Number} key The object to check with.
     * @return {Boolean}                          The check result.
     */
    is: function(key) {
      if (key instanceof EnumItem || (typeof(key) === 'object' && key.key !== undefined && key.value !== undefined)) {
        return this.key === key.key;
      } else if (typeof(key) === 'string') {
        return this.key === key;
      } else {
        return this.value === key;
      }
    },

    /**
     * Returns String representation of this EnumItem.
     * @return {String} String representation of this EnumItem.
     */
    toString: function() {
      return this.key;
    },

    /**
     * Returns JSON object representation of this EnumItem.
     * @return {String} JSON object representation of this EnumItem.
     */
    toJSON: function() {
      return this.key;
    },

    /**
     * Returns the value to compare with.
     * @return {String} The value to compare with.
     */
    valueOf: function() {
      return this.key;
    }

  };

  /**
   * Represents an Enum with enum items.
   * @param {Array || Object}  map     This are the enum items.
   * @param {String || Object} options This are options. [optional]
   */
  function Enum(map, options) {

    if (options && typeof(options) === 'string') {
      options = { name: options };
    }

    this._options = options || {};
    this._options.separator = this._options.separator || ' | ';

    this.enums = [];

    if (map.length) {
      var array = map;
      map = {};

      for (var i = 0; i < array.length; i++) {
        map[array[i]] = Math.pow(2, i);
      }
    }

    for (var member in map) {
      if ((this._options.name && member === 'name') || member === '_options' || member === 'get' || member === 'getKey' || member === 'getValue' || member === 'enums' || member === 'isFlaggable') {
        throw new Error('Enum key "' + member + '" is a reserved word!');
      }
      this[member] = new EnumItem(member, map[member]);
      this.enums.push(this[member]);
    }

    if (this._options.name) {
      this.name = this._options.name;
    }

    var self = this;

    function isFlaggable() {
      for (var i = 0, len = self.enums.length; i < len; i++) {
        var e = self.enums[i];

        if (!((e.value !== 0) && !(e.value & (e.value - 1)))) {
          return false;
        }
      }
      return true;
    }

    this.isFlaggable = isFlaggable();
    this.freezeEnums(); //this will make instances of Enum non-extensible
  }

  Enum.prototype = {

    /*constructor reference so that, this.constructor===Enum//=>true */
    constructor: Enum,

    /**
     * Returns the appropriate EnumItem key.
     * @param  {EnumItem || String || Number} key The object to get with.
     * @return {String}                           The get result.
     */
    getKey: function(value) {
      var item = this.get(value);
      if (item) {
        return item.key;
      } else {
        return 'Undefined';
      }
    },

    /**
     * Returns the appropriate EnumItem value.
     * @param  {EnumItem || String || Number} key The object to get with.
     * @return {Number}                           The get result.
     */
    getValue: function(key) {
      var item = this.get(key);
      if (item) {
        return item.value;
      } else {
        return null;
      }
    },

    /**
     * Returns the appropriate EnumItem.
     * @param  {EnumItem || String || Number} key The object to get with.
     * @return {EnumItem}                         The get result.
     */
    get: function(key) {
      if (key === null || key === undefined) return null;

      if (key instanceof EnumItem || (typeof(key) === 'object' && key.key !== undefined && key.value !== undefined)) {
        var foundIndex = this.enums.indexOf(key);
        if (foundIndex >= 0) {
          return key;
        }
        if (!this.isFlaggable || (this.isFlaggable && key.key.indexOf(this._options.separator) < 0)) {
          return null;
        }
        return this.get(key.key);
      } else if (typeof(key) === 'string') {
        if (key.indexOf(this._options.separator) > 0) {
          var parts = key.split(this._options.separator);

          var value = 0;
          for(var i = 0; i < parts.length; i++) {
            var part = parts[i];

            value |= this[part].value;
          }

          return new EnumItem(key, value);
        } else {
          return this[key];
        }
      } else {
        for (var m in this) {
          if (this.hasOwnProperty(m)) {
            if (this[m].value === key) {
              return this[m];
            }
          }
        }

        var result = null;

        if (this.isFlaggable) {
          for (var n in this) {
            if (this.hasOwnProperty(n)) {
              if ((key & this[n].value) !== 0) {
                if (result) {
                  result += this._options.separator;
                } else {
                  result = '';
                }
                result += n;
              }
            }
          }
        }

        return this.get(result || null);
      }
    },

    /**
     * Define freezeEnums() as a property of the prototype.
     * make enumerable items nonconfigurable and deep freeze the properties. Throw Error on property setter.
     */
    freezeEnums: function() {
      function freezer(o) {
        var props = Object.getOwnPropertyNames(o);
        props.forEach( function(p){
          if (!Object.getOwnPropertyDescriptor(o, p).configurable) {
            return;
          }

          Object.defineProperties(o, p, {writable:false, configurable:false});
        })
        return o;
      }

      function getPropertyValue(value) {
        return value;
      }

      function deepFreezeEnums(o) {
        if (typeof o !== 'object' || o === null || Object.isFrozen(o) || Object.isSealed(o) ){
          return;
        }
        for (var key in o) {
          if (o.hasOwnProperty(key)) {
            o.__defineGetter__(key, getPropertyValue.bind(null, o[key]));
            o.__defineSetter__(key, function throwPropertySetError(value){throw TypeError("Cannot redefine property; Enum Type is not extensible.")});
            deepFreezeEnums(o[key]);
          }
        }
        if (Object.freeze) {
          Object.freeze(o);
        } else {
          freezer(o);
        }
      }

      deepFreezeEnums(this);

      return this;
    },
  };

  if (module && module.exports) {
    module.exports = Enum;
  } else if (define) {
    define(function () {
      return Enum;
    });
  } else {
    root.Enum = Enum;
  }

  if (module && module.exports && global) {

    /**
     * Registers the Enum Type globally in node.js.
     * @param  {String} key Global variable. [optional]
     */
    Enum.register = function(key) {
      key = key || 'Enum';
      if (!global[key]) {
        global[key] = Enum;
      }
    };
  }

}(
  this,
  typeof(module) !== 'undefined' ? module : undefined,
  typeof(global) !== 'undefined' ? global : undefined,
  typeof(define) !== 'undefined' ? define : undefined
));
<commit_msg>implement the "ref Type interface"<commit_after>(function (root, module, global, define) {

  "use strict";

  var endianness = require('os').endianness();

  /**
   * Represents an Item of an Enum.
   * @param {String} key   The Enum key.
   * @param {Number} value The Enum value.
   */
  function EnumItem(key, value) {
    this.key = key;
    this.value = value;
  }

  EnumItem.prototype = {

    /*constructor reference so that, this.constructor===EnumItem//=>true */
    constructor: EnumItem,

    /**
     * Checks if the flagged EnumItem has the passing object.
     * @param  {EnumItem || String || Number} value The object to check with.
     * @return {Boolean}                            The check result.
     */
    has: function(value) {
      if (value instanceof EnumItem || (typeof(value) === 'object' && value.key !== undefined && value.value !== undefined)) {
        return (this.value & value.value) !== 0;
      } else if (typeof(value) === 'string') {
        return this.key.indexOf(value) >= 0;
      } else {
        return (this.value & value) !== 0;
      }
    },

    /**
     * Checks if the EnumItem is the same as the passing object.
     * @param  {EnumItem || String || Number} key The object to check with.
     * @return {Boolean}                          The check result.
     */
    is: function(key) {
      if (key instanceof EnumItem || (typeof(key) === 'object' && key.key !== undefined && key.value !== undefined)) {
        return this.key === key.key;
      } else if (typeof(key) === 'string') {
        return this.key === key;
      } else {
        return this.value === key;
      }
    },

    /**
     * Returns String representation of this EnumItem.
     * @return {String} String representation of this EnumItem.
     */
    toString: function() {
      return this.key;
    },

    /**
     * Returns JSON object representation of this EnumItem.
     * @return {String} JSON object representation of this EnumItem.
     */
    toJSON: function() {
      return this.key;
    },

    /**
     * Returns the value to compare with.
     * @return {String} The value to compare with.
     */
    valueOf: function() {
      return this.key;
    }

  };

  /**
   * Represents an Enum with enum items.
   * @param {Array || Object}  map     This are the enum items.
   * @param {String || Object} options This are options. [optional]
   */
  function Enum(map, options) {

    if (options && typeof(options) === 'string') {
      options = { name: options };
    }

    this._options = options || {};
    this._options.separator = this._options.separator || ' | ';
    this._options.endianness = this._options.endianness || endianness;

    this.enums = [];

    if (map.length) {
      var array = map;
      map = {};

      for (var i = 0; i < array.length; i++) {
        map[array[i]] = Math.pow(2, i);
      }
    }

    for (var member in map) {
      if ((this._options.name && member === 'name') || member === '_options' || member === 'get' || member === 'getKey' || member === 'getValue' || member === 'enums' || member === 'isFlaggable') {
        throw new Error('Enum key "' + member + '" is a reserved word!');
      }
      this[member] = new EnumItem(member, map[member]);
      this.enums.push(this[member]);
    }

    if (this._options.name) {
      this.name = this._options.name;
    }

    var self = this;

    function isFlaggable() {
      for (var i = 0, len = self.enums.length; i < len; i++) {
        var e = self.enums[i];

        if (!((e.value !== 0) && !(e.value & (e.value - 1)))) {
          return false;
        }
      }
      return true;
    }

    this.isFlaggable = isFlaggable();
    this.freezeEnums(); //this will make instances of Enum non-extensible
  }

  Enum.prototype = {

    /*constructor reference so that, this.constructor===Enum//=>true */
    constructor: Enum,

    /* implement the "ref type interface", so that Enum types can
     * be used in `node-ffi` function declarations and invokations.
     * In C, these Enums act as `uint32_t` types.
     *
     * https://github.com/TooTallNate/ref#the-type-interface
     */
    size: 4,
    indirection: 1,

    /**
     * Returns the appropriate EnumItem key.
     * @param  {EnumItem || String || Number} key The object to get with.
     * @return {String}                           The get result.
     */
    getKey: function(value) {
      var item = this.get(value);
      if (item) {
        return item.key;
      } else {
        return 'Undefined';
      }
    },

    /**
     * Returns the appropriate EnumItem value.
     * @param  {EnumItem || String || Number} key The object to get with.
     * @return {Number}                           The get result.
     */
    getValue: function(key) {
      var item = this.get(key);
      if (item) {
        return item.value;
      } else {
        return null;
      }
    },

    /**
     * Returns the appropriate EnumItem.
     * @param  {EnumItem || String || Number} key The object to get with.
     * @return {EnumItem}                         The get result.
     */
    get: function(key, offset) {
      if (key === null || key === undefined) return null;

      // Buffer instance support, part of the ref Type interface
      if (Buffer.isBuffer(key)) {
        key = key['readUInt32' + this.endianness](offset || 0);
      }

      if (key instanceof EnumItem || (typeof(key) === 'object' && key.key !== undefined && key.value !== undefined)) {
        var foundIndex = this.enums.indexOf(key);
        if (foundIndex >= 0) {
          return key;
        }
        if (!this.isFlaggable || (this.isFlaggable && key.key.indexOf(this._options.separator) < 0)) {
          return null;
        }
        return this.get(key.key);
      } else if (typeof(key) === 'string') {
        if (key.indexOf(this._options.separator) > 0) {
          var parts = key.split(this._options.separator);

          var value = 0;
          for(var i = 0; i < parts.length; i++) {
            var part = parts[i];

            value |= this[part].value;
          }

          return new EnumItem(key, value);
        } else {
          return this[key];
        }
      } else {
        for (var m in this) {
          if (this.hasOwnProperty(m)) {
            if (this[m].value === key) {
              return this[m];
            }
          }
        }

        var result = null;

        if (this.isFlaggable) {
          for (var n in this) {
            if (this.hasOwnProperty(n)) {
              if ((key & this[n].value) !== 0) {
                if (result) {
                  result += this._options.separator;
                } else {
                  result = '';
                }
                result += n;
              }
            }
          }
        }

        return this.get(result || null);
      }
    },

    /**
     * Sets the Enum "value" onto the give `buffer` at the specified `offset`.
     * Part of the ref "Type interface".
     *
     * @param  {Buffer} buffer The Buffer instance to write to.
     * @param  {Number} offset The offset in the buffer to write to. Default 0.
     * @param  {EnumItem || String || Number} value The EnumItem to write.
     */
    set: function (buffer, offset, value) {
      var item = this.get(value);
      if (item) {
        return buffer['writeUInt32' + this.endianness](item.value, offset || 0);
      }
    },

    /**
     * Define freezeEnums() as a property of the prototype.
     * make enumerable items nonconfigurable and deep freeze the properties. Throw Error on property setter.
     */
    freezeEnums: function() {
      function freezer(o) {
        var props = Object.getOwnPropertyNames(o);
        props.forEach( function(p){
          if (!Object.getOwnPropertyDescriptor(o, p).configurable) {
            return;
          }

          Object.defineProperties(o, p, {writable:false, configurable:false});
        })
        return o;
      }

      function getPropertyValue(value) {
        return value;
      }

      function deepFreezeEnums(o) {
        if (typeof o !== 'object' || o === null || Object.isFrozen(o) || Object.isSealed(o) ){
          return;
        }
        for (var key in o) {
          if (o.hasOwnProperty(key)) {
            o.__defineGetter__(key, getPropertyValue.bind(null, o[key]));
            o.__defineSetter__(key, function throwPropertySetError(value){throw TypeError("Cannot redefine property; Enum Type is not extensible.")});
            deepFreezeEnums(o[key]);
          }
        }
        if (Object.freeze) {
          Object.freeze(o);
        } else {
          freezer(o);
        }
      }

      deepFreezeEnums(this);

      return this;
    },
  };

  if (module && module.exports) {
    module.exports = Enum;
  } else if (define) {
    define(function () {
      return Enum;
    });
  } else {
    root.Enum = Enum;
  }

  if (module && module.exports && global) {

    /**
     * Registers the Enum Type globally in node.js.
     * @param  {String} key Global variable. [optional]
     */
    Enum.register = function(key) {
      key = key || 'Enum';
      if (!global[key]) {
        global[key] = Enum;
      }
    };
  }

}(
  this,
  typeof(module) !== 'undefined' ? module : undefined,
  typeof(global) !== 'undefined' ? global : undefined,
  typeof(define) !== 'undefined' ? define : undefined
));
<|endoftext|>


<commit_before>/**
 * Created by zwang on 7/30/14.
 */

var redisUtil = require("./redis/redisUtil");
var redis = redisUtil.redis;
var client = redisUtil.client;
var KEYS = redisUtil.KEYS;
var moment = require("moment");
var util = require('util');
var Q = require('q');
var dbUtil = require('./db/dbUtil');
var cql = dbUtil.cql;
var dbClient = dbUtil.dbClient;
var postUtil = require('./post');
var points = require('./points');
var dbPromise = require('./db/promise');
var followUtil = require('./follow');

// TODO: remove dbClient example
// dbClient.execute('SELECT user_id, fname, lname FROM users WHERE user_id=?', [1745],
//   function(err, result) {
//     if (err) console.log('execute failed: ' + err);
//     else console.log('got user profile with name ' + result.rows[0].fname);
//   }
// );

var Comment = {
  postId:null,
  commentId:null,
  userId:null,
  message:null,
  timestamp: null,
  edited:null,

  createComment: function(postId, commentId, userId, message){
    var obj = Object.create(this);
    obj.timestamp = moment();
    obj.postId = postId;
    obj.userId = userId;
    obj.commentId = commentId;
    obj.message = message;
    return obj;
  }
};

/**
 * For newly created post, fan-out the post to follower's feed in Redis and db.
 * @param userId the author of the post
 * @param postId post id
 */
function fanOutPostToFollowers(userId, postId) {
  var key = util.format(KEYS.FOLLOWED_BY, userId);
  Q.ninvoke(client, 'exists', key)
  .then(function(isKeyExists) {
    if (isKeyExists) {
      // Return the followers of the user from Redis
      return Q.ninvoke(client, 'smembers', key);
    } else {
      // Return the followers of the user from DB
      console.log("Fetching followers from db for, " + userId);
      return followUtil.getFollowersFromDb(userId);
    }
  }).then(function (followers) {
    console.log("followers, " + followers);
    // save the post into followers' feed table.
    var promises = followers.map(function(followerId){
      console.log("created db promise for " + postId + " to " + followerId );
      var feedId = cql.types.timeuuid();
      var insertTimeMs = moment().valueOf();
      return dbPromise.genQueryPromise(
          'INSERT INTO feed (user_id, feed_id, post_id, time_inserted) ' +
          'VALUES (?,?,?,?)',
          [followerId,
           {value: feedId, hint: cql.types.dataTypes.timeuuid},
           {value: postId, hint: cql.types.dataTypes.timeuuid},
           {value: insertTimeMs, hint: cql.types.dataTypes.timestamp}]);
    });

    // Save the post into followers' Redis key
    // TODO (kpan), KEYS.FEED should be a sorted set.
    var redisPromises = followers.map(function(followerId){
      console.log("created Redis promise for " + postId + " to " + followerId );
      return Q.ninvoke(client, 'rpush', util.format(KEYS.FEED, followerId), postId);
    });

    Q.all(promises.concat(redisPromises));
  });
}

/**
 * Add post after confirming that Redis is available.
 */
function addPostAfterCheck(userPost, cb){
  if (cb === undefined) {
    cb = redis.print;
  }

  // Add user points first.
  points.addPointsForNewPost(userPost.userId)
  .then(function() {
    // Save the post to Cassandra DB.
    return postUtil.saveToDb(userPost);
  }).then(function() {
    console.log("hset KEYS.POSTS for " + userPost.postId);
    // Save the post into Redis.
    Q.ninvoke(client, 'hset', KEYS.POSTS, userPost.postId, JSON.stringify(userPost));
  }).then(
      function (result) {
        // success
        cb();

        // fan-out to followers' feed in db. Fire-and-forget.
        fanOutPostToFollowers(userPost.userId, userPost.postId);
        // If it is product post, write to user_products table in db. Fire-and-forget.
        // Update category-based Redis sorted set for posts. Fire-and-forget.
      },
      function(err) {
        //error
        console.trace("addPostAfterCheck part 1 error " + err);
        cb(err);
      }
  );
}

/**
 * add a post to a post pool.
 * Add the post to db first. If succeeded, add it to Redis. We need to
 * check the available of Redis server first before we insert the post
 * into db to reduce the possibility of data inconsistency between Redis
 * and DB.
 * RedisKey = 'posts' ; hashKey = {postId}
 * @param userPost
 * @param cb
 */
function addPost(userPost, cb){
  redisUtil.checkAvailable(function (err) {
    if (err) {
      cb(new Error("Server resouce is not available"));
    } else {
      addPostAfterCheck(userPost, cb);
    }
  });
}

function getPost(postId, cb){
  if(cb === undefined) cb = redis.print;
  client.hget(KEYS.POSTS, postId, cb);
}

function likePost(postId, userId, cb){
  if(cb === undefined) cb = redis.print;
  client.sadd(util.format(KEYS.POST_LIKES, postId), userId, cb);
}

function getPostLikes(postId, cb){
  if(cb === undefined) cb = redis.print;
  client.smembers(util.format(KEYS.POST_LIKES, postId), cb);
}

/**
 * add a comment to a thread list as well as comment hash
 * Thread list: RedisKey = "thread:{postId}"
 * Comments Hash: RedisKey = "comments"; hashKey = {commentId}
 * @param comment
 * @param cb
 */
function addComment( comment, cb){
  if(cb === undefined) cb = redis.print;
  client.rpush(util.format(KEYS.THREAD, comment.postId), comment.commentId);
  client.hset(KEYS.COMMENTS, comment.commentId, JSON.stringify(comment), cb);
}

function getThread(postId, cb){
  Q.ninvoke(client, 'lrange', util.format(KEYS.THREAD, postId), 0, -1 )
    .then(function(comments){
      return Q.ninvoke(client, 'hmget', KEYS.COMMENTS, comments);
    })
    .done(function(results){
      cb(null, results);
    });
}

/**
 * public post to various feeds (list)
 * RedisKey = 'feed:%s'
 * @param postId
 * @param userId
 * @param cb
 */
function publishPostToFeeds(postId, userId, cb){
  if(cb === undefined) cb = redis.print;

  // this is an OK example of using Q library
  // what's missing are all of the error cases which will be added later as needed

  Q.nfcall(followUtil.getFollowers, userId )
    .then(function(followers){
      var promises = followers.map(function(followerId){
        console.log("created promise for " + postId + " to " + followerId );
        return Q.ninvoke(client, 'rpush', util.format(KEYS.FEED, followerId), postId);
      });
      return Q.allSettled(promises);
    })
    .done(function(results){
      cb(null, results);
    })
}

/**
 * get the user feed
 * @param userId
 * @param cb
 */
function getFeed(userId, cb){
  if(cb === undefined) cb = redis.print;
  client.lrange(util.format(KEYS.FEED, userId), 0, -1, cb);
}

addPost(postUtil.UserPost.createPost(
  cql.types.timeuuid(),
  "pkdebug",
  "R",
  "3m",
  null,
  "It is a test",
  null,
  null,
  null,
  null,
  null,
  "P",
  false,
  null,
  null,
  0,
  0,
  false,
  null),
  function (err) {
    if (err) {
      console.err("err:" + err);
    } else {
      console.log('debug success');
    }
  });
//fanOutPostToFollowers('pkdebug', 'c938a8a0-27f2-11e4-8ab5-11145f33bdaf');
exports.Comment = Comment;
exports.addPost = addPost;
exports.getPost = getPost;
exports.likePost = likePost;
exports.getPostLikes = getPostLikes;
exports.addComment = addComment;
exports.getThread = getThread;
exports.publishPostToFeeds = publishPostToFeeds;
exports.getFeed = getFeed;

//TODO (wa): unit test failed for addPost.<commit_msg>For new post, add it to author's feed too.<commit_after>/**
 * Created by zwang on 7/30/14.
 */

var redisUtil = require("./redis/redisUtil");
var redis = redisUtil.redis;
var client = redisUtil.client;
var KEYS = redisUtil.KEYS;
var moment = require("moment");
var util = require('util');
var Q = require('q');
var dbUtil = require('./db/dbUtil');
var cql = dbUtil.cql;
var dbClient = dbUtil.dbClient;
var postUtil = require('./post');
var points = require('./points');
var dbPromise = require('./db/promise');
var followUtil = require('./follow');

// TODO: remove dbClient example
// dbClient.execute('SELECT user_id, fname, lname FROM users WHERE user_id=?', [1745],
//   function(err, result) {
//     if (err) console.log('execute failed: ' + err);
//     else console.log('got user profile with name ' + result.rows[0].fname);
//   }
// );

var Comment = {
  postId:null,
  commentId:null,
  userId:null,
  message:null,
  timestamp: null,
  edited:null,

  createComment: function(postId, commentId, userId, message){
    var obj = Object.create(this);
    obj.timestamp = moment();
    obj.postId = postId;
    obj.userId = userId;
    obj.commentId = commentId;
    obj.message = message;
    return obj;
  }
};

/**
 * For newly created post, fan-out the post to follower's and self's feed
 * in Redis and db.
 * @param userId the author of the post
 * @param postId post id
 */
function fanOutPostToFollowers(userId, postId) {
  var key = util.format(KEYS.FOLLOWED_BY, userId);
  Q.ninvoke(client, 'exists', key)
  .then(function(isKeyExists) {
    if (isKeyExists) {
      // Return the followers of the user from Redis
      return Q.ninvoke(client, 'smembers', key);
    } else {
      // Return the followers of the user from DB
      console.log("Fetching followers from db for, " + userId);
      return followUtil.getFollowersFromDb(userId);
    }
  }).then(function (followers) {
    console.log("followers, " + followers);
    // Push the post to author's feed too.
    followers.push(userId);
    // save the post into followers' feed table.
    var promises = followers.map(function(followerId){
      console.log("created db promise for " + postId + " to " + followerId );
      var feedId = cql.types.timeuuid();
      var insertTimeMs = moment().valueOf();
      return dbPromise.genQueryPromise(
          'INSERT INTO feed (user_id, feed_id, post_id, time_inserted) ' +
          'VALUES (?,?,?,?)',
          [followerId,
           {value: feedId, hint: cql.types.dataTypes.timeuuid},
           {value: postId, hint: cql.types.dataTypes.timeuuid},
           {value: insertTimeMs, hint: cql.types.dataTypes.timestamp}]);
    });

    // Save the post into followers' Redis key
    // TODO (kpan), KEYS.FEED should be a sorted set.
    var redisPromises = followers.map(function(followerId){
      console.log("created Redis promise for " + postId + " to " + followerId );
      return Q.ninvoke(client, 'rpush', util.format(KEYS.FEED, followerId), postId);
    });

    Q.all(promises.concat(redisPromises));
  });
}

/**
 * Add post after confirming that Redis is available.
 */
function addPostAfterCheck(userPost, cb){
  if (cb === undefined) {
    cb = redis.print;
  }

  // Add user points first.
  points.addPointsForNewPost(userPost.userId)
  .then(function() {
    // Save the post to Cassandra DB.
    return postUtil.saveToDb(userPost);
  }).then(function() {
    console.log("hset KEYS.POSTS for " + userPost.postId);
    // Save the post into Redis.
    Q.ninvoke(client, 'hset', KEYS.POSTS, userPost.postId, JSON.stringify(userPost));
  }).then(
      function (result) {
        // success
        cb();

        // fan-out to followers' and self's feed in Reis and db. Fire-and-forget.
        fanOutPostToFollowers(userPost.userId, userPost.postId);
        // If it is product post, write to user_products table in db. Fire-and-forget.
        // Update category-based Redis sorted set for posts. Fire-and-forget.
      },
      function(err) {
        //error
        console.trace("addPostAfterCheck part 1 error " + err);
        cb(err);
      }
  );
}

/**
 * add a post to a post pool.
 * Add the post to db first. If succeeded, add it to Redis. We need to
 * check the available of Redis server first before we insert the post
 * into db to reduce the possibility of data inconsistency between Redis
 * and DB.
 * RedisKey = 'posts' ; hashKey = {postId}
 * @param userPost
 * @param cb
 */
function addPost(userPost, cb){
  redisUtil.checkAvailable(function (err) {
    if (err) {
      cb(new Error("Server resouce is not available"));
    } else {
      addPostAfterCheck(userPost, cb);
    }
  });
}

function getPost(postId, cb){
  if(cb === undefined) cb = redis.print;
  client.hget(KEYS.POSTS, postId, cb);
}

function likePost(postId, userId, cb){
  if(cb === undefined) cb = redis.print;
  client.sadd(util.format(KEYS.POST_LIKES, postId), userId, cb);
}

function getPostLikes(postId, cb){
  if(cb === undefined) cb = redis.print;
  client.smembers(util.format(KEYS.POST_LIKES, postId), cb);
}

/**
 * add a comment to a thread list as well as comment hash
 * Thread list: RedisKey = "thread:{postId}"
 * Comments Hash: RedisKey = "comments"; hashKey = {commentId}
 * @param comment
 * @param cb
 */
function addComment( comment, cb){
  if(cb === undefined) cb = redis.print;
  client.rpush(util.format(KEYS.THREAD, comment.postId), comment.commentId);
  client.hset(KEYS.COMMENTS, comment.commentId, JSON.stringify(comment), cb);
}

function getThread(postId, cb){
  Q.ninvoke(client, 'lrange', util.format(KEYS.THREAD, postId), 0, -1 )
    .then(function(comments){
      return Q.ninvoke(client, 'hmget', KEYS.COMMENTS, comments);
    })
    .done(function(results){
      cb(null, results);
    });
}

/**
 * public post to various feeds (list)
 * RedisKey = 'feed:%s'
 * @param postId
 * @param userId
 * @param cb
 */
function publishPostToFeeds(postId, userId, cb){
  if(cb === undefined) cb = redis.print;

  // this is an OK example of using Q library
  // what's missing are all of the error cases which will be added later as needed

  Q.nfcall(followUtil.getFollowers, userId )
    .then(function(followers){
      var promises = followers.map(function(followerId){
        console.log("created promise for " + postId + " to " + followerId );
        return Q.ninvoke(client, 'rpush', util.format(KEYS.FEED, followerId), postId);
      });
      return Q.allSettled(promises);
    })
    .done(function(results){
      cb(null, results);
    })
}

/**
 * get the user feed
 * @param userId
 * @param cb
 */
function getFeed(userId, cb){
  if(cb === undefined) cb = redis.print;
  client.lrange(util.format(KEYS.FEED, userId), 0, -1, cb);
}

addPost(postUtil.UserPost.createPost(
  cql.types.timeuuid(),
  "pkdebug",
  "R",
  "3m",
  null,
  "It is a test",
  null,
  null,
  null,
  null,
  null,
  "P",
  false,
  null,
  null,
  0,
  0,
  false,
  null),
  function (err) {
    if (err) {
      console.err("err:" + err);
    } else {
      console.log('debug success');
    }
  });
//fanOutPostToFollowers('pkdebug', 'c938a8a0-27f2-11e4-8ab5-11145f33bdaf');
exports.Comment = Comment;
exports.addPost = addPost;
exports.getPost = getPost;
exports.likePost = likePost;
exports.getPostLikes = getPostLikes;
exports.addComment = addComment;
exports.getThread = getThread;
exports.publishPostToFeeds = publishPostToFeeds;
exports.getFeed = getFeed;

//TODO (wa): unit test failed for addPost.<|endoftext|>


<commit_before>/*
 * file.js
 * https://github.com/skalio/teambeamjs
 *
 * Copyright (c) 2015 Skalio GmbH
 * Licensed under the MIT license.
 *
 * @license
 */

'use strict';

var Q = require('q'),
	path = require('path'),
	fs = require('fs'),
	statusBar = require('status-bar'),

	reqHelper = require('./reqHelper'),
	settings = require('./teambeamjs').settings;

/**
 * Creates a new File object
 */
var File = function(input) {
	input = input || {};
	this.objectId = input.objectId || undefined;
	this.name = input.name || undefined;
	this.size = input.size || 0;
	this.hashsum = input.hashsum || undefined;
	this.url = input.url || undefined;
	this.malware = {
		status: input.malware ? (input.malware.status || 'unknown') : 'unknown',
		text: input.malware ? (input.malware.text || undefined) : undefined
	};
};

File.prototype.getShortName = function() {
	if (this.name.length > 30) {
		return this.name.substr(0, 12) + "(...)" + this.name.substr(this.name.length - 12);
	} else {
		return this.name;
	}
};

File.prototype.getHTML5MediaType = function() {
	var ext = this.name.substr(this.name.length - 3);
	var type = false;
	switch (ext.toLowerCase()) {
		case "mp4":
			type = "video/mp4";
			break;
		case "m4v":
			type = "video/mp4";
			break;
		case "webm":
			type = "video/webm";
			break;
		case "mp3":
			type = "audio/mpeg";
			break;
		case "ogg":
			type = "audio/ogg";
			break;
		case "wav":
			type = "audio/wav";
			break;
		default:
			type = false;
			break;
	}
	return type;
};

File.prototype.download = function(basedir, options) {
	var that = this;
	options = options || {};
	console.log('Download started for %j', that.objectId);

	var bar = statusBar.create({
		total: that.size
	}).on('render', function(stats) {
		process.stdout.write(' Downloading file ' + that.objectId + ' ' +
				this.format.storage(stats.currentSize) + ' ' +
				this.format.speed(stats.speed) + ' ' +
				this.format.time(stats.remainingTime) + ' [' +
				this.format.progressBar(stats.percentage) + '] ' +
				this.format.percentage(stats.percentage)
		);
		process.stdout.cursorTo(0);
	});

	var deferred = Q.defer();

	var response = reqHelper.getAsStream(that.url);

	// write to disk
	response.pipe(bar)
			.pipe(fs.createWriteStream(path.join(
			basedir,
			options['use-filename'] ? that.name : that.objectId
	)));

	response.on('end', function() {
		// console.log('Download completed for %j', that.objectId);
		deferred.resolve();
	});

	response.on('error', function(err) {
		if (bar) bar.cancel();
		deferred.reject(err);
	});

	return deferred.promise;

/*
	return Q(reqHelper.getAsStream(that.url))
	.then(function(response) {
		var deferred = Q.defer();

		// write to disk
		response.pipe(bar)
				.pipe(fs.createWriteStream(path.join(
				basedir,
				options['use-filename'] ? that.name : that.objectId
		)));

		response.on('end', function() {
			// console.log('Download completed for %j', that.objectId);
			deferred.resolve();
		});

		response.on('error', function(err) {
			if (bar) bar.cancel();
			deferred.reject(err);
		});

		return deferred.promise;
	});
	*/
};

exports = module.exports = File;
<commit_msg>Fixed downloads again<commit_after>/*
 * file.js
 * https://github.com/skalio/teambeamjs
 *
 * Copyright (c) 2015 Skalio GmbH
 * Licensed under the MIT license.
 *
 * @license
 */

'use strict';

var Q = require('q'),
	path = require('path'),
	fs = require('fs'),
	statusBar = require('status-bar'),

	reqHelper = require('./reqHelper'),
	settings = require('./teambeamjs').settings;

/**
 * Creates a new File object
 */
var File = function(input) {
	input = input || {};
	this.objectId = input.objectId || undefined;
	this.name = input.name || undefined;
	this.size = input.size || 0;
	this.hashsum = input.hashsum || undefined;
	this.url = input.url || undefined;
	this.malware = {
		status: input.malware ? (input.malware.status || 'unknown') : 'unknown',
		text: input.malware ? (input.malware.text || undefined) : undefined
	};
};

File.prototype.getShortName = function() {
	if (this.name.length > 30) {
		return this.name.substr(0, 12) + "(...)" + this.name.substr(this.name.length - 12);
	} else {
		return this.name;
	}
};

File.prototype.getHTML5MediaType = function() {
	var ext = this.name.substr(this.name.length - 3);
	var type = false;
	switch (ext.toLowerCase()) {
		case "mp4":
			type = "video/mp4";
			break;
		case "m4v":
			type = "video/mp4";
			break;
		case "webm":
			type = "video/webm";
			break;
		case "mp3":
			type = "audio/mpeg";
			break;
		case "ogg":
			type = "audio/ogg";
			break;
		case "wav":
			type = "audio/wav";
			break;
		default:
			type = false;
			break;
	}
	return type;
};

File.prototype.download = function(basedir, options) {
	var that = this;
	options = options || {};
	console.log('Download starting for %j', that.objectId);

	/*
	 * This progressbar is not working well, since stuff needs to be
	 * piped to it, but it is not a duplex stream -> the data is consumed
	 * and afterwards it can't be written out anymore...
	 *
	var bar = statusBar.create({
		total: that.size
	}).on('render', function(stats) {
		process.stdout.write(' Downloading file ' + that.objectId + ' ' +
				this.format.storage(stats.currentSize) + ' ' +
				this.format.speed(stats.speed) + ' ' +
				this.format.time(stats.remainingTime) + ' [' +
				this.format.progressBar(stats.percentage) + '] ' +
				this.format.percentage(stats.percentage)
		);
		process.stdout.cursorTo(0);
	});
	 */

	var outStream = fs.createWriteStream(path.join(
			basedir,
			options['use-filename'] ? that.name : that.objectId
	));
	var deferred = Q.defer();

	reqHelper.getAsStream(that.url)
	.then(function(response) {
		if (response.statusCode >= 400) {
			deferred.reject(new Error('Request failed: '+ response.body.error.message));
		}

		response.on('progress', function(completedBytes, totalBytes) {

		});

		response.on('end', function() {
			console.log('Download completed for %j', that.objectId);
			deferred.resolve();
		});

		response.on('error', function(err) {
			// if (bar) bar.cancel();
			deferred.reject(err);
		});

		// write to disk
		response.pipe(outStream);
	});

	return deferred.promise;
};

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


<commit_before>/* jslint node: true, esnext: true */
"use strict";

const BaseStep = require('kronos-step').Step;

const ATTRIBUTES = ['description'];
/**
 * This is the flow implementation.
 * It holds all the steps.
 */
const Flow = {
	"name": "kronos-flow",
	"description": "General step collection",

	/**
	 * Declares the following properties:
	 * steps
	 * autostart
	 * @param manager {object} The kronos-service-manager
	 * @param name {string} The name of this step
	 * @param stepDefinition {object} The definition used to create the step
	 * @param props {object} The properties of the new object
	 */
	initialize(manager, name, stepDefinition, props) {

		props.autostart = {
			value: stepDefinition.autostart || false
		};

		// All steps
		const steps = new Map();

		//----------------------------------------------
		//-- Create the sub steps
		//----------------------------------------------
		for (const subStepName in stepDefinition.steps) {
			const subStepDefinition = stepDefinition.steps[subStepName];
			subStepDefinition.name = subStepName;
			const createdStep = manager.createStepInstanceFromConfig(subStepDefinition, manager);
			if (!createdStep) {
				throw new Error(`The step '${subStepName}' in the flow '${this.name}' could not been ceated.`);
			}
			steps.set(subStepName, createdStep);
		}

		props.steps = {
			value: steps
		};

		const ocs = [];
		props.outstandingConnections = {
			value: ocs
		};
	},

	/**
	 * The flow has no real endpoints. It only has proxies.
	 * So just return the configuration
	 * @param {Object} stepDefinition The step configuration
	 */
	createEndpoints(stepDefinition) {
		if (stepDefinition) {
			if (stepDefinition.endpoints) {
				for (const endpointName in stepDefinition.endpoints) {
					this.endpoints[endpointName] = stepDefinition.endpoints[endpointName];
				}
			}
			this.connectEndpoints(stepDefinition);
			this.connectRootEndpoints();
		}
	},

	/**
	 * Find endpoint for given expression
	 * @param {string} expression
	 * @param {boolean} wait for endpoint to become present (deliver a promise)
	 * @param {array[string]} problems
	 * @return {Endpoint} found endpoint
	 */
	endpointFor(expression, wait, problems) {
		const res = expression.match(/^(.+):(.+)/); // service:endpoint

		if (res) {
			const service = this.manager.services[res[1]];
			if (service) {
				const target = service.endpoints[res[2]];
				if (target) {
					return target;
				}
				problems.push(`Endpoint '${res[2]}' of service '${res[1]}' not found`);
			} else {
				if (wait) {
					// wait for service to become present
					return this.manager.declareService({
						name: res[1]
					}, true).then(svc => svc.endpoints[res[2]]);
				}

				problems.push(`Service '${res[1]}' not found`);
			}
		} else {
			const res = expression.match(/^(.+)\/(.+)/);
			if (res) { // step/endpoint
				const step = this.steps.get(res[1]);
				if (step) {
					const target = step.endpoints[res[2]];
					if (target) {
						return target;
					}
					problems.push(`Endpoint '${res[2]}' of step '${res[1]}' not found`);
				} else {
					problems.push(`Step '${res[1]}' not found`);
				}
			}
		}

		return undefined;
	},

	/**
	 * set the target endpoints
	 */
	connectEndpoints(stepDefinition) {
		for (const subStepName in stepDefinition.steps) {
			const subStepDefinition = stepDefinition.steps[subStepName];

			if (subStepDefinition.endpoints) {
				const step = this.steps.get(subStepName);

				for (const endpointName in subStepDefinition.endpoints) {
					const endpointConfig = subStepDefinition.endpoints[endpointName];
					const target = typeof endpointConfig === 'string' ? endpointConfig : endpointConfig.target;

					if (typeof target === 'string') {
						const mandatory = endpointConfig === 'string' ? true : endpointConfig.mandatory === undefined ? true :
							endpointConfig.mandatory;

						const problems = [];
						const targetEndpoint = this.endpointFor(target, mandatory, problems);

						if (targetEndpoint) {
							const endpoint = step.endpoints[endpointName];
							if (targetEndpoint.name) {
								// plain endpoint - no promise
								endpoint.connected = targetEndpoint;
							} else {
								this.outstandingConnections.push(targetEndpoint.then(te => {
									this.info(level => `connect: ${endpoint} <> ${te}`);
									endpoint.connected = te;
								}));
							}
						} else {
							if (step.type === 'kronos-flow') {
								// The target is a step of the flow itself. This has been handled
								// before as the flow was created. ???
							} else {
								if (mandatory) {
									throw new Error(`While evaluating '${target}' ${problems.join(',')}`);
								}
								this.info(level =>
									`Remove optional endpoint ${endpointName} since it can't be connected: ${problems.join(',')}`);
								step.removeEndpoint(endpointName);
							}
						}
					}
				}
			}
		}
	},

	/**
	 * A flow has only endpoint proxies. These will be replaced by the original endpoints
	 * of the sub steps
	 * get the original endpoints for the Flow.
	 */
	connectRootEndpoints() {
		const endpoints = this.endpoints;
		const name = this.name;
		const steps = this.steps;

		for (const endpointName in endpoints) {
			const endpoint = endpoints[endpointName];

			if (typeof endpoint === 'string') {
				const res = endpoint.match(/^(.+)\/(.+)/);
				if (res) { // optional step:
					const targetStepName = res[1];
					const targetEndpointName = res[2];
					let targetStep = steps.get(targetStepName);

					if (!targetStep) {
						// test if the step refernces this current flow.
						if (targetStepName === name) {
							targetStep = this;
						}
					}

					if (targetStep) {
						const targetEndpoint = targetStep.endpoints[targetEndpointName];
						if (targetEndpoint) {
							endpoints[endpointName] = targetEndpoint;
							targetEndpoint.step = targetStep;
						} else {
							throw new Error(`Target endpoint '${targetEndpointName}' not found in step '${targetStepName}'`);
						}
					} else {
						throw new Error(`Target step '${targetStepName}' not found`);
					}
				} else {
					throw new Error(`Endpoint target '${endpoint}' of endpoint '${endpointName}' is not of right format`);
				}
			} else {
				if (!endpoint.default) {
					throw new Error(`Flow endpoint '${endpointName}' in flow '${name}' is not of type 'string'`);
				}
			}
		}
	},

	_start() {
		return Promise.all(this.outstandingConnections).then(ocs => {
			const sbt = stepsByType(this.steps);
			return Promise.all(sbt.normalSteps.map(s => s.start())).then(() =>
				Promise.all(sbt.inboundSteps.map(s => s.start())));
		});
	},

	_stop() {
		const sbt = stepsByType(this.steps);
		return Promise.all(sbt.inboundSteps.map(s => s.stop())).then(() =>
			Promise.all(sbt.normalSteps.map(s => s.stop()))
		);
	},

	_remove() {
		return Promise.all(mapForEachValue(this.steps, s => s.remove()));
	},

	toJSONWithOptions(options) {
		const json = {
			type: this.type,
			endpoints: {}
		};

		if (options.includeName) {
			json.name = this.name;
		}

		if (options.includeRuntimeInfo) {
			json.state = this.state;
		}

		for (const endpointName in this.endpoints) {
			const currentEndpoint = this.endpoints[endpointName];
			if (!currentEndpoint.default || options.includeDefaults) {

				//json.endpoints[endpointName] = `${currentEndpoint.step.name/${currentEndpoint.name}`;
			}
		}

		// if (options.includeDefaults) {
		ATTRIBUTES.forEach(a => json[a] = this[a]);

		json.steps = {};
		for (const subStepName of this.steps.values()) {
			json.steps[subStepName.name] = subStepName.toJSONWithOptions(options);
		}

		return json;
	}
};

function stepsByType(steps) {
	// The inbound steps will be stopped first
	const normalSteps = [];
	const inboundSteps = [];

	// Sort the steps
	for (const subStep of steps.values()) {
		if (subStep.inbound) {
			inboundSteps.push(subStep);
		} else {
			normalSteps.push(subStep);
		}
	}

	return {
		normalSteps: normalSteps,
		inboundSteps: inboundSteps
	};
}

/**
 * Calls function on each map entry and delivers function reults as an iterable.
 * @param {Map} map
 * @param {Function} f
 * @return {Iterator} result of calling f on each map entry
 * @api private
 */
function mapForEachValue(map, f) {
	const r = [];

	for (const i of map.values()) {
		r.push(f(i));
	}
	return r;
}

/**
 * Load a flow json files and created the flows from it.
 * The flows will be registerd at the manager
 * @param manager {object} The kronos-service-manager
 * @param jsonFlowDefinition {object} The flow definition
 * @return promise
 */
function loadFlows(manager, jsonFlowDefinition) {
	if (!manager) {
		throw new Error("The manager is mandatory");
	}

	const promises = [];

	for (const flowName in jsonFlowDefinition) {
		const singleFlowDefinition = jsonFlowDefinition[flowName];

		singleFlowDefinition.type = "kronos-flow";
		singleFlowDefinition.name = flowName;

		const flow = manager.createStepInstanceFromConfig(singleFlowDefinition, manager);
		promises.push(manager.registerStep(flow));
		promises.push(manager.registerFlow(flow));
	}

	return Promise.all(promises);
}

module.exports.FlowFactory = Object.assign({}, BaseStep, Flow);
module.exports.loadFlows = loadFlows;
<commit_msg>fix: quote endpints in log message<commit_after>/* jslint node: true, esnext: true */
"use strict";

const BaseStep = require('kronos-step').Step;

const ATTRIBUTES = ['description'];
/**
 * This is the flow implementation.
 * It holds all the steps.
 */
const Flow = {
	"name": "kronos-flow",
	"description": "General step collection",

	/**
	 * Declares the following properties:
	 * steps
	 * autostart
	 * @param manager {object} The kronos-service-manager
	 * @param name {string} The name of this step
	 * @param stepDefinition {object} The definition used to create the step
	 * @param props {object} The properties of the new object
	 */
	initialize(manager, name, stepDefinition, props) {

		props.autostart = {
			value: stepDefinition.autostart || false
		};

		// All steps
		const steps = new Map();

		//----------------------------------------------
		//-- Create the sub steps
		//----------------------------------------------
		for (const subStepName in stepDefinition.steps) {
			const subStepDefinition = stepDefinition.steps[subStepName];
			subStepDefinition.name = subStepName;
			const createdStep = manager.createStepInstanceFromConfig(subStepDefinition, manager);
			if (!createdStep) {
				throw new Error(`The step '${subStepName}' in the flow '${this.name}' could not been ceated.`);
			}
			steps.set(subStepName, createdStep);
		}

		props.steps = {
			value: steps
		};

		const ocs = [];
		props.outstandingConnections = {
			value: ocs
		};
	},

	/**
	 * The flow has no real endpoints. It only has proxies.
	 * So just return the configuration
	 * @param {Object} stepDefinition The step configuration
	 */
	createEndpoints(stepDefinition) {
		if (stepDefinition) {
			if (stepDefinition.endpoints) {
				for (const endpointName in stepDefinition.endpoints) {
					this.endpoints[endpointName] = stepDefinition.endpoints[endpointName];
				}
			}
			this.connectEndpoints(stepDefinition);
			this.connectRootEndpoints();
		}
	},

	/**
	 * Find endpoint for given expression
	 * @param {string} expression
	 * @param {boolean} wait for endpoint to become present (deliver a promise)
	 * @param {array[string]} problems
	 * @return {Endpoint} found endpoint
	 */
	endpointFor(expression, wait, problems) {
		const res = expression.match(/^(.+):(.+)/); // service:endpoint

		if (res) {
			const service = this.manager.services[res[1]];
			if (service) {
				const target = service.endpoints[res[2]];
				if (target) {
					return target;
				}
				problems.push(`Endpoint '${res[2]}' of service '${res[1]}' not found`);
			} else {
				if (wait) {
					// wait for service to become present
					return this.manager.declareService({
						name: res[1]
					}, true).then(svc => svc.endpoints[res[2]]);
				}

				problems.push(`Service '${res[1]}' not found`);
			}
		} else {
			const res = expression.match(/^(.+)\/(.+)/);
			if (res) { // step/endpoint
				const step = this.steps.get(res[1]);
				if (step) {
					const target = step.endpoints[res[2]];
					if (target) {
						return target;
					}
					problems.push(`Endpoint '${res[2]}' of step '${res[1]}' not found`);
				} else {
					problems.push(`Step '${res[1]}' not found`);
				}
			}
		}

		return undefined;
	},

	/**
	 * set the target endpoints
	 */
	connectEndpoints(stepDefinition) {
		for (const subStepName in stepDefinition.steps) {
			const subStepDefinition = stepDefinition.steps[subStepName];

			if (subStepDefinition.endpoints) {
				const step = this.steps.get(subStepName);

				for (const endpointName in subStepDefinition.endpoints) {
					const endpointConfig = subStepDefinition.endpoints[endpointName];
					const target = typeof endpointConfig === 'string' ? endpointConfig : endpointConfig.target;

					if (typeof target === 'string') {
						const mandatory = endpointConfig === 'string' ? true : endpointConfig.mandatory === undefined ? true :
							endpointConfig.mandatory;

						const problems = [];
						const targetEndpoint = this.endpointFor(target, mandatory, problems);

						if (targetEndpoint) {
							const endpoint = step.endpoints[endpointName];
							if (targetEndpoint.name) {
								// plain endpoint - no promise
								endpoint.connected = targetEndpoint;
							} else {
								this.outstandingConnections.push(targetEndpoint.then(te => {
									this.info(level => `connect: ${endpoint} <> ${te}`);
									endpoint.connected = te;
								}));
							}
						} else {
							if (step.type === 'kronos-flow') {
								// The target is a step of the flow itself. This has been handled
								// before as the flow was created. ???
							} else {
								if (mandatory) {
									throw new Error(`While evaluating '${target}' ${problems.join(',')}`);
								}
								this.info(level =>
									`Remove optional endpoint '${endpointName}' since it can't be connected: ${problems.join(',')}`);
								step.removeEndpoint(endpointName);
							}
						}
					}
				}
			}
		}
	},

	/**
	 * A flow has only endpoint proxies. These will be replaced by the original endpoints
	 * of the sub steps
	 * get the original endpoints for the Flow.
	 */
	connectRootEndpoints() {
		const endpoints = this.endpoints;
		const name = this.name;
		const steps = this.steps;

		for (const endpointName in endpoints) {
			const endpoint = endpoints[endpointName];

			if (typeof endpoint === 'string') {
				const res = endpoint.match(/^(.+)\/(.+)/);
				if (res) { // optional step:
					const targetStepName = res[1];
					const targetEndpointName = res[2];
					let targetStep = steps.get(targetStepName);

					if (!targetStep) {
						// test if the step refernces this current flow.
						if (targetStepName === name) {
							targetStep = this;
						}
					}

					if (targetStep) {
						const targetEndpoint = targetStep.endpoints[targetEndpointName];
						if (targetEndpoint) {
							endpoints[endpointName] = targetEndpoint;
							targetEndpoint.step = targetStep;
						} else {
							throw new Error(`Target endpoint '${targetEndpointName}' not found in step '${targetStepName}'`);
						}
					} else {
						throw new Error(`Target step '${targetStepName}' not found`);
					}
				} else {
					throw new Error(`Endpoint target '${endpoint}' of endpoint '${endpointName}' is not of right format`);
				}
			} else {
				if (!endpoint.default) {
					throw new Error(`Flow endpoint '${endpointName}' in flow '${name}' is not of type 'string'`);
				}
			}
		}
	},

	_start() {
		return Promise.all(this.outstandingConnections).then(ocs => {
			const sbt = stepsByType(this.steps);
			return Promise.all(sbt.normalSteps.map(s => s.start())).then(() =>
				Promise.all(sbt.inboundSteps.map(s => s.start()))
			);
		});
	},

	_stop() {
		const sbt = stepsByType(this.steps);
		return Promise.all(sbt.inboundSteps.map(s => s.stop())).then(() =>
			Promise.all(sbt.normalSteps.map(s => s.stop()))
		);
	},

	_remove() {
		return Promise.all(mapForEachValue(this.steps, s => s.remove()));
	},

	toJSONWithOptions(options) {
		const json = {
			type: this.type,
			endpoints: {}
		};

		if (options.includeName) {
			json.name = this.name;
		}

		if (options.includeRuntimeInfo) {
			json.state = this.state;
		}

		for (const endpointName in this.endpoints) {
			const currentEndpoint = this.endpoints[endpointName];
			if (!currentEndpoint.default || options.includeDefaults) {

				//json.endpoints[endpointName] = `${currentEndpoint.step.name/${currentEndpoint.name}`;
			}
		}

		// if (options.includeDefaults) {
		ATTRIBUTES.forEach(a => json[a] = this[a]);

		json.steps = {};
		for (const subStepName of this.steps.values()) {
			json.steps[subStepName.name] = subStepName.toJSONWithOptions(options);
		}

		return json;
	}
};

function stepsByType(steps) {
	// The inbound steps will be stopped first
	const normalSteps = [];
	const inboundSteps = [];

	// Sort the steps
	for (const subStep of steps.values()) {
		if (subStep.inbound) {
			inboundSteps.push(subStep);
		} else {
			normalSteps.push(subStep);
		}
	}

	return {
		normalSteps: normalSteps,
		inboundSteps: inboundSteps
	};
}

/**
 * Calls function on each map entry and delivers function reults as an iterable.
 * @param {Map} map
 * @param {Function} f
 * @return {Iterator} result of calling f on each map entry
 * @api private
 */
function mapForEachValue(map, f) {
	const r = [];

	for (const i of map.values()) {
		r.push(f(i));
	}
	return r;
}

/**
 * Load a flow json files and created the flows from it.
 * The flows will be registerd at the manager
 * @param manager {object} The kronos-service-manager
 * @param jsonFlowDefinition {object} The flow definition
 * @return promise
 */
function loadFlows(manager, jsonFlowDefinition) {
	if (!manager) {
		throw new Error("The manager is mandatory");
	}

	const promises = [];

	for (const flowName in jsonFlowDefinition) {
		const singleFlowDefinition = jsonFlowDefinition[flowName];

		singleFlowDefinition.type = "kronos-flow";
		singleFlowDefinition.name = flowName;

		const flow = manager.createStepInstanceFromConfig(singleFlowDefinition, manager);
		promises.push(manager.registerStep(flow));
		promises.push(manager.registerFlow(flow));
	}

	return Promise.all(promises);
}

module.exports.FlowFactory = Object.assign({}, BaseStep, Flow);
module.exports.loadFlows = loadFlows;
<|endoftext|>


<commit_before>'use strict';

const path = require('path');
const createTemplates = require('./create-templates');

module.exports = {
  modifiers: {
    content: []
  },
  render: function() {
    return createTemplates(this.modifiers).then(data => {
      this.templates = data.templates;
      return (req, res, next) => {
        let target = this.templates[req.url];
        if (target) {
          res.sendFile(path.join(__dirname, '..', target));
        } else {
          next();
        }
      };
    });
  },
  addModifier: function() {
    let target;
    let modifier;
    if (typeof arguments[0] === 'object') {
      target = arguments[0].target;
      modifier = arguments[0].modifier;
    } else {
      target = arguments[0];
      modifier = arguments[1];
    }

    if (!this.modifiers[target]) throw new Error('Event does not exist.');
    if (typeof modifier !== 'function') throw new Error('Not a function.');
    this.modifiers[target].push(modifier);
  }
};
<commit_msg>added api for getting content structure<commit_after>'use strict';

const path = require('path');
const createTemplates = require('./create-templates');

module.exports = {
  modifiers: {
    content: []
  },
  templates: {},
  render: function() {
    return createTemplates(this.modifiers).then(data => {
      this.templates = data.templates;
      this.api.structure = data.structure;
      return (req, res, next) => {
        let target = this.templates[req.url];
        if (target) {
          res.sendFile(path.join(__dirname, '..', target));
        } else {
          next();
        }
      };
    });
  },
  addModifier: function() {
    let target;
    let modifier;
    if (typeof arguments[0] === 'object') {
      target = arguments[0].target;
      modifier = arguments[0].modifier;
    } else {
      target = arguments[0];
      modifier = arguments[1];
    }

    if (!this.modifiers[target]) throw new Error('Event does not exist.');
    if (typeof modifier !== 'function') throw new Error('Not a function.');
    this.modifiers[target].push(modifier);
  },
  api: {
    structure: {},
    getContentStructure: function(key) {
      return this.structure[key];
    }
  }
};
<|endoftext|>


<commit_before>var Bubble = require('../lib/bubbles');
var Dinosaur = require('../lib/dinosaur');
var Windup = require('../lib/windup');
var GamePlay = require('../lib/game_play');
var Levels = require('../lib/levels');
//
function Game(){
  this.canvas = document.getElementById('game');
  this.context = this.canvas.getContext('2d');
}
// this(game).canvas

Game.prototype.play = function(){
  var canvas = this.canvas;
  var context = this.context;

  var dino = new Dinosaur(canvas);
  var windup = new Windup(canvas);
  var bubbles = [];
  var fruits = [];

  document.addEventListener('keydown', function(key) {
    if (key.keyCode === 65 ) {
      dino.left();
    } else if (key.keyCode === 68) {
      dino.right();
    } else if (key.keyCode === 87 || key.keyCode === 75) {
      dino.setJumpingStatus();
    } else if (key.keyCode === 32 || key.keyCode === 74) {
      key.preventDefault();
      var bubble = new Bubble(dino.mouthX(), dino.mouthY(), dino.direction, canvas);
      bubbles.push(bubble);
    }
  });

  var startScreen = document.getElementById("start-screen");
  startScreen.addEventListener('click', function(){
    startScreen.className += "hidden";
    requestAnimationFrame(gameLoop);
  });

  function gameLoop(){
    var floors = new Levels().whichLevel(canvas, dino.level);
    context.clearRect(0, 0, canvas.width, canvas.height);
    GamePlay.drawFloors(floors, context);
    dino.move(floors).draw(context);
    GamePlay.drawBubbles(bubbles, context);
    windup = GamePlay.checkWindupBubbleCollisions(windup, bubbles);
    GamePlay.checkDinoBubbleCollisions(dino, bubbles, fruits, canvas);
    GamePlay.drawFruits(fruits, context);
    GamePlay.checkDinoFruitCollisions(dino, fruits);
    GamePlay.drawWindup(windup, context);
    GamePlay.checkDinoWindupCollisions(dino, windup);
    GamePlay.drawScore(dino, context);
    if (GamePlay.gameOver(dino)) {
      console.log("Game is over, please play again");
      return true;
    }
    var newWindup = GamePlay.levelUp(dino, fruits, windup, canvas);
    if (newWindup) {
      GamePlay.nextLevel();
      windup = newWindup;
      bubbles = [];
    }
    requestAnimationFrame(gameLoop);
  }
};

module.exports = Game;
<commit_msg>Extract functions out of Game#play<commit_after>var Bubble = require('../lib/bubbles');
var Dinosaur = require('../lib/dinosaur');
var Windup = require('../lib/windup');
var GamePlay = require('../lib/game_play');
var Levels = require('../lib/levels');
//
function Game(){
  this.canvas = document.getElementById('game');
  this.context = this.canvas.getContext('2d');
  this.dino = new Dinosaur(this.canvas);
  this.windup = new Windup(this.canvas);
  this.bubbles = [];
  this.fruits = [];
}

Game.prototype.play = function(){
  setKeyBindings(this.canvas, this.dino, this.bubbles);
  setStartScreen(gameLoop, this);
};

function gameLoop(){
  var game = this;
  var floors = new Levels().whichLevel(game.canvas, game.dino.level);
  game.context.clearRect(0, 0, game.canvas.width, game.canvas.height);
  GamePlay.drawFloors(floors, game.context);
  game.dino.move(floors).draw(game.context);
  GamePlay.drawBubbles(game.bubbles, game.context);
  game.windup = GamePlay.checkWindupBubbleCollisions(game.windup, game.bubbles);
  GamePlay.checkDinoBubbleCollisions(game.dino, game.bubbles, game.fruits, game.canvas);
  GamePlay.drawFruits(game.fruits, game.context);
  GamePlay.checkDinoFruitCollisions(game.dino, game.fruits);
  GamePlay.drawWindup(game.windup, game.context);
  GamePlay.checkDinoWindupCollisions(game.dino, game.windup);
  GamePlay.drawScore(game.dino, game.context);

  if (GamePlay.gameOver(game.dino)) {
    console.log("Game is over, please play again");
    return true;
  }
  var newWindup = GamePlay.levelUp(game.dino, game.fruits, game.windup, game.canvas, game.bubbles);
  if (newWindup) {
    GamePlay.nextLevel();
    game.windup = newWindup;
    // somehow clear out bubbles
  }
  requestAnimationFrame(gameLoop.bind(game));
}

function setKeyBindings(canvas, dino, bubbles){
  document.addEventListener('keydown', function(key) {
    if (key.keyCode === 65 ) {
      dino.left();
    } else if (key.keyCode === 68) {
      dino.right();
    } else if (key.keyCode === 87 || key.keyCode === 75) {
      dino.setJumpingStatus();
    } else if (key.keyCode === 32 || key.keyCode === 74) {
      key.preventDefault();
      var bubble = new Bubble(dino.mouthX(), dino.mouthY(), dino.direction, canvas);
      bubbles.push(bubble);
    }
  });
}

function setStartScreen(gameLoop, game) {
  var startScreen = document.getElementById("start-screen");
  startScreen.addEventListener('click', function(){
    startScreen.className += "hidden";
    requestAnimationFrame(gameLoop.bind(game));
  });
}

module.exports = Game;
<|endoftext|>