diff --git a/javascript/node/deploy.js b/javascript/node/deploy.js deleted file mode 100644 index 7bb221b7b0da8..0000000000000 --- a/javascript/node/deploy.js +++ /dev/null @@ -1,163 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Script used to prepare WebDriverJS as a Node module. - */ - -'use strict'; - -var assert = require('assert'), - fs = require('fs'), - path = require('path'); - -var optparse = require('./optparse'); -var gendocs = require('./gendocs'); - - -/** - * @param {string} srcDir Path to the main source directory. - * @param {string} outputDirPath Path to the directory to copy src files to. - */ -function copySrcs(srcDir, outputDirPath) { - var filePaths = fs.readdirSync(srcDir); - filePaths.forEach(function(filePath) { - if (filePath === 'node_modules') { - return; - } - filePath = path.join(srcDir, filePath); - if (fs.statSync(filePath).isDirectory()) { - copySrcs(filePath, path.join(outputDirPath, path.basename(filePath))); - } else { - var dest = path.join(outputDirPath, path.basename(filePath)); - copyFile(filePath, dest); - } - }); -} - - -function copyFile(src, dest) { - createDirectoryIfNecessary(path.dirname(dest)); - - var buffer = fs.readFileSync(src); - fs.writeFileSync(dest, buffer); -} - - -function copyDirectory(baseDir, dest, exclusions) { - createDirectoryIfNecessary(dest); - if (!fs.statSync(dest).isDirectory()) { - throw Error(dest + ' is not a directory!'); - } - - fs.readdirSync(path.resolve(baseDir)). - map(function(filePath) { - return path.join(baseDir, filePath); - }). - filter(function(filePath) { - return !exclusions.some(function(exclusion) { - return exclusion.test(filePath); - }); - }). - forEach(function(srcFile) { - var destFile = path.join(dest, srcFile.substring(baseDir.length)); - if (fs.statSync(srcFile).isDirectory()) { - copyDirectory(srcFile, destFile, exclusions); - } else { - copyFile(path.resolve(srcFile), destFile); - } - }); -} - - -function createDirectoryIfNecessary(dirPath) { - var toCreate = []; - var current = dirPath; - while (!fs.existsSync(current)) { - toCreate.push(path.basename(current)); - current = path.dirname(current); - } - - while (toCreate.length) { - current = path.join(current, toCreate.pop()); - fs.mkdirSync(current); - } -} - - -function copyResources(outputDirPath, resources, exclusions) { - resources.forEach(function(resource) { - var parts = resource.split(':', 2); - var src = path.resolve(parts[0]); - var dest = outputDirPath; - - var isAbsolute = path.resolve(parts[1]) === parts[1]; - if (!isAbsolute) { - dest = path.join(dest, 'lib'); - } - dest = path.join(dest, parts[1]); - - if (fs.statSync(src).isDirectory()) { - copyDirectory(parts[0], dest, exclusions); - } else { - copyFile(src, dest); - } - }); -} - - -function main() { - var parser = new optparse.OptionParser(). - path('output', { help: 'Path to the output directory' }). - path('src', { - help: 'Path to the module source directory. The entire contents of ' + - 'this directory will be copied recursively to the main output ' + - 'directory.' - }). - string('resource', { - help: 'A resource which should be copied into the final module, in ' + - 'the form of a ":" colon separated pair, the first part ' + - 'designating the source, and the second its destination. If ' + - 'the destination path is absolute, it is relative to the ' + - 'module root, otherwise it will be treated relative to the ' + - 'lib/ directory. If the source refers to a directory, the ' + - 'recursive contents of that directory will be copied to the ' + - 'destination directory.', - list: true - }). - regex('exclude_resource', { - help: 'A pattern for files to exclude when copying ' + - 'an entire directory of resources.', - list: true - }); - parser.parse(); - - var options = parser.options; - - console.log('Copying sources...'); - copySrcs(options.src, options.output); - console.log('Copying resource files...'); - copyResources(options.output, options.resource, options.exclude_resource); - console.log('Generating documentation...'); - gendocs().then(() => console.log('ALL DONE'), function(e) { - setTimeout(() => {throw e}, 0); - }); -} - - -assert.strictEqual(module, require.main, 'This module may not be included'); -main(); diff --git a/javascript/node/externs/global.js b/javascript/node/externs/global.js deleted file mode 100644 index 49469b8d91997..0000000000000 --- a/javascript/node/externs/global.js +++ /dev/null @@ -1,115 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Symbols expected to be defined in the global scope. - */ - -/** @const */ -var global = this; - -// DOM type references. Dossier removes DOM externs when type-checking Node -// modules. - -/** @constructor */ -function Document() {}; -/** - * @param {string} tagName . - * @return {!Element} . - */ -Document.prototype.createElement = function(tagName) {}; - -/** @constructor */ -function Element() {} - -/** @type {!Document} */Element.prototype.ownerDocument; -/** @type {string} */Element.prototype.innerHTML; -/** @type {string} */Element.prototype.outerHTML; -/** @param {!Element} child . */ -Element.prototype.appendChild = function(child) {}; -/** @param {boolean} deep . */ -Element.prototype.cloneNode = function(deep) {}; - - -// The following will only actually be defined if running with mocha. - -/** - * @param {function()} fn - * @return {void} - */ -function after(fn) {} - -/** - * @param {function()} fn - * @return {void} - */ -function afterEach(fn) {} - -/** - * @param {function()} fn - * @return {void} - */ -function before(fn) {} - -/** - * @param {function()} fn - * @return {void} - */ -function beforeEach(fn) {} - -/** - * @param {string} name - * @param {function()=} fn - * @return {void} - */ -function describe(name, fn) {} - -/** - * @param {string} name - * @param {function()=} fn - * @return {void} - */ -describe.skip = function(name, fn) {} - -/** - * @param {string} name - * @param {function()=} fn - * @return {void} - */ -function xdescribe(name, fn) {} - -/** - * @param {string} name - * @param {function()=} fn - * @return {void} - */ -function it(name, fn) {} - -/** - * @param {string} name - * @param {function()=} fn - * @return {void} - */ -it.only = function(name, fn) {}; - -/** - * @param {string} name - * @param {function()=} fn - * @return {void} - */ -function xit(name, fn) {} - diff --git a/javascript/node/externs/jszip.js b/javascript/node/externs/jszip.js deleted file mode 100644 index 6a8d01f63b5d9..0000000000000 --- a/javascript/node/externs/jszip.js +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Extern definitions for the `jszip` npm package: - * - */ - -/** @interface */ -var Entry = function() {}; - -/** - * @param {string} type - * @return {!Promise} - */ -Entry.prototype.async; - - -/** @constructor */ -var Archive = function() {}; - -/** - * @param {string} path - * @param {Buffer=} data - * @return {?Entry} - */ -Archive.prototype.file; - -/** - * @param {string} path - * @return {!Archive} - */ -Archive.prototype.folder; - -/** - * @param {function(string, !Entry): void} callback - */ -Archive.prototype.forEach; - -/** - * @param {{compression: string, type: string}} options - * @return {!Promise} - */ -Archive.prototype.generateAsync; - -/** - * @param {!Buffer} data - * @return {!Promise} - */ -Archive.prototype.loadAsync; - - -module.exports = Archive; diff --git a/javascript/node/externs/mocha.js b/javascript/node/externs/mocha.js deleted file mode 100644 index e9d6d3ae59ee8..0000000000000 --- a/javascript/node/externs/mocha.js +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Extern definitions for mocha's BDD API. - */ - -/** @const */ -var mocha = {}; - -/** @constructor */ -mocha.Runnable = function() {}; - -/** @type {!Function} */ -mocha.Runnable.prototype.callback; - -/** @return {string} */ -mocha.Runnable.prototype.fullTitle = function() {}; - -/** @constructor */ -mocha.Context = function() {}; - -/** @return {!mocha.Runnable} */ -mocha.Context.prototype.runnable = function() {}; - -module.exports = mocha; diff --git a/javascript/node/externs/rimraf.js b/javascript/node/externs/rimraf.js deleted file mode 100644 index daef90b8fd21b..0000000000000 --- a/javascript/node/externs/rimraf.js +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Extern definitions for https://www.npmjs.com/package/rimraf - */ - -/** - * @param {string} glob - * @param {function(Error)} callback - */ -module.exports = function(glob, callback) {}; diff --git a/javascript/node/externs/tmp.js b/javascript/node/externs/tmp.js deleted file mode 100644 index 629b8b0f5166e..0000000000000 --- a/javascript/node/externs/tmp.js +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Extern definitions for https://www.npmjs.com/package/tmp - */ - -/** @const */ -var tmp = {}; - -/** - * @typedef {{module: (string|undefined), - * prefix: (string|undefined), - * postfix: (string|undefined), - * template: (string|undefined), - * dir: (string|undefined), - * tries: (number|undefined), - * keep: (boolean|undefined), - * unsafeCleanup: (boolean|undefined)}} - */ -var FileOptions; - -/** - * @typedef {function(Error, (string|undefined)): void} - */ -var Callback; - -/** - * @param {(FileOptions|Callback)} arg0 - * @param {Callback=} arg1 - */ -tmp.dir = function(arg0, arg1) {}; - -/** - * @param {(FileOptions|Callback)} arg0 - * @param {Callback=} arg1 - */ -tmp.file = function(arg0, arg1) {}; - -module.exports = tmp; diff --git a/javascript/node/externs/ws.js b/javascript/node/externs/ws.js deleted file mode 100644 index 68a17e5a00546..0000000000000 --- a/javascript/node/externs/ws.js +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Extern definitions for https://www.npmjs.com/package/ws - */ - -/** - * @typedef {{server: !http.Server}} - */ -var ServerOptions; - -/** @const */ -var ws = {}; - -/** - * @param {ServerOptions} options . - * @constructor - * @extends events.EventEmitter - */ -ws.Server = function(options) {}; - -/** - * @constructor - * @extends events.EventEmitter - */ -ws.WebSocket = function() {}; - -/** - * @param {string} data - * @param {function(Error)} callback - */ -ws.WebSocket.prototype.send = function(data, callback) {}; - -module.exports = ws; diff --git a/javascript/node/externs/xml2js.js b/javascript/node/externs/xml2js.js deleted file mode 100644 index 45f2357fde728..0000000000000 --- a/javascript/node/externs/xml2js.js +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Extern definitions for https://www.npmjs.com/package/xml2js - */ - -/** @const */ -var xml2js = {}; - -/** - * @param {string} input - * @param {function(Error, Object)} callback - */ -xml2js.parseString = function(input, callback) {}; - -module.exports = xml2js; diff --git a/javascript/node/gendocs.js b/javascript/node/gendocs.js deleted file mode 100644 index 6bad010614dfb..0000000000000 --- a/javascript/node/gendocs.js +++ /dev/null @@ -1,274 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview Module that will generate the API documentation for the - * `selenium-webdriver` npm package. - */ - -'use strict'; - -const child_process = require('child_process'), - fs = require('fs'), - path = require('path'); - -const PROJECT_ROOT = path.join(__dirname, '../..'); - - -/** - * @param {string} command the command to run. - * @param {!Array} args command arguments. - * @param {!Object} opts command options. - * @return {!Promise} a promise that will resolve when the command - * completes. - */ -function exec(command, args, opts) { - console.log(`${command} ${args.join(' ')}`); - return new Promise(function(fulfill, reject) { - child_process.spawn(command, args, opts) - .on('error', reject) - .on('exit', function(code, signal) { - if (code) { - reject(Error(`command terminated with status=${code}`)); - } else if (signal) { - reject(Error(`command killed with signal=${signal}`)); - } else { - fulfill(); - } - }); - }); -} - -/** - * @param {string} aPath path to the directory to create. - * @return {!Promise} promise that will resolve with the path to the - * created directory. - */ -function mkdirp(aPath) { - return new Promise(function(fulfill, reject) { - console.log('...creating %s', aPath); - fs.mkdir(aPath, function(err) { - if (!err) return fulfill(aPath); - - switch (err.code) { - case 'EEXIST': - fulfill(aPath); - break; - - case 'ENOENT': - mkdirp(path.dirname(aPath)) - .then(() => mkdirp(aPath)) - .then(fulfill); - break; - - default: - reject(err); - break; - } - }); - }); -} - -/** - * @return {!Promise} a promise that will resolve with the path to the - * dossier jar. - */ -function installDossier() { - return new Promise(function(fulfill, reject) { - let buildNodeDir = path.join(PROJECT_ROOT); - let jar = path.join(buildNodeDir, 'node_modules/js-dossier/dossier.jar'); - fs.stat(jar, function(err) { - if (!err) return fulfill(jar); - - console.log('Installing dossier...'); - const args = ['install', 'js-dossier']; - const opts = {cwd: buildNodeDir, stdio: 'inherit'}; - exec('npm', args, opts).then(() => fulfill(jar), reject); - }); - }); -} - - -/** - * @return {!Promise>} a promise for the list of modules to - * generate docs for. - */ -function getModules() { - console.log('Scanning sources...'); - const excludeDirs = [ - path.join(__dirname, 'selenium-webdriver/example'), - path.join(__dirname, 'selenium-webdriver/lib/atoms'), - path.join(__dirname, 'selenium-webdriver/lib/firefox'), - path.join(__dirname, 'selenium-webdriver/lib/safari'), - path.join(__dirname, 'selenium-webdriver/lib/test'), - path.join(__dirname, 'selenium-webdriver/lib/tools'), - path.join(__dirname, 'selenium-webdriver/devtools/generator'), - path.join(__dirname, 'selenium-webdriver/node_modules'), - path.join(__dirname, 'selenium-webdriver/test') - ]; - function scan(dir) { - return listFiles(dir).then(function(files) { - return files.filter(f => excludeDirs.indexOf(f) === -1); - }).then(function(files) { - return Promise.all(files.map(isDir)) - .then(function(isDir) { - let jsFiles = files.filter( - (file, index) => !isDir[index] && file.endsWith('.js')); - - return Promise - .all(files.filter((f, i) => isDir[i]).map(scan)) - .then(files => jsFiles.concat.apply(jsFiles, files)); - }); - }); - } - return scan(path.join(__dirname, 'selenium-webdriver')); -} - - -/** - * @param {string} path the path to check. - * @return {!Promise} a promise that will resolve with whether the - * given path is a directory. - */ -function isDir(path) { - return new Promise(function(fulfill, reject) { - fs.stat(path, function(err, stats) { - if (err) return reject(err); - fulfill(stats.isDirectory()); - }); - }); -} - - -/** - * @param {string} dir path to the directory to list. - * @return {!Promise>} a promise that will resolve with the list - * of files in the directory. - */ -function listFiles(dir) { - return new Promise(function(fulfill, reject) { - fs.readdir(dir, function(err, files) { - if (err) return reject(err); - files = (files || []).map(f => path.join(dir, f)); - fulfill(files); - }); - }); -} - - -/** - * @param {!Array} modules List of files to generate docs for. - * @return {!Object} the JSON config. - */ -function buildConfig(modules) { - console.log('Generating dossier config...'); - let webdriver = path.join(__dirname, 'selenium-webdriver'); - let externs = path.join(__dirname, 'externs'); - return { - output: path.join( - PROJECT_ROOT, 'build/javascript/node/selenium-webdriver-docs'), - customPages: [ - { - name: 'Changes', - path: path.join(webdriver, 'CHANGES.md') - } - ], - readme: path.join(webdriver, 'README.md'), - language: 'ES6_STRICT', - moduleNamingConvention: 'NODE', - modules: modules, - // Exclude modules that are considered purely implementation details. - moduleFilters: [ - path.join(webdriver, 'lib/devmode.js'), - path.join(webdriver, 'lib/symbols.js') - ], - externs: [path.join(externs, 'global.js')], - externModules: [ - path.join(externs, 'jszip.js'), - path.join(externs, 'mocha.js'), - path.join(externs, 'rimraf.js'), - path.join(externs, 'tmp.js'), - path.join(externs, 'ws.js'), - path.join(externs, 'xml2js.js') - ], - sourceUrlTemplate: - 'https://github.com/SeleniumHQ/selenium/tree/trunk/' - + 'javascript/node/selenium-webdriver/%path%#L%line%', - strict: false - } -} - - -/** - * @param {!Object} config the JSON config to write - * @return {!Promise} a promise that will resolve with the parsed - * JSON config. - */ -function writeConfig(config) { - console.log('Creating output root...'); - return mkdirp(config.output).then(function() { - let configFile = config.output + '.json'; - console.log('Writing config...'); - return new Promise(function(fulfill, reject) { - fs.writeFile(configFile, JSON.stringify(config), 'utf8', function(err) { - if (err) { - reject(Error(`failed to write config file: ${err}`)); - return; - } - fulfill(config); - }); - }); - }); -} - - -/** - * @param {!Object} config The json config to use. - * @return {!Promise} a promise that will resolve when the task is - * complete. - */ -function generateDocs(config) { - return installDossier().then(function(jar) { - let args = ['-jar', jar, '-c', config.output + '.json']; - console.log(`Generating ${config.output}...`); - return exec('java', args, {stdio: 'inherit'}); - }); -} - - -/** - * Prints the given error and exits the program. - * @param {!Error} e the error to print. - */ -function die(e) { - console.error(e.stack); - process.exit(1); -} - - -function main() { - return getModules() - .then(buildConfig) - .then(writeConfig) - .then(generateDocs); -} -module.exports = main; - - -if (module === require.main) { - return main().then(() => console.log('DONE!'), die); -} diff --git a/javascript/node/optparse.js b/javascript/node/optparse.js deleted file mode 100644 index a7b0b04e52538..0000000000000 --- a/javascript/node/optparse.js +++ /dev/null @@ -1,553 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -/** - * @fileoverview A simple command line option parser. - */ - -'use strict'; - -var path = require('path'); - - -/** - * Repeats a string n times. - * @param {string} str The string to repeat. - * @param {number} n The number of times to repeat the string. - * @return {string} The repeated string, concatenated together. - */ -function repeatStr(str, n) { - return new Array(n + 1).join(str); -} - - -/** - * Trims a string of all trailing and leading whitespace. - * @param {string} str The string to trim. - * @return {string} The trimmed string. - */ -function trimStr(str) { - return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); -} - - -/** - * Wraps the provided {@code text} so every line is at most {@code width} - * characters long. - * @param {string} text The text to wrap. - * @param {number} width The maximum line length. - * @param {string=} opt_indent String that will be prepended to each line. - * Defaults to the empty string. - * @return {!Array.} A list of lines, without newline characters. - */ -function wrapStr(text, width, opt_indent) { - var out = [], - indent = opt_indent || ''; - - if (indent.length >= width) { - throw Error('Wrapped line indentation is longer than permitted width: ' + - indent.length + ' >= ' + width); - } - - text.split('\n').forEach(function(line) { - if (/^\s*$/.test(line)) { - out.push(''); // Push a blank line. - return; - } - - do { - line = indent + trimStr(line); - out.push(line.substring(0, width)); - line = line.substring(width); - } while (line); - }); - - return out; -} - - -/** - * The total number of columns for output in a printed help message. - * @type {number} - * @const - */ -var TOTAL_WIDTH = 80; - - -/** - * Indentation to use between the left column and options string, and between - * the options string and help text. - * @type {string} - * @const - */ -var IDENTATION = ' '; - - -/** - * The maximum column for option text. - * @type {number} - * @const - */ -var MAX_HELP_POSITION = 24; - - -/** - * Column where the help text should begin. - * @type {number} - * @const - */ -var HELP_TEXT_POSITION = MAX_HELP_POSITION + IDENTATION.length; - - -/** - * Formats a help message for the given parser. - * @param {string} usage The usage string. All occurrences of "$0" will be - * replaced with the name of the current program. - * @param {!Object.} options The options to format. - * @return {string} The formatted help message. - */ -function formatHelpMsg(usage, options) { - var prog = path.basename( - process.argv[0]) + ' ' + path.basename(process.argv[1]); - var help = [ - usage.replace(/\$0\b/g, prog), - '', - 'Options:', - formatOption('help', 'Show this message and exit') - ]; - - Object.keys(options).sort().forEach(function(key) { - help.push(formatOption(key, options[key].help)); - }); - - help.push(''); - - return help.join('\n'); -} - - -/** - * Formats the help message for a single option. Will place the option string - * and help text on the same line whenever possible. - * @param {string} name The name of the option. - * @param {string} helpMsg The option's help message. - * @return {string} The formatted option. - */ -function formatOption(name, helpMsg) { - var result = []; - var options = IDENTATION + '--' + name; - - if (options.length > MAX_HELP_POSITION) { - result.push(options); - result.push('\n'); - result.push(wrapStr(helpMsg, TOTAL_WIDTH, - repeatStr(' ', HELP_TEXT_POSITION)).join('\n')); - } else { - var spaceCount = HELP_TEXT_POSITION - options.length; - options += repeatStr(' ', spaceCount) + helpMsg; - result.push(options.substring(0, TOTAL_WIDTH)); - options = options.substring(TOTAL_WIDTH); - if (options) { - result.push('\n'); - result.push(wrapStr(options, TOTAL_WIDTH, - repeatStr(' ', HELP_TEXT_POSITION)).join('\n')); - } - } - - return result.join(''); -} - - -var OPTION_NAME_PATTERN = '[a-zA-Z][a-zA-Z0-9_]*'; -var OPTION_NAME_REGEX = new RegExp('^' + OPTION_NAME_PATTERN + '$'); -var OPTION_FLAG_REGEX = new RegExp( - '^--(' + OPTION_NAME_PATTERN + ')(?:=(.*))?$'); - - -function checkOptionName(name, options) { - if ('help' === name) { - throw Error('"help" is a reserved option name'); - } - - if (!OPTION_NAME_REGEX.test(name)) { - throw Error('option ' + JSON.stringify(name) + ' must match ' + - OPTION_NAME_REGEX); - } - - if (name in options) { - throw Error('option ' + JSON.stringify(name) + ' has already been defined'); - } -} - - -function parseOptionValue(name, spec, value) { - try { - return spec.parse(value); - } catch (ex) { - ex.message = 'Invalid value for ' + JSON.stringify('--' + name) + - ': ' + ex.message; - throw ex; - } -} - - -function parseBoolean(value) { - // Empty string if the option was specified without a value; implies true. - if (!value) { - return true; - } - - var tmp = value.toLowerCase(); - if (tmp === 'true' || tmp === '1') { - return true; - } else if (tmp === 'false' || tmp === '0') { - return false; - } - throw Error(JSON.stringify(value) + ' is not a valid boolean value'); -} -parseBoolean['default'] = false; - - -function parseNumber(value) { - if (/^0x\d+$/.test(value)) { - return parseInt(value); - } - - var num = parseFloat(value); - if (isNaN(num) || num != value) { - throw Error(JSON.stringify(value) + ' is not a valid number'); - } - return num; -} -parseNumber['default'] = 0; - - -function parseString(value) { return value; } -parseString['default'] = ''; - - -function parseRegex(value) { return new RegExp(value); } - - - -/** - * A command line option parser. Will automatically parse the command line - * arguments to this program upon accessing the {@code options} or {@code argv} - * properies. The command line will only be re-parsed if this parser's - * configuration has changed since the last access. - * @constructor - */ -function OptionParser() { - - /** @type {string} */ - var usage = OptionParser.DEFAULT_USAGE; - - /** @type {boolean} */ - var mustParse = true; - - /** @type {!Object.<*>} */ - var parsedOptions = {}; - - /** @type {!Array.} */ - var extraArgs = []; - - /** - * Sets the usage string. All occurrences of "$0" will be replaced with the - * current executable's name. - * @param {string} usageStr The new usage string. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.usage = function(usageStr) { - mustParse = true; - usage = usageStr; - return this; - }; - - /** - * @type {!Object.<{ - * help: string, - * parse: function(string): *, - * required: boolean, - * list: boolean, - * callback: function(*), - * default: * - * }>} - */ - var options = {}; - - /** - * Adds a new option to this parser. - * @param {string} name The name of the option. - * @param {function(string): *} parseFn The function to use for parsing the - * option. If this function has a "default" property, it will be used as - * the default value for the option if it was not provided on the command - * line. If not specified, the default value will be undefined. The - * default value may be overridden using the "default" property in the - * option spec. - * @param {Object=} opt_spec The option spec. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.addOption = function(name, parseFn, opt_spec) { - checkOptionName(name, options); - var spec = opt_spec || {}; - - // Quoted notation for "default" to bypass annoying IDE syntax bug. - var defaultValue = spec['default'] || - (!!spec.list ? [] : parseFn['default']); - - options[name] = { - help: spec.help || '', - parse: parseFn, - required: !!spec.required, - list: !!spec.list, - callback: spec.callback || function() {}, - 'default': defaultValue - }; - mustParse = true; - return this; - }; - - /** - * Defines a boolean option. Option values may be one of {'', 'true', 'false', - * '0', '1'}. In the case of the empty string (''), the option value will be - * set to true. - * @param {string} name The name of the option. - * @param {Object=} opt_spec The option spec. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.boolean = function(name, opt_spec) { - return this.addOption(name, parseBoolean, opt_spec); - }; - - /** - * Defines a numeric option. - * @param {string} name The name of the option. - * @param {Object=} opt_spec The option spec. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.number = function(name, opt_spec) { - return this.addOption(name, parseNumber, opt_spec); - }; - - /** - * Defines a path option. Each path will be resolved relative to the - * current working directory, if not absolute. - * @param {string} name The name of the option. - * @param {Object=} opt_spec The option spec. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.path = function(name, opt_spec) { - return this.addOption(name, path.resolve, opt_spec); - }; - - /** - * Defines a basic string option. - * @param {string} name The name of the option. - * @param {Object=} opt_spec The option spec. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.string = function(name, opt_spec) { - return this.addOption(name, parseString, opt_spec); - }; - - /** - * Defines a regular expression based option. - * @param {string} name The name of the option. - * @param {Object=} opt_spec The option spec. - * @return {!OptionParser} A self reference. - * @this {OptionParser} - */ - this.regex = function(name, opt_spec) { - return this.addOption(name, parseRegex, opt_spec); - }; - - /** - * Returns the parsed command line options. - * - *

