# Rain Documentation |
## cache | ../lib/cache.js
Caching module. Works in-node-memory (simple JavaScript object) and using redis. ## type: String api: public |
    var mod_promise         = require('promised-io')
        , mod_resources     = require('./resources.js')
        , mod_redback       = null
        , redbackClient     = null
        , config            = null
        , logger            = require('./logger.js').getLogger('Cache', require('./logger.js').Logger.SEVERITY.ERROR)
        , cache             = null

    // var _cache = {};
    // var cache = {
    //     add : function (key, value, cb) {
    //         if (typeof _cache[key] === 'undefined') {
    //             _cache[key] = value;
    //         }
    //         cb();
    //     }
    //     , get : function (key, cb) {
    //         cb(null, _cache[key] ? _cache[key] : null);
    //     }
    //     , set : function (key, value, exp, cb) {
    //         _cache[key] = value;
    //         cb();
    //     }
    //     ,exists : function (key, cb) {
    //         cb(typeof _cache[key] !== 'undefined');
    //     }
    // }

    function configure(c) {
        config = c;
        if (config &amp;&amp; config.caching &amp;&amp; config.caching.resources) {
            logger.debug('activate caching');
            mod_redback = require('redback');
            redbackClient = mod_redback.createClient();
            cache = redbackClient.createCache('rain');
        }

    }

    function toCache(url, data) {
        if (cache) {
            logger.debug('write to cache; url: ' + url);
            // [TBD] error handling
            cache.set(url, data, config.caching.ttl, function () {
                //logger.debug('written to cache');
            });
        }
    }

    function fromCache(url) {
        var defer = mod_promise.defer()
            , cr

        if (cache) {
            cache.exists(url, function (err, exists) {
                // [TBD] error handling
                if (err) {
                    throw new Error(err.toString());
                    defer.resolve(null);
                }
                if (exists) {
                    cache.get(url, function (err, value) {
                        if (err) {
                            throw new Error(err);
                        } else {
                            logger.debug('got from cache; url: ' + url);
                            cr = new mod_resources.Resource(url, value);
                            defer.resolve(cr);
                        }
                    });
                } else {
                    logger.debug('not found in cache; url: ' + url);
                    defer.resolve(null);
                }
            });
        } else {
            defer.resolve(null);
        }
        return defer;
    }

    exports.fromCache   = fromCache;
    exports.toCache     = toCache;
    exports.configure   = configure;
## cssnormalizer | ../lib/cssnormalizer.js
This module normalizes URLs in CSS files provided by modules to absolute paths that are valid on an individual module host. |
    var mod_path			= require('path')
    	mod_assert			= require('assert')
     	 , c 				= console.log
Normalizes a URL in a CSS file. Does not change fully-qualified http:// URLs. ## param: String data CSS data param: String path root path param: String orighost URL of old source host param: String newhost URL of new source host return: String normalized CSS data publi: c |
    function normalize(data, path, orighost, newhost) {
    	var data = data.replace(/url\(['"]?([^'"\)']+)['"]?\)?/mg, function () {
    		var p;
    		if (arguments[1].indexOf('http://') === 0) { return arguments[1]; }
    		p = mod_path.join(path, arguments[1]);
    		if (orighost !== newhost) { p = newhost + p; }
    		p = 'url(\"' + p + '\")';
    		return p;
     	});
     	return data;
    }

    exports.normalize = normalize;
## logger | ../lib/logger.js
Logger module. Has appenders for console and HTTP. |
    function getLogger (name, level) {
        var l = typeof level !== 'undefined' ? level : Logger.SEVERITY.INFO
        return new Logger(name, l);
    }

    function Logger(name, level) {
        if ('undefined' === typeof name) { name = '' }
        this.appender = [ new ConsoleAppender() ]; // yes, I know...
        this.name = name;
        this.level = level;
    }
    Logger.SEVERITY = {
        INFO  : 5,
        DEBUG : 4,
        WARN  : 3,
        ERROR : 2
    }

    //
    // [TBD] add argument style and join
    //
    Logger.prototype.info = function (msg) {
      this._log(msg, Logger.SEVERITY.INFO);
    }
    Logger.prototype.debug = function (msg) {
      this._log(msg, Logger.SEVERITY.DEBUG);
    }
    Logger.prototype.warn = function (msg) {
      this._log(msg, Logger.SEVERITY.WARN);
    }
    Logger.prototype.error = function (msg) {
      this._log(msg, Logger.SEVERITY.ERROR);
    }
    Logger.prototype._log = function (msg, level) {
      if (level &gt; this.level) { return; }
      for (var i = this.appender.length; --i &gt;= 0;) {
        this.appender[i].append(msg, this.name, level);
      }
    }

    function ConsoleAppender () {
    }
    ConsoleAppender.prototype.append = function (msg, logger, level) {
        var col = level == 2 ? '\033[31m' : '\033[32m',
            msg = logger != '' ? (col + logger + ':\033[39m ' + msg ) : msg
        // there's no console.debug, map debug to console.info
        var lvls = {'5':'info', '4':'info', '3':'warn', '2' :'error'};
        console[lvls[level]](msg);
    }

    function HttpLogHostAppender (url) {
        var opts = parseUri(url);
        this.options = {
            host: opts.host,
            port: opts.port,
            path: opts.path,
            method: 'POST',
            headers : {
                &quot;Content-Type&quot; : &quot;application/json&quot;
            }
        };
    }
    HttpLogHostAppender.prototype.append = function (msg, logger, level) {
        var req = http.request(this.options, function(res) {
          res.setEncoding('utf8');
          res.on('data', function (chunk) {});
        });
        var data = {};
        data.msg = msg;
        data.origin = logger;
        data.timestamp = new Date().getTime();
        try {
            req.write(JSON.stringify(data));
        } catch (error) {
            // JSON.stringify does not allow circular references, so we need to
            // catch that here
        }
        req.end();
    }

    exports.getLogger   = getLogger;
    exports.Logger      = Logger;
