Add `--build` command

First pass at the build system described in #356.

To test it, move to a new, empty directory and try `tiddlywiki
editions/tw5.com --verbose --build`
This commit is contained in:
Jermolene 2014-04-25 22:41:59 +01:00
parent 552657fc58
commit f7e50e0950
22 changed files with 292 additions and 40 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.DS_Store
tmp/
output/

View File

@ -1612,6 +1612,7 @@ $tw.boot.startup = function(options) {
wikiThemesSubDir: "./themes",
wikiLanguagesSubDir: "./languages",
wikiTiddlersSubDir: "./tiddlers",
wikiOutputSubDir: "./output",
jsModuleHeaderRegExpString: "^\\/\\*\\\\(?:\\r?\\n)((?:^[^\\r\\n]*(?:\\r?\\n))+?)(^\\\\\\*\\/$(?:\\r?\\n)?)",
fileExtensionInfo: Object.create(null), // Map file extension to {type:}
contentTypeInfo: Object.create(null) // Map type to {encoding:,extension:}

View File

@ -0,0 +1,11 @@
title: $:/language/Help/build
description: Automatically run configured commands
Build the specified build targets for the current wiki. If no build targets are specified then all available targets will be built.
```
--build <target> [<target> ...]
```
Build targets are defined in the `tiddlywiki.info` file of a wiki folder.

View File

@ -0,0 +1,8 @@
title: $:/language/Help/clearpassword
description: Set a password for subsequent crypto operations
Clear the password for subsequent crypto operations
```
--clearpassword
```

View File

@ -0,0 +1,10 @@
title: $:/language/Help/output
description: Set the base output directory for subsequent commands
Sets the base output directory for subsequent commands. The default output directory is the current working directory.
```
--output <pathname>
```
If the specified pathname is relative then it is resolved relative to the current output directory.

View File

@ -6,3 +6,5 @@ Render an individual tiddler as a specified ContentType, defaults to `text/html`
```
--rendertiddler <title> <filename> [<type>]
```
Any missing directories in the path to the filename are automatically created.

View File

@ -12,3 +12,5 @@ For example:
```
--rendertiddlers [!is[system]] $:/core/templates/static.tiddler.html ./static text/plain
```
Any files in the target directory are deleted. The target directory is recursively created if it is missing.

View File

@ -6,3 +6,5 @@ Saves an individual tiddler in its raw text or binary format to the specified fi
```
--savetiddler <title> <filename>
```
Any missing directories in the path to the filename are automatically created.

View File

@ -20,11 +20,20 @@ Parse a sequence of commands
callback: a callback invoked as callback(err) where err is null if there was no error
*/
var Commander = function(commandTokens,callback,wiki,streams) {
var path = require("path");
this.commandTokens = commandTokens;
this.nextToken = 0;
this.callback = callback;
this.wiki = wiki;
this.streams = streams;
this.outputPath = process.cwd();
};
/*
Add a string of tokens to the command queue
*/
Commander.prototype.addCommandTokens = function(commandTokens) {
Array.prototype.push.apply(this.commandTokens,commandTokens);
};
/*

View File

@ -0,0 +1,52 @@
/*\
title: $:/core/modules/commands/build.js
type: application/javascript
module-type: command
Command to build a build target
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "build",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
// Get the build targets defined in the wiki
var buildTargets = $tw.boot.wikiInfo.build;
if(!buildTargets) {
return "No build targets defined"
}
// Loop through each of the specified targets
var targets;
if(this.params.length > 0) {
targets = this.params;
} else {
targets = Object.keys(buildTargets);
}
for(var targetIndex=0; targetIndex<targets.length; targetIndex++) {
var target = targets[targetIndex],
commands = buildTargets[target];
if(!commands) {
return "Build target '" + target + "' not found";
}
// Add the commands to the queue
this.commander.addCommandTokens(commands);
}
return null;
};
exports.Command = Command;
})();

View File

@ -0,0 +1,33 @@
/*\
title: $:/core/modules/commands/clearpassword.js
type: application/javascript
module-type: command
Clear password for crypto operations
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "clearpassword",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
$tw.crypto.setPassword(null);
return null;
};
exports.Command = Command;
})();

View File

@ -0,0 +1,38 @@
/*\
title: $:/core/modules/commands/output.js
type: application/javascript
module-type: command
Command to set the default output location (defaults to current working directory)
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "output",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var fs = require("fs"),
path = require("path");
if(this.params.length < 1) {
return "Missing output path";
}
this.commander.outputPath = path.resolve(this.commander.outputPath,this.params[0]);
return null;
};
exports.Command = Command;
})();

View File

@ -31,8 +31,9 @@ Command.prototype.execute = function() {
fs = require("fs"),
path = require("path"),
title = this.params[0],
filename = this.params[1],
filename = path.resolve(this.commander.outputPath,this.params[1]),
type = this.params[2] || "text/html";
$tw.utils.createFileDirectories(filename);
fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) {
self.callback(err);
});

View File

@ -35,10 +35,12 @@ Command.prototype.execute = function() {
wiki = this.commander.wiki,
filter = this.params[0],
template = this.params[1],
pathname = this.params[2],
pathname = path.resolve(this.commander.outputPath,this.params[2]),
type = this.params[3] || "text/html",
extension = this.params[4] || ".html",
tiddlers = wiki.filterTiddlers(filter);
$tw.utils.deleteDirectory(pathname);
$tw.utils.createDirectory(pathname);
$tw.utils.each(tiddlers,function(title) {
var parser = wiki.parseTiddler(template),
widgetNode = wiki.makeWidget(parser,{variables: {currentTiddler: title}});

View File

@ -31,10 +31,11 @@ Command.prototype.execute = function() {
fs = require("fs"),
path = require("path"),
title = this.params[0],
filename = this.params[1],
filename = path.resolve(this.commander.outputPath,this.params[1]),
tiddler = this.commander.wiki.getTiddler(title),
type = tiddler.fields.type || "text/vnd.tiddlywiki",
contentTypeInfo = $tw.config.contentTypeInfo[type] || {encoding: "utf8"};
$tw.utils.createFileDirectories(filename);
fs.writeFile(filename,tiddler.fields.text,contentTypeInfo.encoding,function(err) {
self.callback(err);
});

View File

@ -87,9 +87,13 @@ exports.removeTrailingSeparator = function(dirPath) {
Recursively create a directory
*/
exports.createDirectory = function(dirPath) {
var parts = dirPath.split(path.sep);
for(var component=0; component<parts.length; component++) {
var subDirPath = parts.slice(0,component+1).join(path.sep);
if(dirPath.substr(dirPath.length-1,1) !== path.sep) {
dirPath = dirPath + path.sep;
}
var pos = 1;
pos = dirPath.indexOf(path.sep,pos);
while(pos !== -1) {
var subDirPath = dirPath.substr(0,pos);
if(!$tw.utils.isDirectory(subDirPath)) {
try {
fs.mkdirSync(subDirPath);
@ -97,6 +101,33 @@ exports.createDirectory = function(dirPath) {
return "Error creating directory '" + subDirPath + "'";
}
}
pos = dirPath.indexOf(path.sep,pos + 1);
}
return null;
};
/*
Recursively create directories needed to contain a specified file
*/
exports.createFileDirectories = function(filePath) {
return $tw.utils.createDirectory(path.dirname(filePath));
};
/*
Recursively delete a directory
*/
exports.deleteDirectory = function(dirPath) {
if(fs.existsSync(dirPath)) {
var entries = fs.readdirSync(dirPath);
for(var entryIndex=0; entryIndex<entries.length; entryIndex++) {
var currPath = dirPath + path.sep + entries[entryIndex];
if(fs.lstatSync(currPath).isDirectory()) {
$tw.utils.deleteDirectory(currPath);
} else {
fs.unlinkSync(currPath);
}
}
fs.rmdirSync(dirPath);
}
return null;
};

View File

@ -0,0 +1,7 @@
created: 20140425085548209
modified: 20140425085738745
tags: command
title: BuildCommand
type: text/vnd.tiddlywiki
{{$:/language/Help/build}}

View File

@ -0,0 +1,4 @@
title: ClearPasswordCommand
tags: command
{{$:/language/Help/clearpassword}}

View File

@ -0,0 +1,7 @@
created: 20140425085548209
modified: 20140425085738745
tags: command
title: OutputCommand
type: text/vnd.tiddlywiki
{{$:/language/Help/output}}

View File

@ -24,6 +24,7 @@ The `tiddlywiki.info` file in a wiki folder contains a JSON object comprising th
* ''themes'' - an array of theme names to be included in the wiki
* ''languages'' - an array of language names to be included in the wiki
* ''includeWikis'' - an array of relative paths to external wiki folders to be included in the wiki
* ''build'' - a hashmap of named build targets, each defined by an array of command tokens (see BuildCommand)
* ''config'' - an optional hashmap of configuration options (see below)
Configuration options include:
@ -34,16 +35,23 @@ For example:
```
{
"plugins": [
"tiddlywiki/tiddlyweb",
"tiddlywiki/filesystem"
],
"includeWikis": [
"../tw5.com"
],
"config": {
"retain-original-tiddler-path": true
}
"plugins": [
"tiddlywiki/tiddlyweb",
"tiddlywiki/filesystem"
],
"includeWikis": [
"../tw5.com"
],
"build": {
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"]
},
"config": {
"retain-original-tiddler-path": true
}
}
```
@ -55,26 +63,26 @@ Sub-folders within the `tiddlers` folder can also be given a `tiddlywiki.files`
```
{
"tiddlers": [
{
"file": "d3.min.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/d3/d3.js",
"module-type": "library"
},
"prefix": "var d3;if($tw.browser){\n",
"suffix": "}\nexports.d3 = d3;\n"
},
{
"file": "cloud/d3.layout.cloud.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/d3/d3.layout.cloud.js",
"module-type": "library"
}
}
]
"tiddlers": [
{
"file": "d3.min.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/d3/d3.js",
"module-type": "library"
},
"prefix": "var d3;if($tw.browser){\n",
"suffix": "}\nexports.d3 = d3;\n"
},
{
"file": "cloud/d3.layout.cloud.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/d3/d3.layout.cloud.js",
"module-type": "library"
}
}
]
}
```

View File

@ -26,8 +26,30 @@
"zh-Hant",
"it-IT",
"ja-JP"
]
,
],
"build": {
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"encrypted": [
"--password", "password",
"--rendertiddler", "$:/core/save/all", "encrypted.html", "text/plain",
"--clearpassword"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],
"readmes": [
"--rendertiddler","ReadMe","readme.md","text/html",
"--rendertiddler","ContributingTemplate","contributing.md","text/html",
"--rendertiddler","$:/core/copyright.txt","licenses/copyright.md","text/html"],
"empty": [
"--rendertiddler","$:/editions/tw5.com/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/tw5.com/download-empty","empty.hta","text/plain"],
"static": [
"--rendertiddler","$:/core/templates/static.template.html","static.html","text/plain",
"--rendertiddler","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
"--rendertiddler","$:/core/templates/static.template.css","static/static.css","text/plain",
"--rendertiddlers","[!is[system]]","$:/core/templates/static.tiddler.html","static","text/plain"]
},
"config": {
"retain-original-tiddler-path": true
}

File diff suppressed because one or more lines are too long