The command line arguments will be re-parsed if this parser's - * configuration has changed since the last access. - * - * @return {!Object.<*>} The parsed options. - */ - this.__defineGetter__('options', function() { - parse(); - return parsedOptions; - }); - - /** - * Returns the remaining command line arguments after all options have been - * parsed. - * - *

The command line arguments will be re-parsed if this parser's - * configuration has changed since the last access. - * - * @return {!Array.} The remaining command line arguments. - */ - this.__defineGetter__('argv', function() { - parse(); - return extraArgs; - }); - - /** - * Parses a list of arguments. After parsing, the options property of this - * object will contain the value for each parsed flags, and the argv - * property will contain all remaining command line arguments. - * @throws {Error} If the arguments could not be parsed. - */ - this.parse = parse; - - /** - * Returns a formatted help message for this parser. - * @return {string} The help message for this parser. - */ - this.getHelpMsg = getHelpMsg; - - function getHelpMsg() { - return formatHelpMsg(usage, options); - } - - function parse() { - if (!mustParse) { - return; - } - - parsedOptions = {}; - extraArgs = []; - - var args = process.argv.slice(2); - var n = args.length; - - try { - for (var i = 0; i < n; ++i) { - var arg = args[i]; - if (arg === '--') { - extraArgs = args.slice(i + 1); - break; - } - - var match = arg.match(OPTION_FLAG_REGEX); - if (match) { - // Special case --help. - if (match[1] === 'help') { - printHelpAndDie('', 0); - } - parseOption(match); - } else { - extraArgs = args.slice(i + 1); - break; - } - } - } catch (ex) { - printHelpAndDie(ex.message, 1); - } - - for (var name in options) { - var option = options[name]; - if (!(name in parsedOptions)) { - if (option.required) { - printHelpAndDie('Missing required option: --' + name, 1); - } - parsedOptions[name] = option.list ? [] : option['default']; - } - } - - mustParse = false; - } - - function printHelpAndDie(errorMessage, exitCode) { - process.stdout.write(errorMessage + '\n'); - process.stdout.write(getHelpMsg()); - process.exit(exitCode); - } - - function parseOption(match) { - var option = options[match[1]]; - if (!option) { - throw Error(JSON.stringify('--' + match[1]) + ' is not a valid option'); - } - - var value = match[2]; - if (typeof value !== 'undefined') { - value = parseOptionValue(match[1], option, value); - } else if (option.parse === parseBoolean) { - value = true; - } else { - throw Error('Option ' + JSON.stringify('--' + option.name) + - 'requires an operand'); - } - - option.callback(value); - - if (option.list) { - var array = parsedOptions[match[1]] || []; - parsedOptions[match[1]] = array; - array.push(value); - } else { - parsedOptions[match[1]] = value; - } - } -} - - -/** - * The default usage message for an option parser. - */ -Object.defineProperty(OptionParser, 'DEFAULT_USAGE', { - value: 'Usage: $0 [options] [arguments]', - writable: false, - enumerable: true, - configurable: false -}); - - -/** @type {!OptionParser} */ -exports.OptionParser = OptionParser; - -if (module === require.main) { - var parser = new OptionParser(). - boolean('bool_flag', { - help: 'This is a boolean flag' - }). - number('number_flag', { - help: 'This is a number flag' - }). - path('path_flag', { - help: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + - 'Nam pharetra pellentesque augue ut auctor. Mauris eget est ' + - 'vitae quam imperdiet mollis. Ut lorem lorem, commodo et ' + - 'interdum cursus, convallis quis mi. Aenean facilisis adipiscing ' + - 'imperdiet. Etiam tristique facilisis ullamcorper. Vestibulum ' + - 'at mauris quis eros lobortis viverra vel non massa. ' + - 'Aenean eu sodales quam.' - }). - string('the_quick_brown_fox_jumped_over_the_very_lazy_dog', { - help: 'The quick brown fox jumped over the very lazy dog' - }); - - console.log('Options are: '); - for (var key in parser.options) { - console.log(' --' + key + '=' + parser.options[key]); - } - console.log('Args are: '); - console.log(' ', parser.argv.join(' ')); -}