## modules | ../lib/modules.js
## type: String api: public |
    var mod_sys                 = require('sys')
        , mod_path              = require('path')
        , mod_fs                = require('fs')
        , mod_promise           = require('promised-io')
        , mod_jsdom             = require('jsdom')
        , mod_url               = require('url')
        , mod_querystring       = require('querystring')
        , mod_renderer          = require('./renderer.js')
        , mod_resourcemanager   = require('./resourcemanager.js')
        , tagmanager            = null
        , config                = null
        , taglibrary            = null
        , moduleRootFolder      = mod_path.join(__dirname, '..', 'modules')
        , logger                = require('./logger.js').getLogger('Modules')

    function configure(c, t) {
        config = c;
        tagmanager = t;
    }

    function handleControllerRequest (req, res, next) {
        logger.debug('handleControllerRequest ' + req.url);
        var modulename = req.params[0]
            , method = req.params[1]
            , mp = mod_path.join(moduleRootFolder, 'modules', modulename, 'main.js')

        mod_path.exists(mp, function (exists) {
            if (exists) {
                var module = require(mp);
                if (module[method]) {
                    module[method](req, res).then(function (ret) {
                        res.end(ret.data);
                    });
                } else {
                    res.writeHead(404, { 'Content-Type' : 'text/plain'} );
                    res.end('method not available');
                }
            } else {
                res.writeHead(404, { 'Content-Type' : 'text/plain'} );
                res.end('unknown module ' + modulename);
            }
        });
    }

    function handleScriptRequest (req, res, next) {
        var modulename = req.params[0]
            , path = req.params[1]
            , mp = 'file://' + mod_path.join(moduleRootFolder, modulename, 'htdocs', path);
        logger.debug('handleScriptRequest ' + mp);
        res.setHeader('Content-Type', 'text/javascript');
        mod_resourcemanager.getResource(mp).then(function (resource) {
            res.end(resource.data.toString());
        });
    }

    function handleViewRequest (req, res, next) {
        var query = mod_querystring.parse(mod_url.parse(req.url).query)
            , mode = query.type
            , baseurl = req.url.substring(0, req.url.lastIndexOf('/'))
            , modulename = req.params[0]
            , viewname = req.params[1]
        logger.debug('handleViewRequest, module: ' + req.params[0] + ', view: ' + req.params[1]);

        var r = new mod_renderer.Renderer(tagmanager);
        console.time('render')
        r.render(getViewUrl(modulename, viewname), mode).then(function (doc) {
            console.timeEnd('render');
            res.end(doc);

        });
    }

    const DEFAULT_VIEW = 'main.html';
Gets an absolute file URL of a module. ## param: String moduleId unique module id param: String viewname view name, may be a partial path return: String |
    function getViewUrl(moduleid, viewname) {
        return 'file://' + require('path')
                .join(__dirname
                        , '..'
                        , 'modules'
                        , moduleid
                        , 'htdocs'
                        , viewname ? viewname : DEFAULT_VIEW
                );
    }

    // [TBD] will ask the module factory
    function getModule (modulename) {
        return true;
    }

    exports.handleControllerRequest = handleControllerRequest;
    exports.handleScriptRequest     = handleScriptRequest;
    exports.handleViewRequest       = handleViewRequest;
    exports.configure               = configure;
    exports.getViewUrl              = getViewUrl;
    exports.getModule               = getModule;
## redisclient | ../lib/redisclient.js
Manages the connection to redis db. |
    var redback = require('redback')
        , redbackClient = redback.createClient()

    //cache = redbackClient.createCache('softporn');
    //console.log(require('sys').inspect(redbackClient));
    console.log('redis client started');

    var channel = redbackClient.createChannel(redbackClient);

    channel.publish('foobar', function () {
    	console.log('published');
    });

    channel.subscribe('foobar', function (data) {
    	console.log('received ' + data)
    })

    channel.on('message', function (msg) {
    	console.log('message!! ' + msg);
                // assert.equal('foo', msg);
                // if (msg != 'foo') {
                //     assert.ok(false);
                // }
                // received = true;
            });
