Merge pull request #113 from raphinesse/fix-orgy

Fix orgy
diff --git a/doc/create.txt b/doc/create.txt
new file mode 100644
index 0000000..933034a
--- /dev/null
+++ b/doc/create.txt
@@ -0,0 +1,13 @@
+Create a plugin
+
+Usage: plugman create PARAMETER... [OPTION]...
+
+Parameters:
+  --name <pluginName>           The name of the plugin
+  --plugin_id <pluginID>        An ID for the plugin, ex: org.bar.foo
+  --plugin_version <version>    A version for the plugin, ex: 0.0.1
+
+Options:
+  --path <directory>            An absolute or relative path for the directory
+                                where the plugin project will be created
+  --variable NAME=VALUE         Extra variables such as description or Author
diff --git a/doc/createpackagejson.txt b/doc/createpackagejson.txt
new file mode 100644
index 0000000..0d32061
--- /dev/null
+++ b/doc/createpackagejson.txt
@@ -0,0 +1,3 @@
+Derive a package.json file for a plugin based on its plugin.xml
+
+Usage: plugman createpackagejson <plugin directory>
diff --git a/doc/help.txt b/doc/help.txt
deleted file mode 100644
index 904c7cd..0000000
--- a/doc/help.txt
+++ /dev/null
@@ -1,139 +0,0 @@
-plugman manages plugin.xml-compatible cordova plugins into cordova-generated projects.
-
-Usage
-=====
-
-To display this help file, use one of the following:
-
-$ plugman --help
-$ plugman -h
-
-To display the plugman version, use one of the following:
-
- $ plugman --version
- $ plugman -v 
-
-Optional flags
---------------
-
- --debug|-d    : Verbose mode
- 
-Install a plugin
-----------------
-
-    $ plugman install --platform <platform> --project <directory> --plugin <plugin> [--variable NAME=VALUE]
-
-Parameters:
-
- - platform <platform>: One of android, ios, blackberry10, wp8, or windows8
- - project <directory>: Path reference to a cordova-generated project of the platform you specify
- - plugin <plugin>: One of a path reference to a local copy of a plugin, or a remote https: or git: URL pointing to a cordova plugin (optionally append #branch:subdir) or a plugin ID from http://plugins.cordova.io
- - variable NAME=VALUE: Some plugins require install-time variables to be defined. These could be things like API keys/tokens or other app-specific variables.
-
- Optional parameters:
-
- - www <directory>: www assets for the plugin will be installed into this directory. Default is to install into the standard www directory for the platform specified
- - plugins_dir <directory>: a copy of the plugin will be stored in this directory. Default is to install into the <project directory>/plugins folder
- - searchpath <directory>: when looking up plugins by ID, look in this directory and each of its subdirectories for the plugin before hitting the registry.
-   Multiple search paths can be used by either specifying the flag multiple times, or by separating paths with a delimiter (: on 'nix, ; on Windows).
-
-Uninstall a plugin
-------------------
-
-    $ plugman uninstall --platform <platform> --project <directory> --plugin <plugin-id>
-
-Parameters:
-
- - platform <platform>: One of android, ios, blackberry10, wp8, or windows8
- - project <directory>: Path reference to a cordova-generated project of the platform you specify
- - plugin <plugin-id>: The plugin to remove, identified by its id (see the plugin.xml's <plugin id> attribute)
-
-
-Interacting with the registry
-=============================
-
-NOTICE: The Cordova Plugin registry became read-only, so the following commands have been deprecated and removed:
-
-    $ plugman adduser
-    $ plugman publish
-    $ plugman unpublish
-    $ plugman owner add/rm
-
-For managing plugins for the npm registry, use corresponding npm commands. For more info on npm commands see `npm help <command>`. 
-Learn more about publishing your plugins to npm at http://plugins.cordova.io/npm/developers.html
-
-Search for a plugin
--------------------
-
-    $ plugman search <keyword1 keyword2 ...>
-
-Display plugin information
---------------------------
-
-    $ plugman info <pluginID>
-
-Manage registry configuration
------------------------------
-Display current configuration settings:
-
-    $ plugman config ls
-
-Display the current registry URL:
-
-    $ plugman config get registry
-
-Set registry URL: 
-
-    $ plugman config set registry <url>
-
-Example:
-
-    $ plugman config set registry http://localhost:5984/registry/_design/app/_rewrite
-    
-Manage Owners
--------------
-Plugin owners are allowed to publish updates to a plugin. To display a list of owners for a plugin, use:
-
-    $ plugman owner ls <pluginID>
-
-Example: 
-   
-    $ plugman owner ls org.apache.cordova.core.file
-
-Create A Plugin
----------------
-
-    $ plugman create --name <pluginName> --plugin_id <pluginID> --plugin_version <version> [--path <directory>] [--variable NAME=VALUE]
-
-Parameters:
-
- - <pluginName>: The name of the plugin
- - <pluginID>: An ID for the plugin, ex: org.bar.foo
- - <version>: A version for the plugin, ex: 0.0.1
- - <directory>: An absolute or relative path for the directory where the plugin project will be created
- - variable NAME=VALUE: Extra variables such as description or Author
-
-Add a Package.JSON file to plugin
----------------------------------
-Creates a package.json file in the plugin based on values from plugin.xml.
-
- $ plugman createpackagejson <directory>
-
-
-Add a Platform to a Plugin
---------------------------
-
-    $ plugman platform add --platform_name <platform>
-
-Parameters:
-
-- <platform>: One of android, ios
-
-Remove a Platform from a Plugin
--------------------------------
-
-    $ plugman platform remove --platform_name <platform>
-
-Parameters:
-
-- <platform>: One of android, ios
diff --git a/doc/install.txt b/doc/install.txt
new file mode 100644
index 0000000..60f68fa
--- /dev/null
+++ b/doc/install.txt
@@ -0,0 +1,26 @@
+Install a plugin
+
+Usage: plugman install PARAMETER... [OPTION]...
+
+Parameters:
+  --platform <platform>     The platform for which to install the plugin
+  --project <directory>     Path to a cordova-generated project of <platform>
+  --plugin <plugin>         The spec of the plugin to install. Anything that
+                            could be passed to npm install
+
+Options:
+ --variable NAME=VALUE      Some plugins require install-time variables to be
+                            defined. These could be things like API keys/tokens
+                            or other app-specific variables.
+ --www <directory>          www assets for the plugin will be installed into
+                            this directory. Default is to install into the
+                            standard www directory for the platform specified
+ --plugins_dir <directory>  a copy of the plugin will be stored in this
+                            directory. Default is to install into the
+                            <project directory>/plugins folder
+ --searchpath <directory>   when looking up plugins by ID, look in this
+                            directory and each of its subdirectories for the
+                            plugin before hitting the registry. Multiple search
+                            paths can be used by either specifying the flag
+                            multiple times, or by separating paths with a
+                            delimiter (: on 'nix, ; on Windows).
diff --git a/doc/platform.txt b/doc/platform.txt
new file mode 100644
index 0000000..918e916
--- /dev/null
+++ b/doc/platform.txt
@@ -0,0 +1,4 @@
+Add or remove a platform to a plugin
+
+Usage: plugman platform add --platform_name <platform>
+       plugman platform remove --platform_name <platform>
diff --git a/doc/summary.txt b/doc/summary.txt
new file mode 100644
index 0000000..69527f0
--- /dev/null
+++ b/doc/summary.txt
@@ -0,0 +1,17 @@
+Usage: plugman [OPTION]... <command>
+
+These are the available plugman commands:
+
+Manage plugins in a Cordova project
+    install              Install a plugin into a Cordova project
+    uninstall            Uninstall a plugin from a Cordova project
+
+Author plugins
+    create               Create a plugin
+    createpackagejson    Add a package.json file to a plugin
+    platform             Add and remove platforms from a plugin
+
+Global options:
+    --help, -h           Show help for <command> or this if none was given
+    --debug, -d          Enable verbose output
+    --version, -v        Display the plugman version
diff --git a/doc/uninstall.txt b/doc/uninstall.txt
new file mode 100644
index 0000000..218c6be
--- /dev/null
+++ b/doc/uninstall.txt
@@ -0,0 +1,9 @@
+Uninstall a plugin
+
+Usage plugman uninstall PARAMETER...
+
+Parameters:
+  --platform <platform>    The platform from which to install the plugin
+  --project <directory>    Path to a cordova-generated project of <platform>
+  --plugin <plugin-id>     The ID of the plugin to remove; see attribute 'id'
+                           of element 'plugin' in plugin.xml
diff --git a/main.js b/main.js
index bd73821..1ae9167 100755
--- a/main.js
+++ b/main.js
@@ -20,6 +20,12 @@
 
 // copyright (c) 2013 Andrew Lunny, Adobe Systems
 
+// Register custom fail handler for uncaughtException event
+process.on('uncaughtException', fail);
+
+// On unhandled promise rejection, log it to STDERR and exit with code 1
+require('loud-rejection/register');
+
 const url = require('url');
 const path = require('path');
 
@@ -27,6 +33,7 @@
 
 const pkg = require('./package');
 const help = require('./src/help');
+const commands = require('./src/commands');
 const plugman = require('./plugman');
 const { cordova_platforms } = require('cordova-lib');
 
@@ -48,8 +55,8 @@
     searchpath: [path, Array],
     save: Boolean,
     name: String,
-    platform_id: String,
-    platform_version: String,
+    plugin_id: String,
+    plugin_version: String,
     plugins_dir: String
 };
 const shortHands = { var: ['--variable'], v: ['--version'], h: ['--help'] };
@@ -65,8 +72,6 @@
         path.join(cli_opts.project, 'cordova', 'plugins');
 }
 
-process.on('uncaughtException', fail);
-
 // Set up appropriate logging based on events
 if (cli_opts.debug) {
     plugman.on('verbose', console.log);
@@ -83,18 +88,19 @@
 if (cli_opts.version) {
     console.log(pkg.version);
 } else if (cli_opts.help) {
-    console.log(help());
-} else if (plugman.commands[cmd]) {
-    Promise.resolve(plugman.commands[cmd](cli_opts))
+    console.log(help(commands[cmd] ? cmd : 'summary'));
+} else if (commands[cmd]) {
+    Promise.resolve(commands[cmd](cli_opts))
         .catch(fail);
 } else {
     console.log(help());
+    process.exitCode = 1;
 }
 
 function fail (error) {
     console.error(error.message);
-    if (cli_opts.debug) {
+    if (cli_opts && cli_opts.debug) {
         console.error(error.stack);
     }
-    process.exit(1);
+    process.exitCode = 1;
 }
diff --git a/package.json b/package.json
index acacbd2..7849a52 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,10 @@
   "engineStrict": true,
   "dependencies": {
     "cordova-lib": "^9.0.0",
-    "nopt": "^4.0.1"
+    "loud-rejection": "^1.6.0",
+    "nopt": "^4.0.1",
+    "p-each-series": "^1.0.0",
+    "p-try": "^2.2.0"
   },
   "devDependencies": {
     "eslint": "^5.15.3",
diff --git a/spec/nopt.interface.spec.js b/spec/nopt.interface.spec.js
index bedfcbf..06da67a 100644
--- a/spec/nopt.interface.spec.js
+++ b/spec/nopt.interface.spec.js
@@ -24,10 +24,10 @@
     it('parameters without assignment operator should be assigned', () => {
         const knownOptions = main.__get__('known_opts');
         const shortHands = main.__get__('shortHands');
-        const cli_opts = nopt(knownOptions, shortHands, ['plugman', 'create', '--name', 'MyName', '--platform_id', 'MyId', '--platform_version', '1.0.0']);
+        const cli_opts = nopt(knownOptions, shortHands, ['plugman', 'create', '--name', 'MyName', '--plugin_id', 'MyId', '--plugin_version', '1.0.0']);
 
         expect(cli_opts.name).toEqual('MyName');
-        expect(cli_opts.platform_id).toEqual('MyId');
-        expect(cli_opts.platform_version).toEqual('1.0.0');
+        expect(cli_opts.plugin_id).toEqual('MyId');
+        expect(cli_opts.plugin_version).toEqual('1.0.0');
     });
 });
diff --git a/src/commands.js b/src/commands.js
new file mode 100644
index 0000000..2423706
--- /dev/null
+++ b/src/commands.js
@@ -0,0 +1,107 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF 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.
+*/
+
+// copyright (c) 2013 Andrew Lunny, Adobe Systems
+
+const pTry = require('p-try');
+const pEachSeries = require('p-each-series');
+const { plugman } = require('cordova-lib');
+
+module.exports = {
+    install (cli_opts) {
+        assertRequiredOptions(cli_opts, ['platform', 'project', 'plugin']);
+
+        var opts = {
+            subdir: '.',
+            cli_variables: expandCliVariables(cli_opts.variable),
+            save: cli_opts.save || false,
+            www_dir: cli_opts.www,
+            searchpath: cli_opts.searchpath,
+            link: cli_opts.link,
+            projectRoot: cli_opts.project,
+            force: cli_opts.force || false,
+            nohooks: cli_opts.nohooks || false
+        };
+
+        return pEachSeries(cli_opts.plugin, pluginSrc =>
+            plugman.install(cli_opts.platform, cli_opts.project, pluginSrc, cli_opts.plugins_dir, opts)
+        );
+    },
+
+    uninstall (cli_opts) {
+        assertRequiredOptions(cli_opts, ['platform', 'project', 'plugin']);
+
+        var opts = {
+            www_dir: cli_opts.www,
+            save: cli_opts.save || false,
+            projectRoot: cli_opts.project
+        };
+
+        return pEachSeries(cli_opts.plugin, pluginSrc =>
+            plugman.uninstall(cli_opts.platform, cli_opts.project, pluginSrc, cli_opts.plugins_dir, opts)
+        );
+    },
+
+    create (cli_opts) {
+        assertRequiredOptions(cli_opts, ['name', 'plugin_id', 'plugin_version']);
+
+        const cli_variables = expandCliVariables(cli_opts.variable);
+        return plugman.create(cli_opts.name, cli_opts.plugin_id, cli_opts.plugin_version, cli_opts.path || '.', cli_variables);
+    },
+
+    platform (cli_opts) {
+        assertRequiredOptions(cli_opts, ['platform_name']);
+        var operation = cli_opts.argv.remain[ 0 ] || '';
+        if (operation !== 'add' && operation !== 'remove') {
+            throw new Error(`Operation must be either 'add' or 'remove' but was '${operation}'`);
+        }
+
+        return plugman.platform({ operation: operation, platform_name: cli_opts.platform_name });
+    },
+
+    createpackagejson (cli_opts) {
+        var plugin_path = cli_opts.argv.remain[0];
+        if (!plugin_path) {
+            throw new Error(`Missing required path to plugin`);
+        }
+        return plugman.createpackagejson(plugin_path);
+    }
+};
+
+// Until we can declare all above functions async, wrap them all with pTry
+// to turn all thrown errors into rejections
+for (const key in module.exports) {
+    const fn = module.exports[key];
+    module.exports[key] = (...args) => pTry(fn, ...args);
+}
+
+function assertRequiredOptions (options, requiredKeys) {
+    for (const key of requiredKeys) {
+        if (!options[key]) throw new Error(`Missing required option --${key}`);
+    }
+}
+
+function expandCliVariables (cliVarList) {
+    return (cliVarList || []).reduce((cli_variables, variable) => {
+        var tokens = variable.split('=');
+        var key = tokens.shift().toUpperCase();
+        if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
+        return cli_variables;
+    }, {});
+}
diff --git a/src/help.js b/src/help.js
index f2233b5..05d24c6 100644
--- a/src/help.js
+++ b/src/help.js
@@ -19,8 +19,10 @@
 
 var fs = require('fs');
 var path = require('path');
-var doc_txt = path.join(__dirname, '..', 'doc', 'help.txt');
 
-module.exports = function help () {
-    return fs.readFileSync(doc_txt, 'utf-8');
+const docsDir = path.join(__dirname, '../doc');
+
+module.exports = function help (topic = 'summary') {
+    const doc = path.join(docsDir, topic + '.txt');
+    return fs.readFileSync(doc, 'utf-8');
 };