2013-12-21 09:58:40 +01:00
|
|
|
/*************************************************************************
|
2013-12-28 09:44:40 +01:00
|
|
|
## require - Node.js-style module loading in ScriptCraft
|
2013-12-21 09:58:40 +01:00
|
|
|
|
|
|
|
Node.js is a server-side javascript environment with an excellent
|
|
|
|
module loading system based on CommonJS. Modules in Node.js are really
|
|
|
|
simple. Each module is in its own javascript file and all variables
|
2013-12-21 10:09:11 +01:00
|
|
|
and functions within the file are private to that file/module only.
|
|
|
|
There is a very concise explanation of CommonJS modules at...
|
|
|
|
|
|
|
|
[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules]
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2013-12-24 23:47:57 +01:00
|
|
|
Node.js also has good documentation on [Modules][njsmod].
|
|
|
|
|
2013-12-21 09:58:40 +01:00
|
|
|
If you want to export a variable or function you use the module.export
|
|
|
|
property.
|
|
|
|
|
|
|
|
For example imagine you have 3 files program.js, inc.js and math.js ...
|
|
|
|
|
2013-12-21 10:09:11 +01:00
|
|
|
### math.js
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2014-02-04 22:36:00 +01:00
|
|
|
```javascript
|
|
|
|
exports.add = function(a,b){
|
|
|
|
return a + b;
|
|
|
|
}
|
|
|
|
```
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2013-12-21 10:09:11 +01:00
|
|
|
### inc.js
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2014-02-04 22:36:00 +01:00
|
|
|
```javascript
|
|
|
|
var math = require('./math');
|
|
|
|
exports.increment = function(n){
|
|
|
|
return math.add(n, 1);
|
|
|
|
}
|
|
|
|
```
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2013-12-21 10:09:11 +01:00
|
|
|
### program.js
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2014-02-04 22:36:00 +01:00
|
|
|
```javascript
|
|
|
|
var inc = require('./inc').increment;
|
|
|
|
var a = 7;
|
|
|
|
a = inc(a);
|
|
|
|
print(a);
|
|
|
|
```
|
2013-12-21 09:58:40 +01:00
|
|
|
|
|
|
|
You can see from the above sample code that programs can use modules
|
|
|
|
and modules themeselves can use other modules. Modules have full
|
|
|
|
control over what functions and properties they want to provide to
|
|
|
|
others.
|
|
|
|
|
2013-12-28 09:44:40 +01:00
|
|
|
### Important
|
2013-12-21 09:58:40 +01:00
|
|
|
|
|
|
|
Although ScriptCraft now supports Node.js style modules, it does not
|
|
|
|
support node modules. Node.js and Rhino are two very different
|
|
|
|
Javascript environments. ScriptCraft uses Rhino Javascript, not
|
2013-12-28 09:44:40 +01:00
|
|
|
Node.js. Standard Node.js modules such as `'fs'` are not available in ScriptCraft.
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2013-12-21 10:09:11 +01:00
|
|
|
Modules can be loaded using relative or absolute paths. Per the CommonJS
|
|
|
|
module specification, the '.js' suffix is optional.
|
|
|
|
|
|
|
|
[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1.
|
|
|
|
|
2013-12-21 09:58:40 +01:00
|
|
|
***/
|
2014-03-13 23:49:03 +01:00
|
|
|
(function ( rootDir, modulePaths, hooks, evaluate ) {
|
2013-12-24 01:16:07 +01:00
|
|
|
|
2014-01-29 21:11:47 +01:00
|
|
|
var File = java.io.File,
|
|
|
|
FileReader = java.io.FileReader,
|
|
|
|
BufferedReader = java.io.BufferedReader;
|
2013-12-24 01:16:07 +01:00
|
|
|
|
2014-01-29 20:49:15 +01:00
|
|
|
var readModuleFromDirectory = function( dir ) {
|
|
|
|
|
|
|
|
// look for a package.json file
|
|
|
|
var pkgJsonFile = new File( dir, './package.json' );
|
|
|
|
if ( pkgJsonFile.exists() ) {
|
|
|
|
var pkg = scload( pkgJsonFile );
|
|
|
|
var mainFile = new File( dir, pkg.main );
|
|
|
|
if ( mainFile.exists() ) {
|
|
|
|
return mainFile;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// look for an index.js file
|
2014-03-13 17:10:51 +01:00
|
|
|
var indexJsFile = new File( dir, './index.js' );
|
2014-01-29 20:49:15 +01:00
|
|
|
if ( indexJsFile.exists() ) {
|
|
|
|
return indexJsFile;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2013-12-24 01:16:07 +01:00
|
|
|
|
2014-01-29 20:49:15 +01:00
|
|
|
var fileExists = function( file ) {
|
|
|
|
if ( file.isDirectory() ) {
|
|
|
|
return readModuleFromDirectory( file );
|
|
|
|
} else {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
};
|
2013-12-26 16:38:24 +01:00
|
|
|
|
2014-01-29 20:49:15 +01:00
|
|
|
var _canonize = function(file){
|
|
|
|
return "" + file.canonicalPath.replaceAll("\\\\","/");
|
|
|
|
};
|
2013-12-26 16:38:24 +01:00
|
|
|
|
2013-12-24 01:16:07 +01:00
|
|
|
/**********************************************************************
|
2013-12-28 09:44:40 +01:00
|
|
|
### module name resolution
|
|
|
|
|
|
|
|
When resolving module names to file paths, ScriptCraft uses the following rules...
|
2013-12-24 01:16:07 +01:00
|
|
|
|
|
|
|
1. if the module does not begin with './' or '/' then ...
|
|
|
|
|
|
|
|
1.1 Look in the 'scriptcraft/lib' directory. If it's not there then...
|
|
|
|
1.2 Look in the 'scriptcraft/modules' directory. If it's not there then
|
|
|
|
Throw an Error.
|
|
|
|
|
|
|
|
2. If the module begins with './' or '/' then ...
|
|
|
|
|
|
|
|
2.1 if the module begins with './' then it's treated as a file path. File paths are
|
|
|
|
always relative to the module from which the require() call is being made.
|
|
|
|
|
|
|
|
2.2 If the module begins with '/' then it's treated as an absolute path.
|
|
|
|
|
|
|
|
If the module does not have a '.js' suffix, and a file with the same name and a .js sufix exists,
|
|
|
|
then the file will be loaded.
|
|
|
|
|
|
|
|
3. If the module name resolves to a directory then...
|
|
|
|
|
|
|
|
3.1 look for a package.json file in the directory and load the `main` property e.g.
|
|
|
|
|
|
|
|
// package.json located in './some-library/'
|
|
|
|
{
|
|
|
|
"main": './some-lib.js',
|
|
|
|
"name": 'some-library'
|
|
|
|
}
|
|
|
|
|
|
|
|
3.2 if no package.json file exists then look for an index.js file in the directory
|
|
|
|
|
|
|
|
***/
|
2014-01-29 20:49:15 +01:00
|
|
|
var resolveModuleToFile = function ( moduleName, parentDir ) {
|
2014-02-10 21:55:32 +01:00
|
|
|
var file = new File(moduleName),
|
|
|
|
i = 0,
|
|
|
|
pathWithJSExt,
|
|
|
|
resolvedFile;
|
2014-01-29 20:49:15 +01:00
|
|
|
if ( file.exists() ) {
|
|
|
|
return fileExists(file);
|
|
|
|
}
|
|
|
|
if ( moduleName.match( /^[^\.\/]/ ) ) {
|
|
|
|
// it's a module named like so ... 'events' , 'net/http'
|
|
|
|
//
|
2014-02-10 21:55:32 +01:00
|
|
|
for ( ; i < modulePaths.length; i++ ) {
|
2014-01-29 20:49:15 +01:00
|
|
|
resolvedFile = new File(modulePaths[i] + moduleName);
|
2014-02-10 21:55:32 +01:00
|
|
|
if ( resolvedFile.exists() ) {
|
2014-01-29 20:49:15 +01:00
|
|
|
return fileExists(resolvedFile);
|
2014-02-10 21:55:32 +01:00
|
|
|
} else {
|
2014-01-29 20:49:15 +01:00
|
|
|
// try appending a .js to the end
|
|
|
|
resolvedFile = new File(modulePaths[i] + moduleName + '.js');
|
2014-02-10 21:55:32 +01:00
|
|
|
if ( resolvedFile.exists() ) {
|
2014-01-29 20:49:15 +01:00
|
|
|
return resolvedFile;
|
2014-03-13 23:49:03 +01:00
|
|
|
}
|
2013-12-24 01:16:07 +01:00
|
|
|
}
|
2014-01-29 20:49:15 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// it's of the form ./path
|
|
|
|
file = new File(parentDir, moduleName);
|
2014-02-10 21:55:32 +01:00
|
|
|
if ( file.exists() ) {
|
2014-01-29 20:49:15 +01:00
|
|
|
return fileExists(file);
|
2014-02-10 21:55:32 +01:00
|
|
|
} else {
|
2014-01-29 20:49:15 +01:00
|
|
|
// try appending a .js to the end
|
2014-02-10 21:55:32 +01:00
|
|
|
pathWithJSExt = file.canonicalPath + '.js';
|
|
|
|
file = new File( parentDir, pathWithJSExt );
|
|
|
|
if (file.exists()) {
|
2014-01-29 20:49:15 +01:00
|
|
|
return file;
|
2014-02-10 21:55:32 +01:00
|
|
|
} else {
|
2014-01-29 20:49:15 +01:00
|
|
|
file = new File(pathWithJSExt);
|
2014-02-10 21:55:32 +01:00
|
|
|
if ( file.exists() ) {
|
2014-01-29 20:49:15 +01:00
|
|
|
return file;
|
2014-03-13 23:49:03 +01:00
|
|
|
}
|
2013-12-24 01:16:07 +01:00
|
|
|
}
|
2014-01-29 20:49:15 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
2014-01-29 21:11:47 +01:00
|
|
|
var _loadedModules = {};
|
|
|
|
var _format = java.lang.String.format;
|
|
|
|
/*
|
|
|
|
require() function implementation
|
|
|
|
*/
|
2014-02-10 21:55:32 +01:00
|
|
|
var _require = function( parentFile, path, options ) {
|
2014-01-29 21:11:47 +01:00
|
|
|
var file,
|
2014-03-13 23:49:03 +01:00
|
|
|
canonizedFilename,
|
|
|
|
moduleInfo,
|
|
|
|
buffered,
|
2014-01-29 21:11:47 +01:00
|
|
|
head = '(function(exports,module,require,__filename,__dirname){ ',
|
2014-03-13 23:49:03 +01:00
|
|
|
code = '',
|
|
|
|
line = null;
|
2014-02-10 21:55:32 +01:00
|
|
|
|
|
|
|
if ( typeof options == 'undefined' ) {
|
|
|
|
options = { cache: true };
|
|
|
|
} else {
|
|
|
|
if ( typeof options.cache == 'undefined' ) {
|
2014-03-13 23:49:03 +01:00
|
|
|
options.cache = true;
|
2014-02-10 21:55:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-29 21:11:47 +01:00
|
|
|
file = resolveModuleToFile(path, parentFile);
|
|
|
|
if ( !file ) {
|
|
|
|
var errMsg = '' + _format("require() failed to find matching file for module '%s' " +
|
|
|
|
"in working directory '%s' ", [path, parentFile.canonicalPath]);
|
|
|
|
if (! ( (''+path).match( /^\./ ) ) ) {
|
|
|
|
errMsg = errMsg + ' and not found in paths ' + JSON.stringify(modulePaths);
|
|
|
|
}
|
2014-09-30 00:42:41 +02:00
|
|
|
throw new Error(errMsg);
|
2014-01-29 21:11:47 +01:00
|
|
|
}
|
|
|
|
canonizedFilename = _canonize(file);
|
2014-02-10 21:55:32 +01:00
|
|
|
|
2014-01-29 21:11:47 +01:00
|
|
|
moduleInfo = _loadedModules[canonizedFilename];
|
|
|
|
if ( moduleInfo ) {
|
2014-02-10 21:55:32 +01:00
|
|
|
if ( options.cache ) {
|
2014-03-13 23:49:03 +01:00
|
|
|
return moduleInfo;
|
2014-02-10 21:55:32 +01:00
|
|
|
}
|
2014-01-29 21:11:47 +01:00
|
|
|
}
|
|
|
|
if ( hooks ) {
|
|
|
|
hooks.loading( canonizedFilename );
|
|
|
|
}
|
|
|
|
buffered = new BufferedReader(new FileReader(file));
|
|
|
|
while ( (line = buffered.readLine()) !== null ) {
|
|
|
|
code += line + '\n';
|
|
|
|
}
|
|
|
|
buffered.close(); // close the stream so there's no file locks
|
|
|
|
|
|
|
|
moduleInfo = {
|
|
|
|
loaded: false,
|
|
|
|
id: canonizedFilename,
|
|
|
|
exports: {},
|
|
|
|
require: _requireClosure(file.parentFile)
|
2013-12-21 09:58:40 +01:00
|
|
|
};
|
2014-01-29 21:11:47 +01:00
|
|
|
var tail = '})';
|
|
|
|
code = head + code + tail;
|
|
|
|
|
2014-02-10 21:55:32 +01:00
|
|
|
if ( options.cache ) {
|
|
|
|
_loadedModules[canonizedFilename] = moduleInfo;
|
|
|
|
}
|
2014-01-29 21:11:47 +01:00
|
|
|
var compiledWrapper = null;
|
|
|
|
try {
|
2014-03-13 23:49:03 +01:00
|
|
|
compiledWrapper = evaluate(code);
|
2014-01-29 21:11:47 +01:00
|
|
|
} catch (e) {
|
2014-03-13 23:49:03 +01:00
|
|
|
/*
|
|
|
|
wph 20140313 JRE8 (nashorn) gives misleading linenumber of evaluating code not evaluated code.
|
|
|
|
This can be fixed by instead using __engine.eval
|
|
|
|
*/
|
2014-03-13 19:18:45 +01:00
|
|
|
throw new Error( "Error evaluating module " + path
|
2014-09-30 00:42:41 +02:00
|
|
|
+ " line #" + e.lineNumber
|
|
|
|
+ " : " + e.message, canonizedFilename, e.lineNumber );
|
2014-01-29 21:11:47 +01:00
|
|
|
}
|
|
|
|
var __dirname = '' + file.parentFile.canonicalPath;
|
|
|
|
var parameters = [
|
|
|
|
moduleInfo.exports, /* exports */
|
|
|
|
moduleInfo, /* module */
|
|
|
|
moduleInfo.require, /* require */
|
|
|
|
canonizedFilename, /* __filename */
|
|
|
|
__dirname /* __dirname */
|
|
|
|
];
|
|
|
|
try {
|
|
|
|
compiledWrapper
|
|
|
|
.apply(moduleInfo.exports, /* this */
|
|
|
|
parameters);
|
|
|
|
} catch (e) {
|
2014-09-30 00:42:41 +02:00
|
|
|
var snippet = '';
|
|
|
|
if ((''+e.lineNumber).match(/[0-9]/)){
|
|
|
|
var lines = code.split(/\n/);
|
|
|
|
if (e.lineNumber > 1){
|
|
|
|
snippet = ' ' + lines[e.lineNumber-2] + '\n';
|
|
|
|
}
|
|
|
|
snippet += '> ' + lines[e.lineNumber-1] + '\n';
|
|
|
|
if (e.lineNumber < lines.length){
|
|
|
|
snippet += ' ' + lines[e.lineNumber] + '\n';
|
|
|
|
}
|
|
|
|
}
|
2014-03-13 19:18:45 +01:00
|
|
|
throw new Error( "Error executing module " + path
|
2014-03-13 23:11:01 +01:00
|
|
|
+ " line #" + e.lineNumber
|
2014-09-30 00:42:41 +02:00
|
|
|
+ " : " + e.message + (snippet?('\n' + snippet):''), canonizedFilename, e.lineNumber );
|
2014-01-29 21:11:47 +01:00
|
|
|
}
|
|
|
|
if ( hooks ) {
|
|
|
|
hooks.loaded( canonizedFilename );
|
|
|
|
}
|
|
|
|
moduleInfo.loaded = true;
|
|
|
|
return moduleInfo;
|
|
|
|
};
|
2013-12-21 09:58:40 +01:00
|
|
|
|
2014-01-29 21:11:47 +01:00
|
|
|
var _requireClosure = function( parent ) {
|
2014-02-10 21:55:32 +01:00
|
|
|
return function( path, options ) {
|
|
|
|
var module = _require( parent, path , options);
|
2014-01-29 21:11:47 +01:00
|
|
|
return module.exports;
|
2013-12-21 09:58:40 +01:00
|
|
|
};
|
2014-01-29 21:11:47 +01:00
|
|
|
};
|
|
|
|
return _requireClosure( new java.io.File(rootDir) );
|
2014-03-15 19:21:20 +01:00
|
|
|
// last line deliberately has no semicolon!
|
2013-12-21 09:58:40 +01:00
|
|
|
})
|