## renderer | ../lib/renderer.js
Here da magic. |
    var mod_xml                 = require('node-xml')
        , mod_promise           = require('promised-io')
        , sys                   = require('sys')
        , logger                = require('./logger.js').getLogger('TagManager')
        , mod_tagmanager        = require('./tagmanager.js')
        , mod_resourcemanager   = require('./resourcemanager.js')
        , mod_resources         = require('./resources.js')
        , mod_path              = require('path')
        , mod_modules           = require('./modules.js')

        , c     = console.log
        , i     = sys.inspect
        , mr    = function () { return Math.random()*0; }

    function Renderer(tagmgr) {
        if (!tagmgr) { throw new Error('tagmanager required'); }
        this.tagmanager = tagmgr
        this.renderResources = [];
    }
The main workhorse function. ## param: String View template absolute file:// URL param: String Render mode, 'json' or 'html' returns: Promise |
    Renderer.prototype.render = function (url, mode) {
        var self = this;
        return this._render(url).then(function (renderres) {
           c(self.renderResources.length);
            var deps = self.calcDependencies(renderres)
                , out = self[mode!=='json' ? 'outputHtml' : 'outputJson'](renderres, deps.css, deps.scripts, deps.locales);
            return out;
        } );
    }
    Renderer.prototype.calcDependencies = function (renderres) {
        var self = this
            , css = []
            , scripts = []
            , locales = []
            , regex = new RegExp('file://' + mod_path.join(__dirname, '..'));

            this.renderResources.reverse().forEach(function (dep) {
                c('................... ' + dep.url)
                path = dep.url.replace(regex, &quot;&quot;)
                              .replace(/htdocs.*$/, '');
                Array.prototype.push.apply(css, dep.cssdeps.map(function (item) {
                    return mod_path.join(path, item);
                }));
                Array.prototype.push.apply(scripts, dep.scriptdeps.map(function (item) {
                    return mod_path.join(path,  item);
                }));
                Array.prototype.push.apply(locales, dep.localedeps.map(function (item) {
                    return mod_path.join(path,  'i18n', item);
                }));
            });

            this.renderResources.reverse().forEach(function (dep) {
                c(dep.url);
            });

            return { 'css' : css, 'scripts' : scripts, 'locales' : locales };
        }

    // [TBD] domain of ResourceService
    const RESOURCE_LOADER_URLPATH = &quot;/resources?files&quot;;
Transforms a RenderResource into HTML output format. This includes all dependencies of a view that are rendered into the output by using require.js function calls to have additional dependencies loaded by the web client. ## param: Resource resource RenderedViewResource param: String[] css URL to CSS dependency param: String[] scripts URL to JavaScript dependency param: String[] locales URL to locale dependency |
    Renderer.prototype.outputHtml = function (resource, css, scripts, locales) {
        var doc = resource.data.toString();

        var markup = [];

        // add CSS required by requested view
        // [TBD] the resource service should know how to create links to it, not this module.
        if (css &amp;&amp; css.length) {
          markup.push('<link rel="stylesheet" type="text/css" href="'
                      , RESOURCE_LOADER_URLPATH, '=',css.join(';'), '"/>\n');
        }
        // add JavaScript required by requested view
        // [TBD] the resource service should know how to create links to it, not this module.
        if (scripts &amp;&amp; scripts.length) {
          markup.push('<script type="application/javascript" src="', RESOURCE_LOADER_URLPATH, '='
                      , scripts.join(';'), '"></script>\n');
        }

        locales.forEach(function (locale) {
            c('l ' + locale);
        });

        if (resource.elements.length &gt; 0) {
            markup.push('\n<script type="application/javascript">\n');

            // this.renderResources.reverse().forEach(function (dep) {
            //     markup.push('console.log("'+dep.url+'");');
            // });
            resource.elements.forEach(function (elem) {
                var im = elem.instanceId ? ',"text!/instances/' + elem.instanceId + '.js"' : '';
                var l = elem.locale
                markup.push('\nrequire(["/modules/', elem.moduleId, '/client.js"'
                              , ', "text!/modules/', elem.moduleId, '/main.html?type=json"'
                              , im
                              , ', "text!/modules/', elem.moduleId, '/locales/de_DE.xml"'
                              , '], '
                              , ' function (module, template, instance, localefile) { module.initView("'
                              , elem.id, '", template, instance, localefile) } );'
                );
            });
            markup.push('\n</script>\n');
        }

        // this, erm, could be done more elegantly. but it frakking works for now ^^
        var idx = doc.indexOf('</head>');
        doc = doc.substring(0, idx) + markup.join('') + doc.substring(idx, doc.length);

        return doc;
    }
