lib/assets/CacheManifest.js

lib/ AssetGraph.js errors.js index.js query.js
assets/ Asset.js Atom.js CacheManifest.js CoffeeScript.js Css.js Flash.js Gif.js Htc.js Html.js I18n.js Ico.js Image.js JavaScript.js Jpeg.js Json.js KnockoutJsTemplate.js Less.js Png.js Rss.js StaticUrlMap.js Stylus.js Text.js Xml.js index.js
relations/ CacheManifestEntry.js CssAlphaImageLoader.js CssBehavior.js CssFontFaceSrc.js CssImage.js CssImport.js CssUrlTokenRelation.js HtmlAlternateLink.js HtmlAnchor.js HtmlAppleTouchStartupImage.js HtmlApplet.js HtmlAudio.js HtmlCacheManifest.js HtmlConditionalComment.js HtmlDataBindAttribute.js HtmlEdgeSideInclude.js HtmlEmbed.js HtmlFrame.js HtmlIFrame.js HtmlIFrameSrcDoc.js HtmlImage.js HtmlInlineScriptTemplate.js HtmlKnockoutContainerless.js HtmlObject.js HtmlRelation.js HtmlRequireJsMain.js HtmlScript.js HtmlShortcutIcon.js HtmlStyle.js HtmlStyleAttribute.js HtmlVideo.js HtmlVideoPoster.js JavaScriptAmdDefine.js JavaScriptAmdRequire.js JavaScriptCommonJsRequire.js JavaScriptExtJsRequire.js JavaScriptGetStaticUrl.js JavaScriptGetText.js JavaScriptInclude.js JavaScriptShimRequire.js JavaScriptTrHtml.js Relation.js StaticUrlMapEntry.js index.js
resolvers/ data.js extJs4Dir.js file.js fixedDirectory.js http.js index.js javascript.js
transforms/ addCacheManifest.js bundleRelations.js bundleRequireJs.js compileCoffeeScriptToJavaScript.js compileLessToCss.js compileStylusToCss.js compressJavaScript.js convertCssImportsToHtmlStyles.js convertHtmlStylesToInlineCssImports.js convertStylesheetsToInlineStyles.js drawGraph.js executeJavaScriptInOrder.js externalizeRelations.js flattenStaticIncludes.js inlineCssImagesWithLegacyFallback.js inlineRelations.js loadAssets.js mergeIdenticalAssets.js minifyAssets.js moveAssets.js moveAssetsInOrder.js populate.js prettyPrintAssets.js pullGlobalsIntoVariables.js registerRequireJsConfig.js removeAssets.js removeRelations.js setAssetContentType.js setAssetEncoding.js setAssetExtension.js setHtmlImageDimensions.js startOverIfAssetSourceFilesChange.js writeAssetsToDisc.js writeAssetsToStdout.js writeStatsToStderr.js
util/ deepCopy.js extendWithGettersAndSetters.js fsTools.js getImageInfoFromBuffers.js memoizeAsyncAccessor.js uniqueId.js urlTools.js
var util = require('util'),
    _ = require('underscore'),
    errors = require('../errors'),
    extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
    relations = require('../relations'),
    Text = require('./Text');

function CacheManifest(config) {
    Text.call(this, config);
}

util.inherits(CacheManifest, Text);

extendWithGettersAndSetters(CacheManifest.prototype, {
    contentType: 'text/cache-manifest',

    supportedExtensions: ['.appcache'],

    get parseTree() {
        if (!this._parseTree) {
            var parseTree = {},
                syntaxErrors = [],
                currentSectionName = 'CACHE';
            this.text.split(/\r?\n|\n?\r/).forEach(function (line, i) {
                if (i === 0) {
                    if (line === 'CACHE MANIFEST') {
                        return; // Skip
                    } else {
                        console.warn("Warning: First line of cache manifest wasn't CACHE MANIFEST");
                    }
                }
                var matchNewSection = line.match(/^(CACHE|NETWORK|FALLBACK):\s*$/);
                if (matchNewSection) {
                    currentSectionName = matchNewSection[1];
                } else if (!/^\s*$/.test(line)) {
                    if (!(currentSectionName in parseTree)) {
                        parseTree[currentSectionName] = [];
                    }
                    if (/^\s*#/.test(line)) {
                        parseTree[currentSectionName].push({
                            comment: line.replace(/^\s*#/, "")
                        });
                    } else {
                        var tokens = line.replace(/^\s+|\s+$/g).split(" "), // Trim just in case
                            node;
                        if (tokens.length === (currentSectionName === 'FALLBACK' ? 2 : 1)) {
                            parseTree[currentSectionName].push({
                                tokens: tokens
                            });
                        } else {
                            syntaxErrors.push(new errors.SyntaxError({
                                message: 'CacheManifest.parseTree getter: Parse error in section ' + currentSectionName + ', line ' + i + ': ' + line,
                                line: i
                            }));
                        }
                    }
                }
            }, this);
            if (syntaxErrors.length > 0) {
                if (this.assetGraph) {
                    syntaxErrors.forEach(function (syntaxError) {
                        this.assetGraph.emit('error', syntaxError);
                    }, this);
                } else {
                    throw new Error(_.pluck(syntaxErrors, 'message').join("\n"));
                }
            }
            this._parseTree = parseTree;
        }
        return this._parseTree;
    },

    set parseTree(parseTree) {
        this.unload();
        this._parseTree = parseTree;
        this.markDirty();
    },

    get text() {
        if (!('_text' in this)) {
            if (this._parseTree) {
                this._text = "CACHE MANIFEST\n";

                function getSectionText(nodes) {
                    return nodes.map(function (node) {
                        if ('comment' in node) {
                            return "#" + node.comment;
                        } else {
                            return node.tokens.join(" ");
                        }
                    }).join("\n") + "\n";
                }

                // The heading for the CACHE section can be omitted if it's the first thing in the manifest,
                // so put it first if there is one.
                if (this._parseTree.CACHE) {
                    this._text += getSectionText(this._parseTree.CACHE);
                }
                _.each(this._parseTree, function (nodes, sectionName) {
                    if (sectionName !== 'CACHE' && nodes.length) {
                        this._text += sectionName + ":\n" + getSectionText(nodes);
                    }
                }, this);
            } else {
                this._text = this._getTextFromRawSrc();
            }
        }
        return this._text;
    },

    findOutgoingRelationsInParseTree: function () {
        var outgoingRelations = [];
        // Traverse the sections in alphabetical order so the order of the relations is predictable
        Object.keys(this.parseTree).sort().forEach(function (sectionName) {
            var nodes = this.parseTree[sectionName];
            if (sectionName !== 'NETWORK') {
                nodes.forEach(function (node) {
                    if (node.tokens) {
                        // In the CACHE section there's only one token per entry, in FALLBACK
                        // there's the online URL followed by the offline URL (the one we want).
                        // Just pick the last token as the url.
                        outgoingRelations.push(new relations.CacheManifestEntry({
                            from: this,
                            to: {
                                url: node.tokens[node.tokens.length - 1]
                            },
                            node: node,
                            sectionName: sectionName
                        }));
                    }
                }, this);
            }
        }, this);
        return outgoingRelations;
    }
});

// Grrr...
CacheManifest.prototype.__defineSetter__('text', Text.prototype.__lookupSetter__('text'));

module.exports = CacheManifest;