Skip to content

Commit

Permalink
Refactor the Tiddler object to enforce immutability
Browse files Browse the repository at this point in the history
And in the process move the fields out of the fields member
  • Loading branch information
Jeremy Ruston committed Jan 17, 2012
1 parent 2ff603d commit b898afe
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 87 deletions.
2 changes: 1 addition & 1 deletion js/BitmapParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var BitmapParseTree = function() {

BitmapParseTree.prototype.compile = function(type) {
if(type === "text/html") {
return "(function (tiddler,store,utils) {return '<img src=\"data:' + tiddler.fields.type + ';base64,' + tiddler.fields.text + '\">';})";
return "(function (tiddler,store,utils) {return '<img src=\"data:' + tiddler.type + ';base64,' + tiddler.text + '\">';})";
} else {
return null;
}
Expand Down
24 changes: 12 additions & 12 deletions js/Recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Recipe.prototype.loadTiddlerFiles = function(recipeLine) {
filename = path.basename(filepath), // eg *.js
posStar = filename.indexOf("*");
if(posStar !== -1) {
var fileRegExp = new RegExp("^" + filename.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace("*",".*") + "$");
var fileRegExp = new RegExp("^" + filename.replace(/[\-\[\]{}()+?.,\\\^$|#\s]/g, "\\$&").replace("*",".*") + "$");
var files = fs.readdirSync(path.resolve(path.dirname(recipeLine.contextPath),filedir));
for(var f=0; f<files.length; f++) {
if(fileRegExp.test(files[f])) {
Expand Down Expand Up @@ -341,7 +341,7 @@ Recipe.tiddlerOutputter = {
// Lines starting with //# are removed from javascript tiddlers
for(var t=0; t<tiddlers.length; t++) {
var tid = this.store.getTiddler(tiddlers[t]),
text = tid.fields.text;
text = tid.text;
// For compatibility with cook.rb, remove one trailing \n from tiddler
text = text.charAt(text.length-1) === "\n" ? text.substr(0,text.length-1) : text;
var lines = text.split("\n");
Expand Down Expand Up @@ -370,7 +370,7 @@ Recipe.tiddlerOutputter = {
tid = this.store.getTiddler(title);
out.push("<" + "script type=\"application/javascript\">");
out.push("define(\"" + title + "\",function(require,exports,module) {");
out.push(tid.fields.text);
out.push(tid.text);
out.push("});");
out.push("</" + "script>");
}
Expand All @@ -388,24 +388,24 @@ Recipe.prototype.cookRss = function() {
return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
},
tiddlerToRssItem = function(tiddler,uri) {
var s = "<title" + ">" + utils.htmlEncode(tiddler.fields.title) + "</title" + ">\n";
s += "<description>" + utils.htmlEncode(me.store.renderTiddler("text/html",tiddler.fields.title)) + "</description>\n";
var s = "<title" + ">" + utils.htmlEncode(tiddler.title) + "</title" + ">\n";
s += "<description>" + utils.htmlEncode(me.store.renderTiddler("text/html",tiddler.title)) + "</description>\n";
var i;
if(tiddler.fields.tags) {
for(i=0; i<tiddler.fields.tags.length; i++) {
s += "<category>" + tiddler.fields.tags[i] + "</category>\n";
if(tiddler.tags) {
for(i=0; i<tiddler.tags.length; i++) {
s += "<category>" + tiddler.tags[i] + "</category>\n";
}
}
s += "<link>" + uri + "#" + encodeURIComponent(encodeTiddlyLink(tiddler.fields.title)) + "</link>\n";
if(tiddler.fields.modified) {
s +="<pubDate>" + tiddler.fields.modified.toUTCString() + "</pubDate>\n";
s += "<link>" + uri + "#" + encodeURIComponent(encodeTiddlyLink(tiddler.title)) + "</link>\n";
if(tiddler.modified) {
s +="<pubDate>" + tiddler.modified.toUTCString() + "</pubDate>\n";
}
return s;
},
getRssTiddlers = function(sortField,excludeTag) {
var r = [];
me.store.forEachTiddler(sortField,excludeTag,function(title,tiddler) {
if(!tiddler.hasTag(excludeTag) && tiddler.fields.modified !== undefined) {
if(!tiddler.hasTag(excludeTag) && tiddler.modified !== undefined) {
r.push(tiddler);
}
});
Expand Down
2 changes: 1 addition & 1 deletion js/SVGParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var SVGParseTree = function() {

SVGParseTree.prototype.compile = function(type) {
if(type === "text/html") {
return "(function (tiddler,store,utils) {return tiddler.fields.text;})";
return "(function (tiddler,store,utils) {return tiddler.text;})";
} else {
return null;
}
Expand Down
71 changes: 53 additions & 18 deletions js/Tiddler.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,57 @@ var utils = require("./Utils.js"),
ArgParser = require("./ArgParser.js").ArgParser;

var Tiddler = function(/* tiddler,fields */) {
this.parseTree = null; // Caches the parse tree for the tiddler
this.renderers = {}; // Caches rendering functions for this tiddler (indexed by MIME type)
this.renditions = {}; // Caches the renditions produced by those functions (indexed by MIME type)
this.fields = {};
for(var c=0; c<arguments.length; c++) {
var arg = arguments[c],
src = null;
this.cache = {}; // Expose the cache object
var fields = {}, // Keep the fields private, later we'll expose getters for them
tags, // Keep the tags separately because they're the only Array field
f,t,c,arg,src;
// Accumulate the supplied fields
for(c=0; c<arguments.length; c++) {
arg = arguments[c];
src = null;
if(arg instanceof Tiddler) {
src = arg.fields;
} else {
src = arg;
}
for(var t in src) {
var f = this.parseTiddlerField(t,src[t]);
for(t in src) {
f = Tiddler.parseTiddlerField(t,src[t]);
if(f !== null) {
this.fields[t] = f;
fields[t] = f;
}
}
}
// Pull out the tags
if(fields.tags) {
tags = fields.tags;
delete fields.tags;
}
// Expose the fields as read only properties
for(f in fields) {
Object.defineProperty(this,f,{value: fields[f], writeable: false});
}
// Expose the tags as a getter
Object.defineProperty(this,"tags",{get: function() {return tags ? tags.slice(0) : [];}});
// Other methods that need access to the fields
this.getFields = function() {
var r = {};
for(var f in fields) {
var v = fields[f];
if(v instanceof Array) {
r[f] = v.slice(0);
} else {
r[f] = v;
}
}
if(tags) {
r.tags = tags;
}
return r;
};
};

Tiddler.prototype.hasTag = function(tag) {
return this.tags.indexOf(tag) !== -1;
};

Tiddler.standardFields = {
Expand All @@ -57,18 +89,21 @@ Tiddler.isStandardField = function(name) {
return name in Tiddler.standardFields;
};

Tiddler.prototype.hasTag = function(tag) {
if(this.tags) {
for(var t=0; t<this.tags.length; t++) {
if(this.tags[t] === tag) {
return true;
}
Tiddler.compareTiddlerFields = function(a,b,sortField) {
var aa = a[sortField] || 0,
bb = b[sortField] || 0;
if(aa < bb) {
return -1;
} else {
if(aa > bb) {
return 1;
} else {
return 0;
}
}
return false;
};

Tiddler.prototype.parseTiddlerField = function(name,value) {
Tiddler.parseTiddlerField = function(name,value) {
var type = Tiddler.specialTiddlerFields[name];
if(type) {
return Tiddler.specialTiddlerFieldParsers[type](value);
Expand Down
37 changes: 17 additions & 20 deletions js/TiddlerOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,30 @@ var outputTiddler = function(tid) {
var result = [],
outputAttribute = function(name,value) {
result.push(name + ": " + value + "\n");
};
for(var t in tid.fields) {
},
fields = tid.getFields();
for(var t in fields) {
switch(t) {
case "text":
// Ignore the text field
break;
case "tags":
// Output tags as a list
outputAttribute(t,tiddlerOutput.stringifyTags(tid.fields.tags));
outputAttribute(t,tiddlerOutput.stringifyTags(fields.tags));
break;
case "modified":
case "created":
// Output dates in YYYYMMDDHHMM
outputAttribute(t,utils.convertToYYYYMMDDHHMM(tid.fields[t]));
outputAttribute(t,utils.convertToYYYYMMDDHHMM(fields[t]));
break;
default:
// Output other attributes raw
outputAttribute(t,tid.fields[t]);
outputAttribute(t,fields[t]);
break;
}
}
result.push("\n");
result.push(tid.fields.text);
result.push(fields.text);
return result.join("");
};

Expand All @@ -68,23 +69,19 @@ The fields are in the order title, creator, modifier, created, modified, tags, f
*/
var outputTiddlerDiv = function(tid) {
var result = [],
attributes = {},
outputAttribute = function(name,transform,dontDelete) {
if(name in attributes) {
var value = attributes[name];
fields = tid.getFields(),
text = fields.text,
outputAttribute = function(name,transform) {
if(name in fields) {
var value = fields[name];
if(transform)
value = transform(value);
result.push(" " + name + "=\"" + value + "\"");
if(!dontDelete) {
delete attributes[name];
}
delete fields[name];
}
};
for(var t in tid.fields) {
attributes[t] = tid.fields[t];
}
if(attributes.text) {
delete attributes.text;
if(fields.text) {
delete fields.text;
}
result.push("<div");
// Output the standard attributes in the correct order
Expand All @@ -95,11 +92,11 @@ var outputTiddlerDiv = function(tid) {
outputAttribute("modified", function(v) {return utils.convertToYYYYMMDDHHMM(v);});
outputAttribute("tags", function(v) {return stringifyTags(v);});
// Output any other attributes
for(t in attributes) {
for(var t in fields) {
outputAttribute(t,null,true);
}
result.push(">\n<pre>");
result.push(utils.htmlEncode(tid.fields.text));
result.push(utils.htmlEncode(text));
result.push("</pre>\n</div>");
return result.join("");
};
Expand Down
52 changes: 26 additions & 26 deletions js/WikiStore.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
/*\
title: js/WikiStore.js
WikiStore uses the .cache member of tiddlers to store the following information:
parseTree: Caches the parse tree for the tiddler
renderers: Caches rendering functions for this tiddler (indexed by MIME type)
renditions: Caches the renditions produced by those functions (indexed by MIME type)
\*/
(function(){

Expand Down Expand Up @@ -125,7 +131,7 @@ WikiStore.prototype.getTiddler = function(title) {

WikiStore.prototype.getTiddlerText = function(title) {
var t = this.getTiddler(title);
return t instanceof Tiddler ? t.fields.text : null;
return t instanceof Tiddler ? t.text : null;
};

WikiStore.prototype.deleteTiddler = function(title) {
Expand All @@ -143,8 +149,8 @@ WikiStore.prototype.tiddlerExists = function(title) {
};

WikiStore.prototype.addTiddler = function(tiddler) {
this.tiddlers[tiddler.fields.title] = tiddler;
this.touchTiddler("modified",tiddler.fields.title);
this.tiddlers[tiddler.title] = tiddler;
this.touchTiddler("modified",tiddler.title);
};

WikiStore.prototype.forEachTiddler = function(/* [sortField,[excludeTag,]]callback */) {
Expand All @@ -159,22 +165,10 @@ WikiStore.prototype.forEachTiddler = function(/* [sortField,[excludeTag,]]callba
for(t in this.tiddlers) {
tiddlers.push(this.tiddlers[t]);
}
tiddlers.sort(function (a,b) {
var aa = a.fields[sortField] || 0,
bb = b.fields[sortField] || 0;
if(aa < bb) {
return -1;
} else {
if(aa > bb) {
return 1;
} else {
return 0;
}
}
});
tiddlers.sort(function (a,b) {return Tiddler.compareTiddlerFields(a,b,sortField);});
for(t=0; t<tiddlers.length; t++) {
if(!tiddlers[t].hasTag(excludeTag)) {
callback.call(this,tiddlers[t].fields.title,tiddlers[t]);
callback.call(this,tiddlers[t].title,tiddlers[t]);
}
}
} else {
Expand Down Expand Up @@ -286,10 +280,10 @@ WikiStore.prototype.parseTiddler = function(title) {
var tiddler = this.getTiddler(title);
if(tiddler) {
// Check the cache
if(!tiddler.parseTree) {
tiddler.parseTree = this.parseText(tiddler.fields.type,tiddler.fields.text);
if(!tiddler.cache.parseTree) {
tiddler.cache.parseTree = this.parseText(tiddler.type,tiddler.text);
}
return tiddler.parseTree;
return tiddler.cache.parseTree;
} else {
return null;
}
Expand All @@ -311,11 +305,14 @@ WikiStore.prototype.compileTiddler = function(title,type) {
/*jslint evil: true */
var tiddler = this.getTiddler(title);
if(tiddler) {
if(!tiddler.renderers[type]) {
if(!tiddler.cache.renderers) {
tiddler.cache.renderers = {};
}
if(!tiddler.cache.renderers[type]) {
var tree = this.parseTiddler(title);
tiddler.renderers[type] = eval(tree.compile(type));
tiddler.cache.renderers[type] = eval(tree.compile(type));
}
return tiddler.renderers[type];
return tiddler.cache.renderers[type];
} else {
return null;
}
Expand Down Expand Up @@ -343,10 +340,13 @@ WikiStore.prototype.renderTiddler = function(targetType,title,asTitle) {
var asTiddler = this.getTiddler(asTitle);
return fn(asTiddler,this,utils);
} else {
if(!tiddler.renditions[targetType]) {
tiddler.renditions[targetType] = fn(tiddler,this,utils);
if(!tiddler.cache.renditions) {
tiddler.cache.renditions = {};
}
if(!tiddler.cache.renditions[targetType]) {
tiddler.cache.renditions[targetType] = fn(tiddler,this,utils);
}
return tiddler.renditions[targetType];
return tiddler.cache.renditions[targetType];
}
}
return null;
Expand Down
2 changes: 1 addition & 1 deletion js/macros/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ exports.macro = {
},
handler: function(type,tiddler,store,params) {
var encoder = type === "text/html" ? utils.htmlEncode : function(x) {return x;},
parseTree = store.parseTiddler(tiddler.fields.title);
parseTree = store.parseTiddler(tiddler.title);
if(parseTree) {
var r = [];
var d = parseTree.dependencies;
Expand Down
4 changes: 2 additions & 2 deletions js/macros/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ exports.macro = {
handler,
t;
if(template) {
templateType = template.fields.type;
templateText = template.fields.text;
templateType = template.type;
templateText = template.text;
}
handler = handlers[params.type];
handler = handler || handlers.all;
Expand Down
Loading

0 comments on commit b898afe

Please sign in to comment.