Renders a resource as JSON. Currently mixes up partials and JSON output, which should not be the same. ## param: RenderedViewResource resource Resource to render param: String[] css URL to CSS dependency param: String[] scripts URL to JavaScript dependency param: String[] locales URL to locale dependency |
    Renderer.prototype.outputJson = function (resource, css, scripts, locales) {
        var body = resource.data.toString().match(/(<body[^>]*>)([\s\S]*)(<\/body&gt;)/mi)[2];
        var obj = {
          &quot;resources&quot; : {
            &quot;css&quot;       : css
            , &quot;script&quot;  : scripts
            , &quot;locales&quot; : locales
          }
          , &quot;content&quot; : body
        };
        return JSON.stringify(obj);
    }

    Renderer.prototype._render = function (url) {
        var p = new mod_promise.Promise()
            , self = this
        c('render ' + url);

        self.loadResource(url).then(function (resource){
            if (!resource instanceof mod_resources.Resource) { throw new Error('wrong type'); }
            self.parse(resource).then(function (renderres) {
                if (!renderres instanceof mod_resources.RenderedViewResource) { throw new Error('wrong type'); }
                self.renderResources.push(renderres);
                c('pushing ' + renderres.elements)
                var r = []
                    , depsdone = {};
                if (renderres &amp;&amp; renderres.elements) {
                    renderres.elements.forEach(function (item) {
                        debugger;
                        if (mod_modules.getModule(item.moduleId)) {
                            var viewurl = mod_modules.getViewUrl(item.moduleId, 'main.html');
                            if (!depsdone[viewurl]) {
                                r.push(self._render(viewurl));
                            } else { }
                            depsdone[viewurl] = true;
                        } else {
                            throw new Error('module not found');
                        }
                    });
                    mod_promise.all(r).then(function (vals) {
                        c('resolved all sub-renders ' + url);
                        p.resolve(renderres);
                    });
                } else {
                    p.resolve(renderres);
                }
            });
        });
        return p;
    }

    Renderer.prototype.parse = function (resource) {
        return parseHtmlView(resource, this.tagmanager);
    }

    Renderer.prototype.loadResource = function (url) {
        return mod_resourcemanager.getResource(url);
    }

    exports.Renderer = Renderer;

    function parseHtmlView (resource, tagmanager) {
        var defer               = new mod_promise.defer()
            , url               = resource.url
            , parser            = new mod_xml.SaxParser(new DocParser())

            // these are filled by the parser and returned to the caller
            , elements          = [] // web components used by view, in parse order
            , outputBuffer      = []
            , cssresources      = [] // css resources used by view, in document order
            , scriptresources   = [] // javasript resources used by view, in document order
            , textresources     = [] // javasript resources used by view, in document order

        parser.parseString(resource.data.toString());
        return defer.promise;

        function DocParser() {
            var tagRemoved = false;

            return function (cb) {
                cb.onStartDocument(function() {
                    //console.log('onStartDocument');
                });

                cb.onEndDocument(function() {
                    console.log(elements);
                    defer.resolve(new mod_resources.RenderedViewResource(url
                                                                        , new Buffer(outputBuffer.join(''))
                                                                        , elements
                                                                        , cssresources
                                                                        , scriptresources
                                                                        , textresources)
                    );
                });

                cb.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
                    //sys.puts("=====> Started: " + elem + " uri="+uri +" (Attributes: " + JSON.stringify(attrs) + " )");
                    var instanceid
                        , viewname

                    //
                    // a document should not be transformed in parser.
                    // reminder to refactor to new render transformer
                    //
                    //logger.debug(uri);
                    // [TBD] should do namespace check
                    if (elem === 'link') {
                        attrs.forEach(function (item) {
                            if (item[0] === 'href') {
                                cssresources.push(item[1]);
                                tagRemoved = true;
                            }
                        });
                    } else if (elem == 'script') {
                        attrs.forEach(function (item) {
                            if (item[0] === 'src') {
                                scriptresources.push(item[1]);
                                tagRemoved = true;
                            }
                        });
                    } else if (elem == 'text' &amp;&amp; prefix === 'tx') {
                        attrs.forEach(function (item) {
                            if (item[0] === 'key') {
                                textresources.push(item[1]);
                            }
                        });
                    } else {
                        var tag = tagmanager.getTag(elem, attrs, prefix, uri, namespaces);
                        if (tag !== null) {
                            //logger.debug('found tag ' + tag.selector);
                            for (var i = 0; i &lt; attrs.length; i++) {
                                if (attrs[i][0] === 'instanceid') {
                                    instanceid = attrs[i][1];
                                    break;
                                }
                                if (attrs[i][0] === 'view') {
                                    viewname = attrs[i][1];
                                    break;
                                }
                            }
                            var eid = addId(attrs);
                            var elemResource = {
                                'id' : eid
                                , 'instanceId'  : instanceid
                                , 'moduleId'    : tag.module
                                , 'view'        : viewname
                            };
                            elements.push(elemResource);
                        }
                    }

                    if (!tagRemoved) {
                        outputBuffer = outputBuffer.concat(copyStartTag(elem, attrs, prefix, uri, namespaces));
                    }
                    addId(attrs);
                });

                cb.onEndElementNS(function(elem, prefix, uri) {
                    if (!tagRemoved) {
                        outputBuffer.push(&quot;&lt;/&quot;, (prefix ? prefix + &quot;:&quot; : &quot;&quot;), elem, &quot;&gt;&quot;);
                    } else { }
                    tagRemoved = false;
                });

                cb.onCharacters(copy);
                cb.onCdata(copy);
                //cb.onComment(copy);

                function copy(chars) {
                    outputBuffer.push(chars);
                };

                cb.onWarning(function(msg) {
                    logger.warn('warning ' + msg);
                });

                cb.onError(function(msg) {
                    logger.error('<ERROR>'+JSON.stringify(msg)+&quot;&lt;/ERROR&gt;&quot;);
                });
            }
        }
    }
