Commit d5ff7c42 d5ff7c42579934116e7158a6ab7826d762d81935 by Nicolas Perriault

in the process of a commonjs module implementation

1 parent 427c643f
#!/usr/bin/env phantomjs
/*!
* Casper is a navigation utility for PhantomJS.
*
......@@ -25,104 +27,186 @@
* DEALINGS IN THE SOFTWARE.
*
*/
(function(phantom) {
if (true === phantom.casperLoaded) {
return;
}
// see http://semver.org/
phantom.casperVersion = {
major: 0,
minor: 4,
patch: 2,
ident: 'trunk',
toString: function() {
var version = [this.major, this.minor, this.patch].join('.');
if (this.ident) {
version = [version, this.ident].join('-');
}
return version;
if (true === phantom.casperLoaded) {
return;
}
// see http://semver.org/
phantom.casperVersion = {
major: 0,
minor: 4,
patch: 2,
ident: 'experimental-require',
toString: function() {
var version = [this.major, this.minor, this.patch].join('.');
if (this.ident) {
version = [version, this.ident].join('-');
}
};
return version;
}
};
var fs = require('fs');
// patching fs
var fs = (function(fs) {
if (!fs.hasOwnProperty('basename')) {
fs.basename = function(path) {
return path.replace(/.*\//, '');
};
}
if (!fs.hasOwnProperty('dirname')) {
fs.dirname = function(path) {
return path.replace(/\/[^\/]*\/?$/, '');
};
}
if (!fs.hasOwnProperty('pathJoin')) {
fs.pathJoin = function() {
return Array.prototype.join.call(arguments, this.separator);
};
}
return fs;
})(require('fs'));
fs.pathJoin = function() {
return Array.prototype.join.call(arguments, this.separator);
};
// casper root path
phantom.casperPath = fs.absolute(phantom.libraryScript);
phantom.extractCasperArgs = function(cliArgs) {
var extract = { args: [], options: {} };
cliArgs.forEach(function(arg) {
if (arg.indexOf('--') === 0) {
// named option
var optionMatch = arg.match(/^--(.*)=(.*)/i);
if (optionMatch) {
extract.options[optionMatch[1]] = optionMatch[2];
} else {
// flag
var flagMatch = arg.match(/^--(.*)/);
if (flagMatch) {
extract.options[flagMatch[1]] = true;
}
}
// casper cli args
phantom.casperArgs = (function(cliArgs) {
var extract = {
args: [],
options: {},
get: function(what) {
if (typeof what === "number") {
return this.args[what];
} else if (typeof what === "string") {
return this.options[what];
} else {
// positional arg
extract.args.push(arg);
throw new Error("Unsupported cli arg getter " + typeof what);
}
});
return extract;
}
};
phantom.casperArgs = phantom.extractCasperArgs(phantom.args);
if (!phantom.casperPath) {
phantom.casperPath = phantom.casperArgs.options['casper-path'];
}
if (!phantom.casperPath) {
console.log('Cannot find CasperJS home path. Did you set phantom.casperPath or pass the --casper-path option?');
phantom.exit(1);
} else if (!fs.isDirectory(phantom.casperPath)) {
console.log('Invalid CasperJS path: ' + phantom.casperPath);
phantom.exit(1);
}
[
'casper.js',
'clientutils.js',
'colorizer.js',
'injector.js',
'tester.js',
'utils.js',
'xunit.js'
].forEach(function(lib) {
phantom.injectJs(fs.pathJoin(phantom.casperPath, 'lib', lib));
cliArgs.forEach(function(arg) {
if (arg.indexOf('--') === 0) {
// named option
var optionMatch = arg.match(/^--(.*)=(.*)/i);
if (optionMatch) {
extract.options[optionMatch[1]] = optionMatch[2];
} else {
// flag
var flagMatch = arg.match(/^--(.*)/);
if (flagMatch) {
extract.options[flagMatch[1]] = true;
}
}
} else {
// positional arg
extract.args.push(arg);
}
});
return extract;
})(phantom.args);
phantom.casperLoaded = true;
if (true === phantom.casperArgs.options.cli) {
if (!!phantom.casperArgs.options.version) {
console.log(phantom.casperVersion.toString());
phantom.exit(0);
} else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
console.log('CasperJS version ' + phantom.casperVersion.toString());
console.log('Usage: casperjs script.(js|coffee) [options...]');
console.log('Read the docs http://n1k0.github.com/casperjs/');
phantom.exit(0);
// Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
// TODO: remove when PhantomJS has full module support
require = (function(require, requireDir) {
var phantomBuiltins = ['fs', 'webpage', 'webserver'];
var phantomRequire = phantom.__orig__require = require;
var requireCache = {};
return function(path) {
var i, dir, paths = [],
fileGuesses = [],
file, code, fn,
module = {
exports: {}
};
if (phantomBuiltins.indexOf(path) !== -1) {
return phantomRequire(path);
} else {
if (path[0] === '.') {
paths.push(fs.absolute(fs.pathJoin(requireDir, path)));
} else if (path[0] === '/') {
paths.push(path);
} else {
dir = fs.absolute(requireDir);
while (dir !== '') {
// nodejs compatibility
paths.push(fs.pathJoin(dir, 'node_modules', path));
dir = fs.dirname(dir);
}
paths.push(fs.pathJoin(requireDir, 'modules', path));
}
paths.forEach(function(testPath) {
fileGuesses.push.apply(fileGuesses, [
testPath,
testPath + '.js',
testPath + '.coffee',
fs.pathJoin(testPath, 'index.js'),
fs.pathJoin(testPath, 'index.coffee'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
]);
});
file = null;
for (i = 0; i < fileGuesses.length && !file; ++i) {
if (fs.isFile(fileGuesses[i])) {
file = fileGuesses[i];
}
}
if (!file) {
throw new Error("Can't find module " + path);
}
if (file in requireCache) {
return requireCache[file].exports;
}
code = fs.read(file);
if (file.match(/\.coffee$/)) {
try {
code = CoffeeScript.compile(code);
} catch (e) {
e.fileName = file;
throw e;
}
}
// a trick to associate Error's sourceId with file
//code += ";throw new Error('__sourceId__');";
try {
fn = new Function('module', 'exports', code);
fn(module, module.exports);
} catch (e) {
if (typeof sourceIds === "object" && !sourceIds.hasOwnProperty(e.sourceId)) {
sourceIds[e.sourceId] = file;
}
if (e.message !== '__sourceId__') {
throw e;
}
}
requireCache[file] = module;
return module.exports;
}
};
})(require, phantom.casperPath);
phantom.casperScript = phantom.casperArgs.args[0];
phantom.casperLoaded = true;
if (!fs.isFile(phantom.casperScript)) {
console.log('Unable to open file: ' + phantom.casperScript);
phantom.exit(1);
}
if (!!phantom.casperArgs.options.version) {
console.log(phantom.casperVersion.toString());
phantom.exit(0);
} else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
console.log('CasperJS version ' + phantom.casperVersion.toString());
console.log('Usage: casperjs script.(js|coffee) [options...]');
console.log('Read the docs http://n1k0.github.com/casperjs/');
phantom.exit(0);
}
// filter out the called script name from casper args
phantom.casperArgs.args = phantom.casperArgs.args.filter(function(arg) {
return arg !== phantom.casperScript;
});
phantom.injectJs(phantom.casperScript);
}
})(phantom);
phantom.casperScript = phantom.casperArgs.args[0];
if (!fs.isFile(phantom.casperScript)) {
console.log('Unable to open file: ' + phantom.casperScript);
phantom.exit(1);
}
// filter out the called script name from casper args
phantom.casperArgs.args = phantom.casperArgs.args.filter(function(arg) {
return arg !== phantom.casperScript;
});
phantom.injectJs(phantom.casperScript);
......
......@@ -25,67 +25,73 @@
* DEALINGS IN THE SOFTWARE.
*
*/
(function(phantom){
exports.create = create;
exports.Colorizer = Colorizer;
function create() {
return new Colorizer();
}
/**
* This is a port of lime colorizer.
* http://trac.symfony-project.org/browser/tools/lime/trunk/lib/lime.php)
*
* (c) Fabien Potencier, Symfony project, MIT license
*/
var Colorizer = function() {
var options = { bold: 1, underscore: 4, blink: 5, reverse: 7, conceal: 8 };
var foreground = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37 };
var background = { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47 };
var styles = {
'ERROR': { bg: 'red', fg: 'white', bold: true },
'INFO': { fg: 'green', bold: true },
'TRACE': { fg: 'green', bold: true },
'PARAMETER': { fg: 'cyan' },
'COMMENT': { fg: 'yellow' },
'WARNING': { fg: 'red', bold: true },
'GREEN_BAR': { fg: 'white', bg: 'green', bold: true },
'RED_BAR': { fg: 'white', bg: 'red', bold: true },
'INFO_BAR': { bg: 'cyan', fg: 'white', bold: true }
};
/**
* This is a port of lime colorizer.
* http://trac.symfony-project.org/browser/tools/lime/trunk/lib/lime.php)
* Adds a style to provided text.
*
* (c) Fabien Potencier, Symfony project, MIT license
* @params String text
* @params String styleName
* @return String
*/
phantom.Casper.Colorizer = function() {
var options = { bold: 1, underscore: 4, blink: 5, reverse: 7, conceal: 8 };
var foreground = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37 };
var background = { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47 };
var styles = {
'ERROR': { bg: 'red', fg: 'white', bold: true },
'INFO': { fg: 'green', bold: true },
'TRACE': { fg: 'green', bold: true },
'PARAMETER': { fg: 'cyan' },
'COMMENT': { fg: 'yellow' },
'WARNING': { fg: 'red', bold: true },
'GREEN_BAR': { fg: 'white', bg: 'green', bold: true },
'RED_BAR': { fg: 'white', bg: 'red', bold: true },
'INFO_BAR': { bg: 'cyan', fg: 'white', bold: true }
};
this.colorize = function(text, styleName) {
if (styleName in styles) {
return this.format(text, styles[styleName]);
}
return text;
};
/**
* Adds a style to provided text.
*
* @params String text
* @params String styleName
* @return String
*/
this.colorize = function(text, styleName) {
if (styleName in styles) {
return this.format(text, styles[styleName]);
}
/**
* Formats a text using a style declaration object.
*
* @param String text
* @param Object style
* @return String
*/
this.format = function(text, style) {
if (typeof style !== "object") {
return text;
};
/**
* Formats a text using a style declaration object.
*
* @param String text
* @param Object style
* @return String
*/
this.format = function(text, style) {
if (typeof style !== "object") {
return text;
}
var codes = [];
if (style.fg && foreground[style.fg]) {
codes.push(foreground[style.fg]);
}
if (style.bg && background[style.bg]) {
codes.push(background[style.bg]);
}
for (var option in options) {
if (style[option] === true) {
codes.push(options[option]);
}
}
var codes = [];
if (style.fg && foreground[style.fg]) {
codes.push(foreground[style.fg]);
}
if (style.bg && background[style.bg]) {
codes.push(background[style.bg]);
}
for (var option in options) {
if (style[option] === true) {
codes.push(options[option]);
}
return "\033[" + codes.join(';') + 'm' + text + "\033[0m";
};
}
return "\033[" + codes.join(';') + 'm' + text + "\033[0m";
};
})(phantom);
\ No newline at end of file
};
......
......@@ -25,54 +25,60 @@
* DEALINGS IN THE SOFTWARE.
*
*/
(function(phantom) {
/**
* Function argument injector.
*
*/
phantom.Casper.FunctionArgsInjector = function(fn) {
if (!isType(fn, "function")) {
throw new Error("FunctionArgsInjector() can only process functions");
exports.create = create;
exports.FunctionArgsInjector = FunctionArgsInjector;
function create(fn) {
return new FunctionArgsInjector(fn);
}
/**
* Function argument injector.
*
*/
var FunctionArgsInjector = function(fn) {
if (!isType(fn, "function")) {
throw new Error("FunctionArgsInjector() can only process functions");
}
this.fn = fn;
this.extract = function(fn) {
var match = /^function\s?(\w+)?\s?\((.*)\)\s?\{([\s\S]*)\}/i.exec(fn.toString().trim());
if (match && match.length > 1) {
var args = match[2].split(',').map(function(arg) {
return arg.replace(new RegExp(/\/\*+.*\*\//ig), "").trim();
}).filter(function(arg) {
return arg;
}) || [];
return {
name: match[1] ? match[1].trim() : null,
args: args,
body: match[3] ? match[3].trim() : ''
};
}
this.fn = fn;
};
this.extract = function(fn) {
var match = /^function\s?(\w+)?\s?\((.*)\)\s?\{([\s\S]*)\}/i.exec(fn.toString().trim());
if (match && match.length > 1) {
var args = match[2].split(',').map(function(arg) {
return arg.replace(new RegExp(/\/\*+.*\*\//ig), "").trim();
}).filter(function(arg) {
return arg;
}) || [];
return {
name: match[1] ? match[1].trim() : null,
args: args,
body: match[3] ? match[3].trim() : ''
};
}
};
this.process = function(values) {
var fnObj = this.extract(this.fn);
if (!isType(fnObj, "object")) {
throw new Error("Unable to process function " + this.fn.toString());
}
var inject = this.getArgsInjectionString(fnObj.args, values);
return 'function ' + (fnObj.name || '') + '(){' + inject + fnObj.body + '}';
};
this.process = function(values) {
var fnObj = this.extract(this.fn);
if (!isType(fnObj, "object")) {
throw new Error("Unable to process function " + this.fn.toString());
this.getArgsInjectionString = function(args, values) {
values = typeof values === "object" ? values : {};
var jsonValues = escape(encodeURIComponent(JSON.stringify(values)));
var inject = [
'var __casper_params__ = JSON.parse(decodeURIComponent(unescape(\'' + jsonValues + '\')));'
];
args.forEach(function(arg) {
if (arg in values) {
inject.push('var ' + arg + '=__casper_params__["' + arg + '"];');
}
var inject = this.getArgsInjectionString(fnObj.args, values);
return 'function ' + (fnObj.name || '') + '(){' + inject + fnObj.body + '}';
};
this.getArgsInjectionString = function(args, values) {
values = typeof values === "object" ? values : {};
var jsonValues = escape(encodeURIComponent(JSON.stringify(values)));
var inject = [
'var __casper_params__ = JSON.parse(decodeURIComponent(unescape(\'' + jsonValues + '\')));'
];
args.forEach(function(arg) {
if (arg in values) {
inject.push('var ' + arg + '=__casper_params__["' + arg + '"];');
}
});
return inject.join('\n') + '\n';
};
});
return inject.join('\n') + '\n';
};
})(phantom);
\ No newline at end of file
};
......
......@@ -105,7 +105,7 @@ function createPage(casper) {
__utils__ = new ClientUtils();
return __utils__ instanceof ClientUtils;
}, {
utils: encodeURIComponent(phantom.Casper.ClientUtils.toString())
utils: encodeURIComponent(require('./lib/clientutils').ClientUtils.toString())
}));
if (!injected) {
casper.log("Failed to inject Casper client-side utilities!", "warning");
......
......@@ -25,69 +25,75 @@
* DEALINGS IN THE SOFTWARE.
*
*/
(function(phantom) {
/**
* JUnit XML (xUnit) exporter for test results.
*
*/
phantom.Casper.XUnitExporter = function() {
var node = function(name, attributes) {
var node = document.createElement(name);
for (var attrName in attributes) {
var value = attributes[attrName];
if (attributes.hasOwnProperty(attrName) && isType(attrName, "string")) {
node.setAttribute(attrName, value);
}
exports.create = create;
exports.XUnitExporter = XUnitExporter;
function create() {
return new XUnitExporter();
}
/**
* JUnit XML (xUnit) exporter for test results.
*
*/
XUnitExporter = function() {
var node = function(name, attributes) {
var node = document.createElement(name);
for (var attrName in attributes) {
var value = attributes[attrName];
if (attributes.hasOwnProperty(attrName) && isType(attrName, "string")) {
node.setAttribute(attrName, value);
}
return node;
};
}
return node;
};
var xml = node('testsuite');
xml.toString = function() {
return this.outerHTML; // ouch
};
var xml = node('testsuite');
xml.toString = function() {
return this.outerHTML; // ouch
};
/**
* Adds a successful test result
*
* @param String classname
* @param String name
*/
this.addSuccess = function(classname, name) {
xml.appendChild(node('testcase', {
classname: classname,
name: name
}));
};
/**
* Adds a successful test result
*
* @param String classname
* @param String name
*/
this.addSuccess = function(classname, name) {
xml.appendChild(node('testcase', {
classname: classname,
name: name
}));
};
/**
* Adds a failed test result
*
* @param String classname
* @param String name
* @param String message
* @param String type
*/
this.addFailure = function(classname, name, message, type) {
var fnode = node('testcase', {
classname: classname,
name: name
});
var failure = node('failure', {
type: type || "unknown"
});
failure.appendChild(document.createTextNode(message || "no message left"));
fnode.appendChild(failure);
xml.appendChild(fnode);
};
/**
* Adds a failed test result
*
* @param String classname
* @param String name
* @param String message
* @param String type
*/
this.addFailure = function(classname, name, message, type) {
var fnode = node('testcase', {
classname: classname,
name: name
});
var failure = node('failure', {
type: type || "unknown"
});
failure.appendChild(document.createTextNode(message || "no message left"));
fnode.appendChild(failure);
xml.appendChild(fnode);
};
/**
* Retrieves generated XML object - actually an HTMLElement.
*
* @return HTMLElement
*/
this.getXML = function() {
return xml;
};
/**
* Retrieves generated XML object - actually an HTMLElement.
*
* @return HTMLElement
*/
this.getXML = function() {
return xml;
};
})(phantom);
\ No newline at end of file
};
......