Adding rendering machinery
This commit is contained in:
parent
bf2ac07bde
commit
750d6b1f12
94
bin/messc
Executable file
94
bin/messc
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
fs = require('fs'),
|
||||||
|
sys = require('sys');
|
||||||
|
|
||||||
|
require.paths.unshift(path.join(__dirname, '..', 'lib'));
|
||||||
|
|
||||||
|
var less = require('less');
|
||||||
|
var args = process.argv.slice(1);
|
||||||
|
var options = {
|
||||||
|
compress: false,
|
||||||
|
optimization: 1,
|
||||||
|
silent: false
|
||||||
|
};
|
||||||
|
|
||||||
|
args = args.filter(function (arg) {
|
||||||
|
var match;
|
||||||
|
|
||||||
|
if (match = arg.match(/^--?([a-z][0-9a-z-]*)$/i)) { arg = match[1] }
|
||||||
|
else { return arg }
|
||||||
|
|
||||||
|
switch (arg) {
|
||||||
|
case 'v':
|
||||||
|
case 'version':
|
||||||
|
sys.puts("lessc " + less.version.join('.') + " (LESS Compiler) [JavaScript]");
|
||||||
|
process.exit(0);
|
||||||
|
case 'verbose':
|
||||||
|
options.verbose = true;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
case 'silent':
|
||||||
|
options.silent = true;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
case 'help':
|
||||||
|
sys.puts("usage: lessc source [destination]");
|
||||||
|
process.exit(0);
|
||||||
|
case 'x':
|
||||||
|
case 'compress':
|
||||||
|
options.compress = true;
|
||||||
|
break;
|
||||||
|
case 'O0': options.optimization = 0; break;
|
||||||
|
case 'O1': options.optimization = 1; break;
|
||||||
|
case 'O2': options.optimization = 2; break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var input = args[1];
|
||||||
|
if (input && input[0] != '/') {
|
||||||
|
input = path.join(process.cwd(), input);
|
||||||
|
}
|
||||||
|
var output = args[2];
|
||||||
|
if (output && output[0] != '/') {
|
||||||
|
output = path.join(process.cwd(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
var css, fd, tree;
|
||||||
|
|
||||||
|
if (! input) {
|
||||||
|
sys.puts("lessc: no input files");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(input, 'utf-8', function (e, data) {
|
||||||
|
if (e) {
|
||||||
|
sys.puts("lessc: " + e.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
new(less.Renderer)({
|
||||||
|
paths: [path.dirname(input)],
|
||||||
|
optimization: options.optimization,
|
||||||
|
filename: input
|
||||||
|
}).render(data, function (err, tree) {
|
||||||
|
if (err) {
|
||||||
|
less.writeError(err, options);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
css = tree.toCSS({ compress: options.compress });
|
||||||
|
if (output) {
|
||||||
|
fd = fs.openSync(output, "w");
|
||||||
|
fs.writeSync(fd, css, 0, "utf8");
|
||||||
|
} else {
|
||||||
|
sys.print(css);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
less.writeError(e, options);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
160
lib/less/external.js
Normal file
160
lib/less/external.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
var fs = require('fs'),
|
||||||
|
netlib = require('./netlib'),
|
||||||
|
url = require('url'),
|
||||||
|
path = require('path'),
|
||||||
|
_ = require('underscore')._,
|
||||||
|
spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: use node-minizip instead of shelling
|
||||||
|
* TODO: include or don't use underscore
|
||||||
|
*/
|
||||||
|
|
||||||
|
var External = function External(env) {
|
||||||
|
this.env = env;
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get a processor, given a file's extension
|
||||||
|
* @param {String} extension the file's extension.
|
||||||
|
* @return {Function} processor function.
|
||||||
|
*/
|
||||||
|
processors: function(extension) {
|
||||||
|
return {
|
||||||
|
'.zip': this.unzip,
|
||||||
|
'.mss': this.plainfile,
|
||||||
|
'.geojson': this.plainfile,
|
||||||
|
'.kml': this.plainfile
|
||||||
|
}[extension];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the final resting position of an external's directory
|
||||||
|
* @param {String} ext name of the external.
|
||||||
|
* @return {String} file path.
|
||||||
|
*/
|
||||||
|
pos: function(ext) {
|
||||||
|
return path.join(env.data_dir, netlib.safe64(ext));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the temporary path of an external before processing
|
||||||
|
* @param {String} ext filename of the external.
|
||||||
|
* @return {String} file path.
|
||||||
|
*/
|
||||||
|
tmppos: function(ext) {
|
||||||
|
return path.join(env.data_dir, require('crypto')
|
||||||
|
.createHash('md5').update(ext).digest('hex'));
|
||||||
|
},
|
||||||
|
|
||||||
|
plainname: function(resource_url) {
|
||||||
|
return require('crypto')
|
||||||
|
.createHash('md5').update(resource_url).digest('hex') +
|
||||||
|
path.extname(resource_url);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download an external, process it, and return the usable filepath for
|
||||||
|
* Mapnik
|
||||||
|
* @param {String} resource_url the URI of the datasource from a mapfile.
|
||||||
|
* @param {Function} callback passed into processor function after
|
||||||
|
* localizing.
|
||||||
|
*/
|
||||||
|
process: function(resource_url, callback) {
|
||||||
|
var file_format = path.extname(resource_url),
|
||||||
|
that = this;
|
||||||
|
netlib.download(resource_url, this.tmppos(resource_url),
|
||||||
|
function(err, url, filename) {
|
||||||
|
if (that.processors(file_format)) {
|
||||||
|
that.processors(file_format)(
|
||||||
|
filename,
|
||||||
|
resource_url,
|
||||||
|
callback,
|
||||||
|
that);
|
||||||
|
} else {
|
||||||
|
console.log('no processor found for %s', file_format);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deal with a plain file, which is likely to be
|
||||||
|
* GeoJSON, KML, or one of the other OGR-supported formats,
|
||||||
|
* returning a Mapnik-usable filename
|
||||||
|
*
|
||||||
|
* @param {String} filename the place of the file on your system.
|
||||||
|
* @param {String} resource_url
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
plainfile: function(filename, resource_url, callback, that) {
|
||||||
|
// TODO: possibly decide upon default extension
|
||||||
|
var extension = path.extname(resource_url);
|
||||||
|
if (extension !== '') {
|
||||||
|
// TODO: make sure dir doesn't exist
|
||||||
|
var destination = path.join(that.pos(resource_url),
|
||||||
|
that.plainname(resource_url));
|
||||||
|
fs.mkdirSync(that.pos(resource_url), 0777);
|
||||||
|
fs.renameSync(
|
||||||
|
filename,
|
||||||
|
destination);
|
||||||
|
return destination;
|
||||||
|
} else {
|
||||||
|
throw Exception('Non-extended files cannot be processed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unzip a file and return a shapefile contained within it
|
||||||
|
*
|
||||||
|
* TODO: handle other files than shapefiles
|
||||||
|
* @param {String} filename the place of the shapefile on your system.
|
||||||
|
* @param {String} resource_url
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
unzip: function(filename, resource_url, callback, that) {
|
||||||
|
// regrettably complex because zip library isn't written for
|
||||||
|
// node yet.
|
||||||
|
var locateShp = function(dir) {
|
||||||
|
var unzipped = fs.readdirSync(dir);
|
||||||
|
var shp = _.detect(unzipped,
|
||||||
|
function(f) {
|
||||||
|
return path.extname(f) == '.shp';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!shp) {
|
||||||
|
var dirs = _.select(unzipped,
|
||||||
|
function(f) {
|
||||||
|
return fs.statSync(path.join(dir, f)).isDirectory();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (dirs) {
|
||||||
|
for (var i = 0, l = dirs.length; i < l; i++) {
|
||||||
|
var located = locateShp(path.join(dir, dirs[i]));
|
||||||
|
if (located) {
|
||||||
|
return located;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return dir + '/' + shp;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
spawn('unzip', [filename, '-d', that.pos(resource_url)])
|
||||||
|
.on('exit', function(code) {
|
||||||
|
if (code > 0) {
|
||||||
|
console.log('Unzip returned a code of %d', code);
|
||||||
|
} else {
|
||||||
|
// TODO; eliminate locality of reference
|
||||||
|
var shpfile = locateShp(that.pos(resource_url));
|
||||||
|
callback(null, [resource_url, shpfile]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = External;
|
@ -7,6 +7,7 @@ require.paths.unshift(path.join(__dirname, '..'));
|
|||||||
var less = {
|
var less = {
|
||||||
version: [1, 0, 40],
|
version: [1, 0, 40],
|
||||||
Parser: require('less/parser').Parser,
|
Parser: require('less/parser').Parser,
|
||||||
|
Renderer: require('less/renderer').Renderer,
|
||||||
importer: require('less/parser').importer,
|
importer: require('less/parser').importer,
|
||||||
tree: require('less/tree'),
|
tree: require('less/tree'),
|
||||||
reference: JSON.parse(fs.readFileSync(
|
reference: JSON.parse(fs.readFileSync(
|
||||||
|
126
lib/less/netlib.js
Normal file
126
lib/less/netlib.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
var fs = require('fs'),
|
||||||
|
http = require('http'),
|
||||||
|
url = require('url');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A library of net-interaction functions - this could be simplified
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Download a file to the disk and return the downloaded
|
||||||
|
* filename and its data
|
||||||
|
*
|
||||||
|
* @param {String} file_url the URI of the file.
|
||||||
|
* @param {String} filename the filename on the system.
|
||||||
|
* @param {Function} callback to call after finishing the download and
|
||||||
|
* run with arguments [err, filename, data].
|
||||||
|
*/
|
||||||
|
downloadAndGet: function(file_url, filename, callback) {
|
||||||
|
var file_url = url.parse(file_url);
|
||||||
|
var c = http.createClient(file_url.port || 80, file_url.hostname);
|
||||||
|
var request = c.request('GET', file_url.pathname + '?' + (file_url.query || ''), {
|
||||||
|
host: file_url.hostname
|
||||||
|
});
|
||||||
|
request.end();
|
||||||
|
|
||||||
|
var data = '';
|
||||||
|
var f = fs.createWriteStream(filename);
|
||||||
|
request.on('response', function(response) {
|
||||||
|
response.on('data', function(chunk) {
|
||||||
|
data += chunk;
|
||||||
|
f.write(chunk);
|
||||||
|
});
|
||||||
|
response.on('end', function() {
|
||||||
|
f.destroy();
|
||||||
|
callback(null, filename, data);
|
||||||
|
});
|
||||||
|
response.on('error', function(err) {
|
||||||
|
console.log('error downloading file');
|
||||||
|
callback(err, null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file and return data
|
||||||
|
*
|
||||||
|
* @param {String} file_url the URI of the file.
|
||||||
|
* @param {String} filename the filename on the system.
|
||||||
|
* @param {Function} callback to call after finishing the download and
|
||||||
|
* run with arguments [err, filename, data].
|
||||||
|
*/
|
||||||
|
get: function(file_url, filename, callback) {
|
||||||
|
var file_url = url.parse(file_url);
|
||||||
|
var c = http.createClient(file_url.port || 80, file_url.hostname);
|
||||||
|
// TODO: more robust method for getting things with params?
|
||||||
|
console.log(file_url.pathname + '?' + file_url.query);
|
||||||
|
var request = c.request('GET', file_url.pathname + '?' + file_url.query, {
|
||||||
|
host: file_url.host,
|
||||||
|
query: file_url.query
|
||||||
|
});
|
||||||
|
request.end();
|
||||||
|
|
||||||
|
var data = '';
|
||||||
|
request.on('response', function(response) {
|
||||||
|
response.on('data', function(chunk) {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
response.on('end', function() {
|
||||||
|
callback(null, filename, data);
|
||||||
|
});
|
||||||
|
response.on('error', function(err) {
|
||||||
|
console.log('error downloading file');
|
||||||
|
callback(err, null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file
|
||||||
|
*
|
||||||
|
* @param {String} file_url the URI of the file.
|
||||||
|
* @param {String} filename the filename on the system.
|
||||||
|
* @param {Function} callback to call after finishing the download and
|
||||||
|
* run with arguments [err, filename, data].
|
||||||
|
*/
|
||||||
|
download: function(file_url_raw, filename, callback) {
|
||||||
|
var file_url = url.parse(file_url_raw);
|
||||||
|
var c = http.createClient(file_url.port || 80, file_url.hostname);
|
||||||
|
var request = c.request('GET', file_url.pathname + '?' + file_url.query, {
|
||||||
|
host: file_url.hostname
|
||||||
|
});
|
||||||
|
request.end();
|
||||||
|
|
||||||
|
console.log('Downloading\n\t%s', file_url.hostname);
|
||||||
|
var f = fs.createWriteStream(filename);
|
||||||
|
request.on('response', function(response) {
|
||||||
|
response.on('data', function(chunk) {
|
||||||
|
f.write(chunk);
|
||||||
|
});
|
||||||
|
response.on('end', function() {
|
||||||
|
f.destroy();
|
||||||
|
console.log('Download finished');
|
||||||
|
callback(null, file_url_raw, filename);
|
||||||
|
});
|
||||||
|
response.on('error', function(err) {
|
||||||
|
console.log('Error downloading file');
|
||||||
|
callback(err, null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a string as base64
|
||||||
|
*
|
||||||
|
* TODO: actually make safe64 by chunking
|
||||||
|
*
|
||||||
|
* @param {String} s the string to be encoded.
|
||||||
|
* @return {String} base64 encoded string.
|
||||||
|
*/
|
||||||
|
safe64: function(s) {
|
||||||
|
var b = new Buffer(s, 'utf-8');
|
||||||
|
return b.toString('base64');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
154
lib/less/step.js
Executable file
154
lib/less/step.js
Executable file
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2010 Tim Caswell <tim@creationix.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Inspired by http://github.com/willconant/flow-js, but reimplemented and
|
||||||
|
// modified to fit my taste and the node.JS error handling system.
|
||||||
|
function Step() {
|
||||||
|
var steps = Array.prototype.slice.call(arguments),
|
||||||
|
counter, results, lock;
|
||||||
|
|
||||||
|
// Define the main callback that's given as `this` to the steps.
|
||||||
|
function next() {
|
||||||
|
|
||||||
|
// Check if there are no steps left
|
||||||
|
if (steps.length === 0) {
|
||||||
|
// Throw uncaught errors
|
||||||
|
if (arguments[0]) {
|
||||||
|
throw arguments[0];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next step to execute
|
||||||
|
var fn = steps.shift();
|
||||||
|
counter = 0;
|
||||||
|
results = [];
|
||||||
|
|
||||||
|
// Run the step in a try..catch block so exceptions don't get out of hand.
|
||||||
|
try {
|
||||||
|
lock = true;
|
||||||
|
var result = fn.apply(next, arguments);
|
||||||
|
} catch (e) {
|
||||||
|
// Pass any exceptions on through the next callback
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If a syncronous return is used, pass it to the callback
|
||||||
|
if (result !== undefined) {
|
||||||
|
next(undefined, result);
|
||||||
|
}
|
||||||
|
lock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a special callback generator `this.parallel()` that groups stuff.
|
||||||
|
next.parallel = function () {
|
||||||
|
var i = counter;
|
||||||
|
counter++;
|
||||||
|
function check() {
|
||||||
|
counter--;
|
||||||
|
if (counter === 0) {
|
||||||
|
// When they're all done, call the callback
|
||||||
|
next.apply(null, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return function () {
|
||||||
|
// Compress the error from any result to the first argument
|
||||||
|
if (arguments[0]) {
|
||||||
|
results[0] = arguments[0];
|
||||||
|
}
|
||||||
|
// Send the other results as arguments
|
||||||
|
results[i + 1] = arguments[1];
|
||||||
|
if (lock) {
|
||||||
|
process.nextTick(check);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
check();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generates a callback generator for grouped results
|
||||||
|
next.group = function () {
|
||||||
|
var localCallback = next.parallel();
|
||||||
|
var counter = 0;
|
||||||
|
var result = [];
|
||||||
|
var error = undefined;
|
||||||
|
// Generates a callback for the group
|
||||||
|
return function () {
|
||||||
|
var i = counter;
|
||||||
|
counter++;
|
||||||
|
function check() {
|
||||||
|
counter--;
|
||||||
|
if (counter === 0) {
|
||||||
|
// When they're all done, call the callback
|
||||||
|
localCallback(error, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return function () {
|
||||||
|
// Compress the error from any result to the first argument
|
||||||
|
if (arguments[0]) {
|
||||||
|
error = arguments[0];
|
||||||
|
}
|
||||||
|
// Send the other results as arguments
|
||||||
|
result[i] = arguments[1];
|
||||||
|
if (lock) {
|
||||||
|
process.nextTick(check);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the engine an pass nothing to the first step.
|
||||||
|
next([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tack on leading and tailing steps for input and output and return
|
||||||
|
// the whole thing as a function. Basically turns step calls into function
|
||||||
|
// factories.
|
||||||
|
Step.fn = function StepFn() {
|
||||||
|
var steps = Array.prototype.slice.call(arguments);
|
||||||
|
return function () {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
|
||||||
|
// Insert a first step that primes the data stream
|
||||||
|
var toRun = [function () {
|
||||||
|
this.apply(null, args);
|
||||||
|
}].concat(steps);
|
||||||
|
|
||||||
|
// If the last arg is a function add it as a last step
|
||||||
|
if (typeof args[args.length-1] === 'function') {
|
||||||
|
toRun.push(args.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Step.apply(null, toRun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Hook into commonJS module systems
|
||||||
|
if (typeof module !== 'undefined' && "exports" in module) {
|
||||||
|
module.exports = Step;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user