add an 'id' attribute to identify the element when inserting its rendered content ## param: attributes[] attrs attributes from node-xml sax return: String existing or new element id |
    function addId(attrs) {
        var eid
            // [TBD] use fixed format so file sizes is guaranteed on equal input
            , elemId = &quot;module&quot; + new Date().getTime() + &quot;&quot; + parseInt(Math.random()*10);
        for (var j = 0, al = attrs.length, hasId = false; j &lt; al; j++) {
            if (attrs[j][0] === &quot;id&quot;) {
                eid = attrs[j][1];
                hasId = true;
            }
        }
        if (!hasId) {
            attrs.push([&quot;id&quot;, elemId]);
            return elemId
        } else {
            return eid;
        }
    }

    function copyStartTag(elem, attrs, prefix, uri, namespaces) {
        var outputBuffer = [];
        outputBuffer.push(&quot;&lt;&quot;, (prefix ? prefix + &quot;:&quot; : &quot;&quot;), elem);
        if (namespaces.length &gt; 0 || attrs.length &gt; 0) {
            outputBuffer.push(&quot; &quot;);
        }
        //for (i = 0; i < namespaces.length; i++) {
        //    outputBuffer.push("xmlns:" + namespaces[i][0] + "=\"" + namespaces[i][1] + "\" ");
        //    savedNamespaces[namespaces[i][1]] = namespaces[i][0];
        //}
        for (var i = 0; i &lt; attrs.length; i++) {
            outputBuffer.push(attrs[i][0], '="', attrs[i][1], '"');
            if (i &lt; attrs.length - 1) {
                outputBuffer.push(' ');
            }
        }
        outputBuffer.push(['meta', 'link', 'br', 'img', 'input'].indexOf(elem) &gt; -1 ? '/>' : '>');
        return outputBuffer;
    }
## resourcemanager | ../lib/resourcemanager.js
Resources are normally not constructed manually but accessed using this module. If caching is enabled, the Resource Manager takes care of caching. ## Todos Resource Resolution Define schemes and transitions: External HTTP URLs --> Routed URLS --> Module-internal URLs --> File system URLs; vice versa Module-specific URIs, e.g. module://weather;1.0.1 maps to [determined by global web component broker] Need clear borders between sub-components for all URL schemes |
    var resources           = require('./resources.js')
        , mod_events        = require('events')
        , mod_resources     = require('./resources.js')
        , mod_promise       = require('promised-io')
        , mod_path          = require('path')
        , mod_sys           = require('sys')
        , logger            = require('./logger.js').getLogger('ResourceManager')
        , config            = null
        , cache             = null
        , c                 = console.log

    // we are in ./lib, document root is always one up
    const documentRoot      = mod_path.join(__dirname, '..');

    function configure(c, ch) {
        config = c;
        cache = ch;
    }
Convenience array style version of getResource ## param: String[] urls URL to resource return: Promise Promise, is resolved once all resources are loaded publi: c |
    function getResources(urls) {
        var defer = mod_promise.defer();
        var self = this;
        var u =
        mod_promise.all(urls.map(function (item) {
                            return self.getResource(item);
                        }))
                        .then(function (all) {
                            defer.resolve(all);
                        });
        return defer.promise;
    }
Loads a resource from either a file://, http:// or relative path. ## param: String url file:// or http:// URL return: Promise Promise, is resolved when resource is ready publi: c |
    function getResource (url) {
        return loadUrl(url);
        // var defer = mod_promise.defer();
        // loadUrl(url).then(function (res) {
        //     defer.resolve(res);
        // });
        // return defer.promise;
    }
Gets a resource either from the cache (if enabled) or loads it. ## param: String url resource url param: Promise Promise, resolves to resource publi: c |
    function loadUrl(url) {
        var defer = mod_promise.defer()
            , type = null
            , resource = null

        if (cache) {
            cache.fromCache(url).then(function (resource) {
            if (!resource) {
                resource = getResourceByUrl(url)
                        .then(function (resource) {
                            cache.toCache(url, resource.data);
                            defer.resolve(resource);
                        });
            } else {
                defer.resolve(resource);
            }
            });
        } else {
            resource = getResourceByUrl(url)
                        .then(function (resource) {
                            defer.resolve(resource);
                        });
        }

        return defer.promise;
    }
