From c51595bfa69271593680e00e8521488c48710704 Mon Sep 17 00:00:00 2001 From: Jannon Date: Tue, 6 Mar 2012 17:59:26 -0800 Subject: [PATCH 1/7] Just some link updates --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e7349b93..56948e79 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ configured to override that included Rhino and point to some older version of Rhino instead. If this is the case, simply correct the CLASSPATH to remove the older Rhino. +The version of rhino distributed with JSDoc3 can be found here: https://github.com/jannon/rhino + Debugging --------- @@ -81,6 +83,7 @@ See Also Project Documentation: (under development) Project Documentation Source: JSDoc User's Group: +JSDoc3 Ant Task Subversion Mirror: Project Annoncements: From bcf1ab387011f39802b3edde0df2ee36c7366f8a Mon Sep 17 00:00:00 2001 From: Jannon Date: Fri, 16 Mar 2012 02:42:58 -0700 Subject: [PATCH 2/7] Link updates --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56948e79..33ef68ed 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Installation ------------ Download a copy of JSDoc 3 from the official Git Hub repository here: - + To test that jsdoc is working, change your working directory to the jsdoc folder and run the following command: @@ -83,8 +83,7 @@ See Also Project Documentation: (under development) Project Documentation Source: JSDoc User's Group: -JSDoc3 Ant Task -Subversion Mirror: +JSDoc3 Ant Task Project Annoncements: License From 28d0af0749a812df90aaa3edfecac7a5641b31e9 Mon Sep 17 00:00:00 2001 From: Jannon Date: Sat, 3 Mar 2012 22:22:44 -0800 Subject: [PATCH 3/7] Separate doclet.meta.filename into doclet.meta.path and doclet.meta.filename when appropriate --- rhino_modules/jsdoc/doclet.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rhino_modules/jsdoc/doclet.js b/rhino_modules/jsdoc/doclet.js index c324d5ad..48d9f86c 100644 --- a/rhino_modules/jsdoc/doclet.js +++ b/rhino_modules/jsdoc/doclet.js @@ -189,7 +189,13 @@ exports.Doclet.prototype.setMeta = function(meta) { The name of the file containing the code associated with this doclet. @type string */ - this.meta.filename = meta.filename; + var idx = meta.filename.lastIndexOf(String(java.lang.System.getProperty("file.separator"))); + if (idx != -1) { + this.meta.path = meta.filename.substring(0, idx); + this.meta.filename = meta.filename.substring(idx + 1); + } else { + this.meta.filename = meta.filename; + } } /** From 60197c6164e39c4aecbef0568f5aa00134e3de10 Mon Sep 17 00:00:00 2001 From: Jannon Date: Sat, 3 Mar 2012 21:01:39 -0800 Subject: [PATCH 4/7] Add 'install' task to Jakefile and ignore conf.json Also adding wrench for directory copy goodness and pointing to more up-to-date Jake information --- .gitignore | 1 + Jakefile.js | 81 +++++-- node_modules/wrench/wrench.js | 397 ++++++++++++++++++++++++++++++++++ 3 files changed, 459 insertions(+), 20 deletions(-) create mode 100644 node_modules/wrench/wrench.js diff --git a/.gitignore b/.gitignore index 04ae28a8..014318f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build-files/java/build jsdoc.jar test/tutorials/out +conf.json \ No newline at end of file diff --git a/Jakefile.js b/Jakefile.js index 3f11938b..1ca09fcf 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -1,32 +1,73 @@ -// see: http://howtonode.org/intro-to-jake +// see: https://github.com/mde/jake desc('Updating package.json revision.'); -task('default', [], function (params) { - var fs = require('fs'), - sys = require('sys'); - +task('default', [], function(params) { + var fs = require('fs'), sys = require('sys'); + // import the Mustache template tool eval(fs.readFileSync('Jake/lib/mustache.js', 'utf8')); - + var templates = { - packagejson: fs.readFileSync('Jake/templates/package.json.tmpl', 'utf8') + packagejson : fs.readFileSync('Jake/templates/package.json.tmpl', 'utf8') }; - + var metadata = { - appname: 'JSDoc', - appversion: '3.0.0alpha', - timestamp: ''+new Date().getTime() + appname : 'JSDoc', + appversion : '3.0.0alpha', + timestamp : '' + new Date().getTime() }; - + var outdir = './'; - - var rendered = Mustache.to_html( - templates.packagejson, - metadata - ); - + + var rendered = Mustache.to_html(templates.packagejson, metadata); + fs.writeFileSync(outdir + 'package.json', rendered, 'utf8'); - + process.exit(0); - + +}); + +desc('Installs a plugin/template.'); +task('install', [], function(loc) { + var fs = require('fs'), util = require('util'), path = require('path'), wrench = require('wrench/wrench'); + + if(!loc) { + fail("You must specify the location of the plugin/template."); + } + + if(!path.existsSync(loc)) { + fail("plugin/template location [" + loc + "] is not valid."); + } + + var pluginLoc = path.join(loc, "plugins"), templateLoc = path.join(loc, "templates"), jsdocLoc = process.cwd(), name, config; + + //First the plugin + if(path.existsSync(pluginLoc)) { + //copy it over + wrench.copyDirSyncRecursive(pluginLoc, path.join(jsdocLoc, "plugins"), { + preserve : true + }); + //find out what it's called + name = fs.readdirSync(pluginLoc)[0].replace(".js", ""); + //And finally edit the conf.json + try { + config = JSON.parse(fs.readFileSync(path.join(jsdocLoc, 'conf.json'), 'utf8')); + if(config.plugins.indexOf('plugins/' + name) == -1) { + config.plugins.push('plugins/' + name); + fs.writeFileSync(path.join(jsdocLoc, 'conf.json'), JSON.stringify(config, null, " "), 'utf8'); + } + } catch (e) { + fail("Could not edit the conf.json file: " + e); + } + } + + //Then the template + if(path.existsSync(pluginLoc)) { + wrench.copyDirSyncRecursive(templateLoc, path.join(jsdocLoc, "templates"), { + preserve : true + }); + } + + process.exit(0); + }); \ No newline at end of file diff --git a/node_modules/wrench/wrench.js b/node_modules/wrench/wrench.js new file mode 100644 index 00000000..592aa6fe --- /dev/null +++ b/node_modules/wrench/wrench.js @@ -0,0 +1,397 @@ +/* wrench.js + * + * A collection of various utility functions I've found myself in need of + * for use with Node.js (http://nodejs.org/). This includes things like: + * + * - Recursively deleting directories in Node.js (Sync, not Async) + * - Recursively copying directories in Node.js (Sync, not Async) + * - Recursively chmoding a directory structure from Node.js (Sync, not Async) + * - Other things that I'll add here as time goes on. Shhhh... + * + * ~ Ryan McGrath (ryan [at] venodesigns.net) + */ + +var fs = require("fs"), + _path = require("path"); + + +/* wrench.readdirSyncRecursive("directory_path"); + * + * Recursively dives through directories and read the contents of all the + * children directories. + */ +exports.readdirSyncRecursive = function(baseDir) { + baseDir = baseDir.replace(/\/$/, ''); + + var readdirSyncRecursive = function(baseDir) { + var files = [], + curFiles, + nextDirs, + isDir = function(fname){ + return fs.statSync( _path.join(baseDir, fname) ).isDirectory(); + }, + prependBaseDir = function(fname){ + return _path.join(baseDir, fname); + }; + + curFiles = fs.readdirSync(baseDir); + nextDirs = curFiles.filter(isDir); + curFiles = curFiles.map(prependBaseDir); + + files = files.concat( curFiles ); + + while (nextDirs.length) { + files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) ); + } + + return files; + }; + + // convert absolute paths to relative + var fileList = readdirSyncRecursive(baseDir).map(function(val){ + return val.replace(baseDir + '/', ''); + }); + + return fileList; +}; + +/* wrench.readdirRecursive("directory_path", function(error, files) {}); + * + * Recursively dives through directories and read the contents of all the + * children directories. + * + * Asynchronous, so returns results/error in callback. + * Callback receives the of files in currently recursed directory. + * When no more directories are left, callback is called with null for all arguments. + * + */ +exports.readdirRecursive = function(baseDir, fn) { + baseDir = baseDir.replace(/\/$/, ''); + + var waitCount = 0; + + function readdirRecursive(curDir) { + var files = [], + curFiles, + nextDirs, + prependcurDir = function(fname){ + return _path.join(curDir, fname); + }; + + waitCount++; + fs.readdir(curDir, function(e, curFiles) { + waitCount--; + + curFiles = curFiles.map(prependcurDir); + + curFiles.forEach(function(it) { + waitCount++; + + fs.stat(it, function(e, stat) { + waitCount--; + + if (e) { + fn(e); + } else { + if (stat.isDirectory()) { + readdirRecursive(it); + } + } + + if (waitCount == 0) { + fn(null, null); + } + }); + }); + + fn(null, curFiles.map(function(val) { + // convert absolute paths to relative + return val.replace(baseDir + '/', ''); + })); + + if (waitCount == 0) { + fn(null, null); + } + }); + }; + + readdirRecursive(baseDir); +}; + + + +/* wrench.rmdirSyncRecursive("directory_path", forceDelete, failSilent); + * + * Recursively dives through directories and obliterates everything about it. This is a + * Sync-function, which blocks things until it's done. No idea why anybody would want an + * Asynchronous version. :\ + */ +exports.rmdirSyncRecursive = function(path, failSilent) { + var files; + + try { + files = fs.readdirSync(path); + } catch (err) { + if(failSilent) return; + throw new Error(err.message); + } + + /* Loop through and delete everything in the sub-tree after checking it */ + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(path + "/" + files[i]); + + if(currFile.isDirectory()) // Recursive function back to the beginning + exports.rmdirSyncRecursive(path + "/" + files[i]); + + else if(currFile.isSymbolicLink()) // Unlink symlinks + fs.unlinkSync(path + "/" + files[i]); + + else // Assume it's a file - perhaps a try/catch belongs here? + fs.unlinkSync(path + "/" + files[i]); + } + + /* Now that we know everything in the sub-tree has been deleted, we can delete the main + directory. Huzzah for the shopkeep. */ + return fs.rmdirSync(path); +}; + +/* wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts); + * + * Recursively dives through a directory and moves all its files to a new location. This is a + * Synchronous function, which blocks things until it's done. If you need/want to do this in + * an Asynchronous manner, look at wrench.copyDirRecursively() below. + * + * Note: Directories should be passed to this function without a trailing slash. + */ +exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) { + + if (!opts || !opts.preserve) { + try { + if(fs.statSync(newDirLocation).isDirectory()) exports.rmdirSyncRecursive(newDirLocation); + } catch(e) { } + } + + /* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */ + var checkDir = fs.statSync(sourceDir); + try { + fs.mkdirSync(newDirLocation, checkDir.mode); + } catch (e) { + //if the directory already exists, that's okay + if (e.code !== 'EEXIST') throw e; + } + + var files = fs.readdirSync(sourceDir); + + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(sourceDir + "/" + files[i]); + + if(currFile.isDirectory()) { + /* recursion this thing right on back. */ + exports.copyDirSyncRecursive(sourceDir + "/" + files[i], newDirLocation + "/" + files[i], opts); + } else if(currFile.isSymbolicLink()) { + var symlinkFull = fs.readlinkSync(sourceDir + "/" + files[i]); + fs.symlinkSync(symlinkFull, newDirLocation + "/" + files[i]); + } else { + /* At this point, we've hit a file actually worth copying... so copy it on over. */ + var contents = fs.readFileSync(sourceDir + "/" + files[i]); + fs.writeFileSync(newDirLocation + "/" + files[i], contents); + } + } +}; + +/* wrench.chmodSyncRecursive("directory", filemode); + * + * Recursively dives through a directory and chmods everything to the desired mode. This is a + * Synchronous function, which blocks things until it's done. + * + * Note: Directories should be passed to this function without a trailing slash. + */ +exports.chmodSyncRecursive = function(sourceDir, filemode) { + var files = fs.readdirSync(sourceDir); + + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(sourceDir + "/" + files[i]); + + if(currFile.isDirectory()) { + /* ...and recursion this thing right on back. */ + exports.chmodSyncRecursive(sourceDir + "/" + files[i], filemode); + } else { + /* At this point, we've hit a file actually worth copying... so copy it on over. */ + fs.chmod(sourceDir + "/" + files[i], filemode); + } + } + + /* Finally, chmod the parent directory */ + fs.chmod(sourceDir, filemode); +}; + + +/* wrench.chownSyncRecursive("directory", uid, gid); + * + * Recursively dives through a directory and chowns everything to the desired user and group. This is a + * Synchronous function, which blocks things until it's done. + * + * Note: Directories should be passed to this function without a trailing slash. + */ +exports.chownSyncRecursive = function(sourceDir, uid, gid) { + var files = fs.readdirSync(sourceDir); + + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(sourceDir + "/" + files[i]); + + if(currFile.isDirectory()) { + /* ...and recursion this thing right on back. */ + exports.chownSyncRecursive(sourceDir + "/" + files[i], uid, gid); + } else { + /* At this point, we've hit a file actually worth chowning... so own it. */ + fs.chownSync(sourceDir + "/" + files[i], uid, gid); + } + } + + /* Finally, chown the parent directory */ + fs.chownSync(sourceDir, uid, gid); +}; + + + +/* wrench.rmdirRecursive("directory_path", callback); + * + * Recursively dives through directories and obliterates everything about it. + */ +exports.rmdirRecursive = function rmdirRecursive(dir, clbk){ + fs.readdir(dir, function(err, files){ + if (err) return clbk(err); + (function rmFile(err){ + if (err) return clbk(err); + + var filename = files.shift(); + if (filename === null || typeof filename == 'undefined') + return fs.rmdir(dir, clbk); + + var file = dir+'/'+filename; + fs.stat(file, function(err, stat){ + if (err) return clbk(err); + if (stat.isDirectory()) + rmdirRecursive(file, rmFile); + else + fs.unlink(file, rmFile); + }); + })(); + }); +}; + +/* wrench.copyDirRecursive("directory_to_copy", "new_location", callback); + * + * Recursively dives through a directory and moves all its files to a new + * location. + * + * Note: Directories should be passed to this function without a trailing slash. + */ +exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, clbk) { + fs.stat(newDir, function(err, newDirStat){ + if (!err) return exports.rmdirRecursive(newDir, function(err){ + copyDirRecursive(srcDir, newDir, clbk); + }); + + fs.stat(srcDir, function(err, srcDirStat){ + if (err) return clbk(err); + fs.mkdir(newDir, srcDirStat.mode, function(err){ + if (err) return clbk(err); + fs.readdir(srcDir, function(err, files){ + if (err) return clbk(err); + (function copyFiles(err){ + if (err) return clbk(err); + + var filename = files.shift(); + if (filename === null || typeof filename == 'undefined') + return clbk(); + + var file = srcDir+'/'+filename, + newFile = newDir+'/'+filename; + + fs.stat(file, function(err, fileStat){ + if (fileStat.isDirectory()) + copyDirRecursive(file, newFile, copyFiles); + else if (fileStat.isSymbolicLink()) + fs.readlink(file, function(err, link){ + fs.symlink(link, newFile, copyFiles); + }); + else + fs.readFile(file, function(err, data){ + fs.writeFile(newFile, data, copyFiles); + }); + }); + })(); + }); + }); + }); + }); +}; + +var mkdirSyncRecursive = function(path, mode) { + var self = this; + + try { + fs.mkdirSync(path, mode); + } catch(err) { + if(err.code == "ENOENT") { + var slashIdx = path.lastIndexOf("/"); + if(slashIdx < 0) { + slashIdx = path.lastIndexOf("\\"); + } + + if(slashIdx > 0) { + var parentPath = path.substring(0, slashIdx); + mkdirSyncRecursive(parentPath, mode); + mkdirSyncRecursive(path, mode); + } else { + throw err; + } + } else if(err.code == "EEXIST") { + return; + } else { + throw err; + } + } +}; +exports.mkdirSyncRecursive = mkdirSyncRecursive; + +exports.LineReader = function(filename, bufferSize) { + this.bufferSize = bufferSize || 8192; + this.buffer = ""; + this.fd = fs.openSync(filename, "r"); + this.currentPosition = 0; +}; + +exports.LineReader.prototype = { + getBufferAndSetCurrentPosition: function(position) { + var res = fs.readSync(this.fd, this.bufferSize, position, "ascii"); + + this.buffer += res[0]; + if(res[1] === 0) return -1; + + this.currentPosition = position + res[1]; + return this.currentPosition; + }, + + hasNextLine: function() { + while(this.buffer.indexOf('\n') === -1) { + this.getBufferAndSetCurrentPosition(this.currentPosition); + if(this.currentPosition === -1) return false; + if(this.buffer.length === 0) return false; + } + + if(this.buffer.indexOf("\n") > -1) return true; + return false; + }, + + getNextLine: function() { + var lineEnd = this.buffer.indexOf("\n"), + result = this.buffer.substring(0, lineEnd); + + this.buffer = this.buffer.substring(result.length + 1, this.buffer.length); + return result; + } +}; + +// vim: et ts=4 sw=4 From 3d1d9d732148b46057d7915b8d5d1337d3091583 Mon Sep 17 00:00:00 2001 From: Jannon Date: Sat, 3 Mar 2012 20:18:59 -0800 Subject: [PATCH 5/7] Use conf.json.EXAMPLE and .gitignore conf.json to changes don't affect repo status To make it no impact, if JSDoc can't find a conf.json, it looks for conf.json.EXAMPLE and copies that over. --- conf.json => conf.json.EXAMPLE | 6 +----- jsdoc.js | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) rename conf.json => conf.json.EXAMPLE (82%) diff --git a/conf.json b/conf.json.EXAMPLE similarity index 82% rename from conf.json rename to conf.json.EXAMPLE index 2ff04b4c..3d669f09 100644 --- a/conf.json +++ b/conf.json.EXAMPLE @@ -2,14 +2,10 @@ "tags": { "allowUnknownTags": true }, - "source": { "includePattern": ".+\\.js(doc)?$", "excludePattern": "(^|\\/)_" }, - - "plugins": [ - ], - + "plugins": [], "jsVersion": 180 } \ No newline at end of file diff --git a/jsdoc.js b/jsdoc.js index 9e425ced..05b82b38 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -144,17 +144,26 @@ function main() { } }, resolver, - dictionary = require('jsdoc/tag/dictionary'); + dictionary = require('jsdoc/tag/dictionary'), + fs = require('fs'); env.opts = jsdoc.opts.parser.parse(env.args); try { env.conf = JSON.parse( - require('fs').readFileSync( env.opts.configure || __dirname + '/conf.json' ) + fs.readFileSync( env.opts.configure || __dirname + '/conf.json' ) ); } catch (e) { - throw('Configuration file cannot be evaluated. '+e); + try { + //Try to copy over the example conf + var example = fs.readFileSync(__dirname + '/conf.json.EXAMPLE', 'utf8'); + fs.writeFileSync(__dirname + '/conf.json', example, 'utf8'); + env.conf = JSON.parse(example); + } + catch(e) { + throw('Configuration file cannot be evaluated. ' + e); + } } // allow to pass arguments from configuration file From cf023b0461557c57999987de0d3789aeadbdb0d8 Mon Sep 17 00:00:00 2001 From: Jannon Date: Sat, 3 Mar 2012 20:00:09 -0800 Subject: [PATCH 6/7] Use non-reserved word for name of arguments variable The jsdoc.js file is processed twice for some reason when executing from the jsdoc3 ant task (github.com/jannon/jsdoc3-ant-task), but the arguments variable remains the same so the second pass is missing the "dirname option that was spliced out the first time. Trouble ensues. Although the root cause of that is unknown, there's no reason not to just be safe and name the variable something else. Now, during the second pass, the arguments retains it's original value. --- jsdoc.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jsdoc.js b/jsdoc.js index 05b82b38..c04e2702 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -13,19 +13,19 @@ @global */ __dirname = '.', -arguments = Array.prototype.slice.call(arguments, 0); +args = Array.prototype.slice.call(arguments, 0); // rhino has no native way to get the base dirname of the currently running script // so this information must be manually passed in from the command line -for (var i = 0; i < arguments.length; i++) { - if ( /^--dirname(?:=(.+?)(\/|\/\.)?)?$/i.test(arguments[i]) ) { +for (var i = 0; i < args.length; i++) { + if ( /^--dirname(?:=(.+?)(\/|\/\.)?)?$/i.test(args[i]) ) { if (RegExp.$1) { __dirname = RegExp.$1; // last wins - arguments.splice(i--, 1); // remove --dirname opt from arguments + args.splice(i--, 1); // remove --dirname opt from arguments } else { - __dirname = arguments[i + 1]; - arguments.splice(i--, 2); + __dirname = args[i + 1]; + args.splice(i--, 2); } } } @@ -49,7 +49,7 @@ env = { The command line arguments passed into jsdoc. @type Array */ - args: Array.prototype.slice.call(arguments, 0), + args: Array.prototype.slice.call(args, 0), /** From 00f3ed8dc6d44a17dcf4c5724b8b9e53a2d77859 Mon Sep 17 00:00:00 2001 From: Jannon Date: Fri, 16 Mar 2012 03:06:26 -0700 Subject: [PATCH 7/7] typo fix in template helper and changes for split path and filename in doclet.meta --- rhino_modules/jsdoc/tag/dictionary/definitions.js | 8 ++++++-- rhino_modules/jsdoc/util/templateHelper.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rhino_modules/jsdoc/tag/dictionary/definitions.js b/rhino_modules/jsdoc/tag/dictionary/definitions.js index f34cb108..8841b624 100644 --- a/rhino_modules/jsdoc/tag/dictionary/definitions.js +++ b/rhino_modules/jsdoc/tag/dictionary/definitions.js @@ -577,7 +577,11 @@ function setDocletDescriptionToValue(doclet, tag) { } function setNameToFile(doclet, tag) { - if (doclet.meta.filename) { doclet.addTag( 'name', 'file:'+doclet.meta.filename ); } + if (doclet.meta.filename) { + var name = 'file:'; + if (doclet.meta.path) { name += doclet.meta.path + java.lang.System.getProperty("file.separator"); } + doclet.addTag( 'name', name + doclet.meta.filename ); + } } function setDocletMemberof(doclet, tag) { @@ -599,7 +603,7 @@ function applyNamespace(docletOrNs, tag) { } function setDocletNameToFilename(doclet, tag) { - var name = doclet.meta.filename; + var name = (doclet.meta.path ? (doclet.meta.path + java.lang.System.getProperty("file.separator")) : "") + doclet.meta.filename; name = name.replace(/\.js$/i, ''); for (var i = 0, len = env.opts._.length; i < len; i++) { diff --git a/rhino_modules/jsdoc/util/templateHelper.js b/rhino_modules/jsdoc/util/templateHelper.js index bc3be2b0..4303961a 100644 --- a/rhino_modules/jsdoc/util/templateHelper.js +++ b/rhino_modules/jsdoc/util/templateHelper.js @@ -71,7 +71,7 @@ var nsprefix = /^(event|module|external):/; function strToFilename(str) { // allow for namespace prefix - var basename = str.replace(nsPrefix, '$1-'); + var basename = str.replace(nsprefix, '$1-'); if ( /[^$a-z0-9._-]/i.test(basename) ) { return hash.hex_md5(str).substr(0, 10);