Gets the correct resource type assiocated to a URL without querying the cache. ## param: String url resource URL param: Promise promise, resolves to resource instance. publi: c |
    function getResourceByUrl(url) {
        var defer       = mod_promise.defer()
            , resource  = null
            , intUrl    = url

        if (url.indexOf('http://') === 0) {
            resource = new mod_resources.HttpResource();
        } else if (url.indexOf('file://') === 0) {
            resource = new mod_resources.FileResource();
        } else {                                                    // assume it's a file location
            intUrl = translatePathToFileUrl(url);
            resource = new mod_resources.FileResource();
        }
        //c('getResourceByUrl ' + url + (url != intUrl ? url : ''))
        resource.load(intUrl);
        resource.addListener('stateChanged', function () {
            defer.resolve(resource);
        });

        return defer.promise;
    }
Map a standard file path to a absolute file:// URL. Paths are always assumed relative to an installation root folder. ## param: String path file path param: String an absolute file:// URL publi: c |
    function translatePathToFileUrl(path) {
        var p = 'file://'+ mod_path.join(documentRoot, path);
        return p;
    }

    exports.getResources            = getResources;
    exports.getResource             = getResource;
    exports.configure               = configure;
    exports.translatePathToFileUrl  = translatePathToFileUrl;
## resources | ../lib/resources.js
Everything is a resource. ## Todos Resources should be sealed and/or frozen after loading (state 'complete') ## type: String api: public |
    var logger          = require('./logger.js').getLogger('Resource')
        , mod_util      = require('util')
        , mod_events    = require('events')
        , mod_http      = require('http')
        , mod_fs        = require('fs')
        , mod_url       = require('url')

    function uuid() {
        return new Date().getTime() + '-' + Math.round(Math.random()*10000);
    }
class: Base resource class ## param: String URL of resource param: String resource payload |
    function Resource(url, data) {
        this._state = Resource.STATE.INIT;
        if (typeof url !== 'undefined' &amp;&amp; typeof data !== 'undefined') {
            this.url = url;
            this.data = new Buffer(data);
            this._state = Resource.STATE.READY;
        } else {
            this.data = null;
        }

        this.uuid = uuid();
    };
    mod_util.inherits(Resource, mod_events.EventEmitter);

    Object.defineProperty(Resource.prototype, 'state', {
        get : function () {
            return this._state;
        },
        set : function (val) {
            if ('number' !== typeof val) throw Error('number expected, got ' + val);
            if (val !== this._state) {
                this._state = val;
                this.emit('stateChanged', this);
            }
        }
    });
Add a resource as a dependency to this resource. ## param: Resource res |
    Resource.prototype.addDependency = function (res) {
        this._requiredResources[res.uuid] = res;
        var self = this;
        res.addListener('stateChanged', function () {
            //logger.debug('state changed from sub ' + this.uuid);
            self._onDepStateChange.call(self, res);
        });
    };

    Resource.prototype._onDepStateChange = function (resource) {
        //logger.debug('_onDepStateChange from ' + resource.uuid + '@' + this.uuid);
        var state = Resource.STATE.READY;
        for (rn in this._requiredResources) {
            state = Math.min(this._requiredResources[rn].state, state);
        }
        this.state = state;
    };
Must be implemented by subclasses. |
    Resource.prototype.load = function () {
        throw new Error('not implemented');
    }

    Resource.STATE = {
        INIT        : 0
        , LOADING     : 2
        , WAITING     : 4
        , READY       : 6
    };

    exports.Resource = Resource;
class: FileResource ## param: String file URL of resource param: String resource payload |
    function FileResource() {
        Resource.apply(this, arguments);
    }
    mod_util.inherits(FileResource, Resource);
## param: String file file URL of resource |
    FileResource.prototype.load = function (file) {
        if ('undefined' === typeof file || file.indexOf('file://') !== 0) {
            throw new Error('missing or faulty argument');
        }
        this.url = file;
        this.state = Resource.STATE.LOADING;
        var self = this,
            file = file.substring(7);
        mod_fs.readFile(file, function (err, data) {
            if (err) { throw err; }
            self.data = data;
            self.state = Resource.STATE.READY;
        });
    };
class: HttpResource ## param: String http URL of resource param: String resource payload |
    function HttpResource() {
        Resource.apply(this);
    }
    mod_util.inherits(HttpResource, Resource);
## param: String url http URL of resource |
    HttpResource.prototype.load =  function (url) {
        if ('undefined' === typeof url || url.indexOf('http://') !== 0) {
            throw new Error('missing or faulty argument');
        }
        var self = this,
            req;
        this.url = url;
        this.state = Resource.STATE.LOADING;
        this.content = '';
        this._options = mod_url.parse(url);

        req = mod_http.get(this._options, function (res) {
            if (res.statusCode &gt;= 400 &amp;&amp; res.statusCode &lt;= 500) {
                throw new Error('URL could not be loaded, code ' + res.statusCode
                    + ', url ' + self._options.host + ':' + self._options.port + self._options.path);
            }
            res.on('data', function (data) {
                self.data = data;
            });
            res.on('end', function () {
                self.state = Resource.STATE.READY;
            });
        });
    };
class: HttpResource ## param: String url http URL of view template resource param: Buffer data param: String[] cssdeps param: String[] scriptdeps param: String[] localdeps |
    function RenderedViewResource(url, data, elements, cssdeps, scriptdeps, localedeps) {
        Resource.call(this, url, data);
        this.elements = elements;
        this.cssdeps = cssdeps;
        this.scriptdeps = scriptdeps;
        this.localedeps = localedeps;
    }

    RenderedViewResource.prototype.toString = function () {
        process.exit();
        return this.url + ',' + this.cssdeps + '' + this.scriptdeps + ',' + this.localedeps;
    }
    mod_util.inherits(RenderedViewResource, Resource);

    Resource.prototype.toString = function () {
        process.exit();
    }

    exports.FileResource = FileResource;
    exports.HttpResource = HttpResource;
    exports.RenderedViewResource = RenderedViewResource;
## server | ../lib/server.js
main server. |
    var mod_connect         = require('connect')
        , mod_sys               = require('sys')
        , mod_path              = require('path')
        , mod_resourceservice   = null
        , mod_resourcemanager   = require('./resourcemanager.js')
        , mod_tagmanager        = require('./tagmanager.js')
        , mod_cache             = require('./Cache.js')
        , mod_socketio          = require('./socketio.js')
        , mod_modules           = require('./modules.js')
        , mod_fs                = require('fs')
        , cache                 = null
        , logger                = require('./logger.js').getLogger('Server')

    if (process.argv.length &lt; 3) {
        logger.error('usage: ...');
        process.exit();
    }

    // [TBD] handle arguments properly
    // [TBD] dynamic config service
    var config = null;
    logger.info('reading config from ' + process.argv[2]);
    mod_fs.readFile(process.argv[2], function (err, data) {
        if (err) {
            logger.error('error reading configuration');
            process.exit();
        }
        config = JSON.parse(data);
        logger.info('config loaded');
        mod_cache.configure(config);
        mod_resourcemanager.configure(config, mod_cache);
        mod_resourceservice = require('./resourceservice.js')(config, mod_resourcemanager);
        mod_tagmanager.setTagList(config.taglib);
        mod_modules.configure(config, mod_tagmanager);
        createServer(config);
    });

    function createServer(config) {
        var server = mod_connect.createServer(
            mod_connect.favicon()
            , mod_connect.router(function (app) {
                    app.get(/^\/modules\/([^\.]*)\/(.*\.js)$/,          mod_modules.handleScriptRequest);
                    app.get(/^\/modules\/([^\.]*)\/(.*\.html)$/,        mod_modules.handleViewRequest);
                    app.get(/^\/modules\/([^\.]*)\/controller\/(.*)$/,  mod_modules.handleControllerRequest);
                    app.get(/^\/resources(.*)$/,                        mod_resourceservice.handleRequest);
                    //app.get(/instances\/(.*)/,                          mod_instances.handleInstanceRequest);
                }
            )
            , mod_connect.static(config.server.documentRoot)
            , mod_connect.logger()
        );

        //var io = require('socket.io').listen(server);
        //mod_socketio.init(io);
        server.listen(config.server.port);
    }

    // process.on('SIGINT', function () {
    //  process.exit();
    // });
## resourceservice | ../lib/resourceservice.js
The resource service, code name "cyclone", is a center piece of Rain. Clients can call it with a list of resources separated by semicolons, that are loaded and concatenated in the order of their occurence. Rain currently creates request markup to this service in the render process. ## Examples <link rel="stylesheet" type="text/css" href="/resources?files=/modules/scrollabletable/main.css ;/modules/domains/main.css;/modules/weather/main.css;/modules/app/application.css ;/modules/app/jquery-ui/css/smoothness/jquery-ui-1.8.14.custom.css"/> <script type="application/javascript" src="/resources?files=/modules/app/require-jquery.js ;/modules/app/jquery-ui/js/jquery-ui-1.8.14.custom.min.js ;/modules/app/js/socket.io/socket.io.js"></script> A file must follow some simple rules to be loadable by the resource service: For URLs, e.g. for background images, always use the url() notation, since the parsers won't recognize them otherwise. ## Todos refactor to use the resource manager add error handling for files not found rewrite URLs used url() in CSS files connect to redis for caching support for HTTP files correct distinction between local and remote file by protocol put the little fucker into its own worker |
    module.exports = function (c, resmgr) {

    	var  mod_fs		 		= require('fs')
    		, mod_url			= require('url')
    		, mod_http			= require('http')
    		, mod_promise		= require('promised-io')
    		, mod_path			= require('path')
    		, mod_cssnormalizer = require('./cssnormalizer.js')
    		, logger			= require('./logger.js').getLogger('ResourceService')
    		, config 			= c
    		, resourceManager   = resmgr

    	if (!config || !resmgr) { throw new Error('dependencies missing'); }
     * Handles resource requests, usually created by the render engine when scanning a template file.
     * Resource requests are aggregated requests to CSS or JavaScript files, both can not be mixed
     * into a single request. The correct mime type is determined by the suffix of the first
     * file.
     *
     * @param {request} req Request object
     * @param {response} res Response object
     * @param {response} next Next connect middleware routing rule
     * @public
    function handleRequest (req, res, next) {
    		var url = mod_url.parse(req.url, true)
    			, files = url.query.files.indexOf(';') === -1
    				? [url.query.files]
    				: url.query.files.split(';')
    			, isCss = files[0].lastIndexOf('.css') == files[0].length - 4
    			, absFiles;

    		// The config must be present for computing the absolute file path of a resource.
    		if (!config || !resourceManager) {
    			logger.error('config not found');
    			return;
    		}

    		files = files.map(function (url) {
    			return config.server.serverRoot + url.replace(new RegExp(&quot;^(/modules\/[^\/]+)"), "$1/htdocs/&quot;)
    		})

    		resourceManager.getResources(files).then(function (resources) {
    			var output = [];
    			resources.forEach(function (resource) {
    				output.push('\n\n\n\/\*\* FILE ', resource.url, ' */\n', resource.data.toString());
    			});
    			res.end(output.join(''));
    		});
    	}

    	return {
    		'handleRequest' : handleRequest
    	}
    };
## socketio | ../lib/socketio.js
This does not work yet as the connection is lost on re-loading the server. Client reload should go into the 'demon' script (to be build from the ashes of run.js) |
    function init (io) {
    	io.sockets.on('connection', function (socket) {
    		console.log('client connected');
    		socket.on('rain.application.state', function (from, msg) {
    	    	console.log('rain.application.state message', from, ':', msg);
    		});

    		socket.on('disconnect', function () {
    			io.sockets.emit('user disconnected');
    		});
    	});
    }

    function send (io) {
    	console.log('send reload request');
    	io.sockets.emit('rain.application.reload', { 'reload' : true } );
    }

    exports.init = init;
## tagmanager | ../lib/tagmanager.js
The Tag Manager handles the mappings between CSS selectors on view templates and web components. ## Todos Make it dynamicly configurable |
    var logger        = require('./logger.js').getLogger('TagManager');

    function TagManager() {

      var taglist = [];

      this.addTag = function (tag) {
        taglist.push(tag);
      }

      this.setTagList = function (tags) {
        taglist = tags
      }
[TBD] This code sucks big time... I need to organize tags more efficiently to get rid of this ugly and buggy piece of code. Sizzle could be used, but it's most probably way too slow. ## Todos find best match, selectors with predicated must be ranked higher more than one module my map to a single element |
    this.getTag = function (elem, attrs, prefix, uri, namespaces) {
        //logger.debug(JSON.stringify(arguments));
        for (var i = 0, tag = null; i &lt; taglist.length; i++) {
          tag = taglist[i];
          if (uri !== null &amp;&amp; tag.namespace != '' &amp;&amp; tag.namespace != uri) {
            //logger.debug('tag has namespace, but no match');
            continue;
          }

          var si = tag.selector.indexOf('[');
          if (si !== -1) {
            var tagelem = tag.selector.substring(0, si),
                preds = {},
                ps,
                attrh = {};
            ps = tag.selector.substring(si).split(']').forEach(function (val) {
              if (val === '') return;
              preds[val.substring(1, val.indexOf('='))] = val.substring(val.indexOf('=') + 1);
            });
            for (var j = 0; j &lt; attrs.length; j++) {
              attrh[attrs[j][0]] = attrs[j][1];
            }
          } else {
            var tagelem = tag.selector;
          }

          var fe = true;
          if (tagelem !== elem &amp;&amp; tagelem !== '*') {
            //logger.debug('element name not matched');
            fe = false;
            continue;
          }

          var fp = true
          if (si !== -1) {
            var count = 0;
            for (pred in preds) {
              //logger.debug('pred ' + pred + ". " + attrh[pred] + "," + preds[pred]);
              if (attrh[pred] !== preds[pred]) {
                fp = false;
                //logger.debug('attribute val not mached');
                break;
              }
              count++;
            }
            if (count -1 &gt;= j) {
              fp = false;
              //logger.debug('matched not all predicates');
              break;
            }
          }

          if (!fp) {
            //logger.debug('attrs not matched');
            continue;
          }
          if (!fe) {
            //logger.debug('selector not matched');
            continue;
          }

          //logger.debug('matching tag ' + tag.selector);
          return tag;
        }

        return null;
      }
    }

    module.exports = new TagManager();
## modulecontainer | ../lib/modulecontainer.js
Module Container, Manager, Factory, whatever. All module related thingz must belong to us. ## Todos scan the local module folder for web component descriptors as resources. download module descriptors from remote web component hosts as resources. provide web component objects. crud on module instances. |
    var mod_fs = require('fs')
    	, mod_resourcemanager = require('./resourcemanager.js')
    	, path = require('path')

    function moduleRootFolder() {
    	return path.join(__dirname, '..', 'modules');
    }

    function scanFolder(folder) {
    	mod_fs.readdir(moduleRootFolder(), function (err, files) {
    		if (err) throw err;

    		mod_resourcemanager.getResources(['file://' + moduleRootFolder() + '/app/htdocs/index.html']).then(function () {
    			console.log('grunz...');
    		});

    	});
    }

    scanFolder();