cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fil...@apache.org
Subject [03/15] git commit: hugeass refactor
Date Mon, 22 Apr 2013 20:18:39 GMT
hugeass refactor


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/5f4bb9d5
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/5f4bb9d5
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/5f4bb9d5

Branch: refs/heads/future
Commit: 5f4bb9d5f80cb654559d660bfef11bdbfa13fff5
Parents: 442e12b
Author: Fil Maj <maj.fil@gmail.com>
Authored: Fri Apr 19 14:59:44 2013 -0700
Committer: Fil Maj <maj.fil@gmail.com>
Committed: Fri Apr 19 14:59:44 2013 -0700

----------------------------------------------------------------------
 main.js                        |   47 +++--
 package.json                   |    4 +-
 platforms/android.js           |  216 --------------------
 platforms/blackberry.js        |  172 ----------------
 platforms/ios.js               |  370 -----------------------------------
 plugman.js                     |  137 +------------
 src/fetch.js                   |   29 +++
 src/install.js                 |   94 +++++++++
 src/platforms.js               |    5 +
 src/platforms/android.js       |  229 ++++++++++++++++++++++
 src/platforms/blackberry.js    |  172 ++++++++++++++++
 src/platforms/ios.js           |  370 +++++++++++++++++++++++++++++++++++
 src/prepare.js                 |  152 ++++++++++++++
 src/remove.js                  |   14 ++
 src/uninstall.js               |   75 +++++++
 src/util/config-changes.js     |   38 ++++
 src/util/fs.js                 |   34 ++++
 src/util/plugins.js            |  119 +++++++++++
 src/util/search-and-replace.js |   37 ++++
 src/util/test-helpers.js       |   26 +++
 src/util/xml-helpers.js        |  175 +++++++++++++++++
 test/android-one-install.js    |   52 ++---
 util/config-changes.js         |   38 ----
 util/fs.js                     |   34 ----
 util/plugin_loader.js          |  152 --------------
 util/plugins.js                |  114 -----------
 util/search-and-replace.js     |   37 ----
 util/test-helpers.js           |   26 ---
 util/xml-helpers.js            |  175 -----------------
 29 files changed, 1623 insertions(+), 1520 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/main.js
----------------------------------------------------------------------
diff --git a/main.js b/main.js
index f33e00b..f3ada41 100755
--- a/main.js
+++ b/main.js
@@ -23,7 +23,7 @@ var path = require('path')
     , url = require('url')
     , package = require(path.join(__dirname, 'package'))
     , nopt = require('nopt')
-    , plugins = require('./util/plugins')
+    , plugins = require('./src/util/plugins')
     , plugman = require('./plugman');
 
 var known_opts = { 'platform' : [ 'ios', 'android', 'blackberry' ]
@@ -55,49 +55,52 @@ if (cli_opts.plugins_dir || cli_opts.project) {
 }
 
 // only dump stack traces in debug mode, otherwise show error message and exit
-if (!cli_opts.debug) {
-    // provide clean output on exceptions rather than dumping a stack trace
-    process.on('uncaughtException', function(error){
+// provide clean output on exceptions rather than dumping a stack trace
+process.on('uncaughtException', function(error){
+    if (cli_opts.debug) {
+        console.error(error.stack);
+    } else {
         console.error(error + '\n');
-        process.exit(1);
-    });
-}
+    }
+    process.exit(1);
+});
 
 if (cli_opts.v) {
     console.log(package.name + ' version ' + package.version);
 }
 else if (cli_opts.list) {
     plugins.listAllPlugins(function(plugins) {
-      for(var i = 0, j = plugins.length ; i < j ; i++) {
-        console.log(plugins[i].value.name, '-', plugins[i].value.description);
-      }
+        for(var i = 0, j = plugins.length ; i < j ; i++) {
+            console.log(plugins[i].value.name, '-', plugins[i].value.description);
+        }
     });
 }
 else if (cli_opts.prepare && cli_opts.project) {
-    plugman.handlePrepare(cli_opts.project, cli_opts.platform, plugins_dir);
+    plugman.prepare(cli_opts.project, cli_opts.platform, plugins_dir);
 }
 else if (cli_opts.remove) {
-    plugman.removePlugin(cli_opts.plugin, plugins_dir);
+    plugman.remove(cli_opts.plugin, plugins_dir);
+    console.log('Plugin ' + cli_opts.plugin + ' deleted.');
 }
 else if (cli_opts.fetch) {
-    plugman.fetchPlugin(cli_opts.plugin, plugins_dir, cli_opts.link);
+    plugman.fetch(cli_opts.plugin, plugins_dir, cli_opts.link);
 }
 else if (!cli_opts.platform || !cli_opts.project || !cli_opts.plugin) {
     printUsage();
 }
 else if (cli_opts.uninstall) {
-    plugman.handlePlugin('uninstall', cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir);
+    plugman.uninstall(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir);
 }
 else {
-  var cli_variables = {}
-  if (cli_opts.variable) {
-    cli_opts.variable.forEach(function (variable) {
-        var tokens = variable.split('=');
-        var key = tokens.shift().toUpperCase();
-        if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
+    var cli_variables = {}
+    if (cli_opts.variable) {
+        cli_opts.variable.forEach(function (variable) {
+            var tokens = variable.split('=');
+            var key = tokens.shift().toUpperCase();
+            if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
         });
-  }
-  plugman.handlePlugin('install', cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, cli_variables);
+    }
+    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, cli_variables);
 }
 
 function printUsage() {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 0264af9..691e829 100644
--- a/package.json
+++ b/package.json
@@ -5,9 +5,9 @@
   "version": "0.5.7",
   "repository": {
     "type": "git",
-    "url": "git://github.com/imhotep/plugman.git"
+    "url": "git://git-wip-us.apache.org/repos/asf/cordova-plugman.git"
   },
-  "main": "main.js",
+  "main": "plugman.js",
   "engines": {
     "node": ">=0.6.7"
   },

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/platforms/android.js
----------------------------------------------------------------------
diff --git a/platforms/android.js b/platforms/android.js
deleted file mode 100644
index 885291a..0000000
--- a/platforms/android.js
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- *
- * Copyright 2013 Anis Kadri
- *
- * Licensed 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.
- *
-*/
-
-var fs = require('fs')  // use existsSync in 0.6.x
-   , path = require('path')
-   , util = require('util')
-   , shell = require('shelljs')
-   , et = require('elementtree')
-   , getConfigChanges = require(path.join(__dirname, '..', 'util', 'config-changes'))
-   , searchAndReplace = require(path.join(__dirname, '..', 'util', 'search-and-replace'))
-   , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'))
-   , assetsDir = 'assets/www'
-   , sourceDir = 'src';
-
-exports.handlePlugin = function (action, project_dir, plugin_dir, plugin_et, variables) {
-    var plugin_id = plugin_et._root.attrib['id']
-      , version = plugin_et._root.attrib['version']
-      , external_hosts = []
-      , i = 0
-      , PACKAGE_NAME = androidPackageName(project_dir)
-
-
-    var platformTag = plugin_et.find('./platform[@name="android"]');
-    if (!platformTag) {
-        // Either this plugin doesn't support this platform, or it's a JS-only plugin.
-        // Either way, return now.
-        return;
-    }
-
-    var sourceFiles = platformTag.findall('./source-file')
-      , libFiles = platformTag.findall('./library-file')
-      , configChanges = getConfigChanges(platformTag);
-
-    variables = variables || {}
-
-	  // get config.xml filename
-	  var config_xml_filename = 'res/xml/config.xml';
-    if(fs.existsSync(path.resolve(project_dir, 'res/xml/plugins.xml'))) {
-        config_xml_filename = 'res/xml/plugins.xml';
-    }
-
-    // collision detection 
-    if(action.match(/force-/) == null) {
-      if(action == "install" && pluginInstalled(plugin_et, project_dir, config_xml_filename)) {
-          throw new Error("Plugin "+plugin_id+" already installed");
-      } else if(action == "uninstall" && !pluginInstalled(plugin_et, project_dir, config_xml_filename)) {
-          throw new Error("Plugin "+plugin_id+" not installed");
-      }
-    } else {
-      action = action.replace('force-', '');
-    }
-
-    root = et.Element("config-file");
-    root.attrib['parent'] = '.'
-      plugin_et.findall('./access').forEach(function (tag) { 
-      root.append(tag);
-    });
-
-    if (root.len()) {
-      (configChanges[config_xml_filename]) ?
-            configChanges[config_xml_filename].push(root) :
-            configChanges[config_xml_filename] = [root];
-    }
-
-    // find which config-files we're interested in
-    Object.keys(configChanges).forEach(function (configFile) {
-        if (!fs.existsSync(path.resolve(project_dir, configFile))) {
-            delete configChanges[configFile];
-        }
-    });
-
-    // move source files
-    sourceFiles.forEach(function (sourceFile) {
-        
-        var srcDir = path.resolve(project_dir,
-                                sourceFile.attrib['target-dir'])
-          , destFile = path.resolve(srcDir,
-                                path.basename(sourceFile.attrib['src']));
-
-        if (action == 'install') {
-            shell.mkdir('-p', srcDir);
-            var srcFile = srcPath(plugin_dir, sourceFile.attrib['src']);
-            shell.cp(srcFile, destFile);
-        } else {
-            fs.unlinkSync(destFile);
-            // check if directory is empty
-            var curDir = srcDir;
-            while(curDir !== path.join(project_dir, 'src')) {
-                if(fs.readdirSync(curDir).length == 0) {
-                    fs.rmdirSync(curDir);
-                    curDir = path.resolve(path.join(curDir, '..'));
-                } else {
-                    // directory not empty...do nothing
-                    break;
-                }
-            }
-        }
-    })
-
-    // move library files
-    libFiles.forEach(function (libFile) {
-        var libDir = path.resolve(project_dir,
-                                libFile.attrib['target-dir'])
-
-        if (action == 'install') {
-            shell.mkdir('-p', libDir);
-            var src = path.resolve(plugin_dir,
-                                        libFile.attrib['src']),
-                dest = path.resolve(libDir,
-                                path.basename(libFile.attrib['src']));
-
-            shell.cp(src, dest);
-        } else {
-            var destFile = path.resolve(libDir,
-                            path.basename(libFile.attrib['src']));
-
-            fs.unlinkSync(destFile);
-            // check if directory is empty
-            var files = fs.readdirSync(libDir);
-            if(files.length == 0) {
-                shell.rm('-rf', libDir);
-            }
-        }
-    })
-
-    // edit configuration files
-    Object.keys(configChanges).forEach(function (filename) {
-        var filepath = path.resolve(project_dir, filename),
-            xmlDoc = xml_helpers.parseElementtreeSync(filepath),
-            output;
-
-        configChanges[filename].forEach(function (configNode) {
-            var selector = configNode.attrib["parent"],
-                children = configNode.findall('*');
-
-            if( action == 'install') {
-                if (!xml_helpers.graftXML(xmlDoc, children, selector)) {
-                    throw new Error('failed to add children to ' + filename);
-                }
-            } else {
-                if (!xml_helpers.pruneXML(xmlDoc, children, selector)) {
-                    throw new Error('failed to remove children from' + filename);
-                }
-            }
-        });
-
-        output = xmlDoc.write({indent: 4});
-        fs.writeFileSync(filepath, output);
-    });
-
-    if (action == 'install') {
-        variables['PACKAGE_NAME'] = androidPackageName(project_dir);
-        searchAndReplace(path.resolve(project_dir, config_xml_filename), variables);
-        searchAndReplace(path.resolve(project_dir, 'AndroidManifest.xml'), variables);
-    }
-
-    // Remove all assets and JS modules installed by this plugin.
-    if (action == 'uninstall') {
-        var assets = plugin_et.findall('./asset');
-        assets && assets.forEach(function(asset) {
-            var target = asset.attrib.target;
-            shell.rm('-rf', path.join(project_dir, assetsDir, target));
-        });
-
-        shell.rm('-rf', path.join(project_dir, assetsDir, 'plugins', plugin_id));
-    }
-}
-
-
-function srcPath(pluginPath, filename) {
-    return path.resolve(pluginPath, filename);
-}
-
-// reads the package name out of the Android Manifest file
-// @param string project_dir the absolute path to the directory containing the project
-// @return string the name of the package
-function androidPackageName(project_dir) {
-    var mDoc = xml_helpers.parseElementtreeSync(
-            path.resolve(project_dir, 'AndroidManifest.xml'));
-
-    return mDoc._root.attrib['package'];
-}
-
-function pluginInstalled(plugin_et, project_dir, config_xml_filename) {
-    var tag_xpath = util.format('./platform[@name="android"]/config-file[@target="%s"]/plugin', config_xml_filename);
-
-    var plugin_tag = plugin_et.find(tag_xpath);
-    if (!plugin_tag) {
-        return false;
-    }
-    var plugin_name = plugin_tag.attrib.name;
-
-    return (fs.readFileSync(path.resolve(project_dir, config_xml_filename), 'utf8')
-           .match(new RegExp(plugin_name, "g")) != null);
-}
-
-exports.www_dir = function(project_dir) {
-    return path.join(project_dir, 'assets', 'www');
-};
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/platforms/blackberry.js
----------------------------------------------------------------------
diff --git a/platforms/blackberry.js b/platforms/blackberry.js
deleted file mode 100644
index 03652b5..0000000
--- a/platforms/blackberry.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- *
- * Copyright 2013 Anis Kadri
- *
- * Licensed 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.
- *
-*/
-
-var fs = require('fs')  // use existsSync in 0.6.x
-   , path = require('path')
-   , shell = require('shelljs')
-   , et = require('elementtree')
-   , getConfigChanges = require('../util/config-changes')
-   , sourceDir = 'src'
-   , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'));
-
-
-exports.handlePlugin = function (action, project_dir, plugin_dir, plugin_et) {
-    var plugin_id = plugin_et._root.attrib['id']
-      , version = plugin_et._root.attrib['version']
-      , external_hosts = []
-      , platformTag = plugin_et.find('./platform[@name="blackberry"]')
-
-    if (!platformTag) {
-        // Either this plugin doesn't support this platform, or it's a JS-only plugin.
-        // Either way, return now.
-        return;
-    }
-
-    var sourceFiles = platformTag.findall('./source-file')
-      , libFiles = platformTag.findall('./library-file')
-      , configChanges = getConfigChanges(platformTag);
-    // find which config-files we're interested in
-    Object.keys(configChanges).forEach(function (configFile) {
-        if (!fs.existsSync(path.resolve(project_dir, configFile))) {
-            delete configChanges[configFile];
-        }
-    });
-
-    // collision detection 
-    if(action.match(/force-/) == null) {
-      if(action == "install" && pluginInstalled(plugin_et, project_dir)) {
-          throw new Error("Plugin "+plugin_id+" already installed");
-      } else if(action == "uninstall" && !pluginInstalled(plugin_et, project_dir)) {
-          throw new Error("Plugin "+plugin_id+" not installed");
-      }
-    } else {
-      action = action.replace('force-', '');
-    }
-    
-    // move source files
-    sourceFiles.forEach(function (sourceFile) {
-        var srcDir = path.resolve(project_dir,
-                                sourceFile.attrib['target-dir'])
-          , destFile = path.resolve(srcDir,
-                                path.basename(sourceFile.attrib['src']));
-
-        if (action == 'install') {
-            shell.mkdir('-p', srcDir);
-            var srcFile = srcPath(plugin_dir, sourceFile.attrib['src']);
-            shell.cp(srcFile, destFile);
-        } else {
-            fs.unlinkSync(destFile);
-            // check if directory is empty
-            var curDir = srcDir;
-            while(curDir !== path.join(project_dir, 'src')) {
-                if(fs.readdirSync(curDir).length == 0) {
-                    fs.rmdirSync(curDir);
-                    curDir = path.resolve(curDir, '..');
-                } else {
-                    // directory not empty...do nothing
-                    break;
-                }
-            }
-        }
-    })
-
-    // move library files
-    libFiles.forEach(function (libFile) {
-        var libDir = path.resolve(project_dir,
-                                libFile.attrib['target-dir'])
-
-        if (action == 'install') {
-            shell.mkdir('-p', libDir);
-
-            var src = path.resolve(plugin_dir,
-                                        libFile.attrib['src']),
-                dest = path.resolve(libDir,
-                                path.basename(libFile.attrib['src']));
-            
-            shell.cp(src, dest);
-        } else {
-            var destFile = path.resolve(libDir,
-                            path.basename(libFile.attrib['src']));
-
-            fs.unlinkSync(destFile);
-            // check if directory is empty
-            var files = fs.readdirSync(libDir);
-            if(files.length == 0) {
-                shell.rm('-rf', libDir);
-            }
-        }
-    })
-
-
-    // edit configuration files
-    Object.keys(configChanges).forEach(function (filename) {
-        var filepath = path.resolve(project_dir, filename),
-            xmlDoc = xml_helpers.parseElementtreeSync(filepath),
-            output;
-
-        configChanges[filename].forEach(function (configNode) {
-            var selector = configNode.attrib["parent"],
-                children = configNode.findall('*');
-
-            if( action == 'install') {
-                if (!xml_helpers.graftXML(xmlDoc, children, selector)) {
-                    throw new Error('failed to add children to ' + filename);
-                }
-            } else {
-                if (!xml_helpers.pruneXML(xmlDoc, children, selector)) {
-                    throw new Error('failed to remove children from' + filename);
-                }
-            }
-        });
-
-        output = xmlDoc.write({indent: 4});
-        fs.writeFileSync(filepath, output);
-    });
-
-    // Remove all assets and JS modules installed by this plugin.
-    if (action == 'uninstall') {
-        var assets = plugin_et.findall('./asset');
-        assets && assets.forEach(function(asset) {
-            var target = asset.attrib.target;
-            shell.rm('-rf', path.join(project_dir, 'www', target));
-        });
-
-        shell.rm('-rf', path.join(project_dir, 'www', 'plugins', plugin_id));
-    }
-}
-
-
-function srcPath(pluginPath, filename) {
-    return path.resolve(pluginPath, filename);
-}
-
-function pluginInstalled(plugin_et, project_dir) {
-    var config_tag = plugin_et.find('./platform[@name="blackberry"]/config-file[@target="config.xml"]/feature');
-    if (!config_tag) {
-        return false;
-    }
-    var plugin_name = config_tag.attrib.id;
-    return (fs.readFileSync(path.resolve(project_dir, 'config.xml'), 'utf8')
-           .match(new RegExp(plugin_name, "g")) != null);
-}
-
-exports.www_dir = function(project_dir) {
-    return path.join(project_dir, 'www');
-};
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/platforms/ios.js
----------------------------------------------------------------------
diff --git a/platforms/ios.js b/platforms/ios.js
deleted file mode 100644
index dda2110..0000000
--- a/platforms/ios.js
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- *
- * Copyright 2013 Anis Kadri
- *
- * Licensed 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.
- *
-*/
-
-var path = require('path')
-  , fs = require('../util/fs')  // use existsSync in 0.6.x
-  , glob = require('glob')
-  , et = require('elementtree')
-  , xcode = require('xcode')
-  , plist = require('plist')
-  , bplist = require('bplist-parser')
-  , shell = require('shelljs')
-  , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'))
-  , searchAndReplace = require(path.join(__dirname, '..', 'util', 'search-and-replace'))
-  , getConfigChanges = require(path.join(__dirname, '..', 'util', 'config-changes'));
-  
-exports.handlePlugin = function (action, project_dir, plugin_dir, plugin_et, variables) {
-    var plugin_id = plugin_et._root.attrib['id']
-      , version = plugin_et._root.attrib['version']
-      , i = 0
-      , matched;
-
-    variables = variables || {}
-
-    var platformTag = plugin_et.find('./platform[@name="ios"]');
-    
-    if (!platformTag) {
-       // Either this plugin doesn't support this platform, or it's a JS-only plugin.
-       // Either way, return now.
-       return;
-    }
-    
-    var sourceFiles = platformTag.findall('./source-file'),
-           headerFiles = platformTag.findall('./header-file'),
-           resourceFiles = platformTag.findall('./resource-file'),
-           frameworks = platformTag.findall('./framework');
-           
-    // grab and parse pbxproj
-    // we don't want CordovaLib's xcode project
-    var project_files = glob.sync(path.join(project_dir, '*.xcodeproj', 'project.pbxproj'));
-    
-    if (!project_files.length) throw new Error("does not appear to be an xcode project (no xcode project file)");
-    var pbxPath = project_files[0];
-
-    var xcodeproj = xcode.project(project_files[0]);
-    xcodeproj.parseSync();
-
-    // grab and parse plist file or config.xml
-    var config_files = (glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist')).length == 0 ? 
-                        glob.sync(path.join(project_dir, '**', 'config.xml')) :
-                        glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist'))
-                       );
-
-    config_files = config_files.filter(function (val) {
-        return !(/^build\//.test(val));
-    });
-
-    if (!config_files.length) {
-        throw new Error("does not appear to be a PhoneGap project");
-    }
-
-    var config_file = config_files[0];
-    var xcode_dir = path.dirname(config_file);
-    var pluginsDir = path.resolve(xcode_dir, 'Plugins');
-    var resourcesDir = path.resolve(xcode_dir, 'Resources');
-    
-    // get project plist for package name
-    var project_plists = glob.sync(xcode_dir + '/*-Info.plist');
-    var projectPListPath = project_plists[0];
-    
-    // collision detection 
-    if(action.match(/force-/) == null) {
-      if(action == "install" && pluginInstalled(plugin_et, config_file)) {
-          throw new Error("Plugin "+plugin_id+" already installed");
-      } else if(action == "uninstall" && !pluginInstalled(plugin_et, config_file)) {
-          throw new Error("Plugin "+plugin_id+" not installed");
-      }
-    } else {
-      action = action.replace('force-', '');
-    }
-
-    // move native files (source/header/resource)
-    sourceFiles && sourceFiles.forEach(function (sourceFile) {
-        var src = sourceFile.attrib['src'],
-            srcFile = path.resolve(plugin_dir, src),
-            targetDir = path.resolve(pluginsDir, getRelativeDir(sourceFile)),
-            destFile = path.resolve(targetDir, path.basename(src));
-         
-        if (action == 'install') {
-            xcodeproj.addSourceFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
-            shell.mkdir('-p', targetDir);
-            checkLastCommand();
-            shell.cp(srcFile, destFile);
-            checkLastCommand();
-        } else {
-            xcodeproj.removeSourceFile(path.join('Plugins', path.basename(src)));
-            if(fs.existsSync(destFile))
-                fs.unlinkSync(destFile);
-            shell.rm('-rf', targetDir);    
-            checkLastCommand();
-        }
-    });
-
-    headerFiles && headerFiles.forEach(function (headerFile) {
-        var src = headerFile.attrib['src'],
-            srcFile = path.resolve(plugin_dir, src),
-            targetDir = path.resolve(pluginsDir, getRelativeDir(headerFile)),
-            destFile = path.resolve(targetDir, path.basename(src));
-         
-        if (action == 'install') {     
-            xcodeproj.addHeaderFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
-            shell.mkdir('-p', targetDir);
-            checkLastCommand();
-            shell.cp(srcFile, destFile);
-            checkLastCommand();
-        } else {
-            xcodeproj.removeHeaderFile(path.join('Plugins', path.basename(src)));
-            if(fs.existsSync(destFile))
-                fs.unlinkSync(destFile);
-            shell.rm('-rf', targetDir);
-            checkLastCommand();
-        }
-    });
-
-    resourceFiles && resourceFiles.forEach(function (resource) {
-        var src = resource.attrib['src'],
-            srcFile = path.resolve(plugin_dir, src),
-            destFile = path.resolve(resourcesDir, path.basename(src));
-
-        if (action == 'install') {
-            xcodeproj.addResourceFile(path.join('Resources', path.basename(src)));
-            var st = fs.statSync(srcFile);
-            shell.mkdir('-p', resourcesDir);
-            if (st.isDirectory()) {
-                shell.cp('-R', srcFile, resourcesDir);
-                checkLastCommand();
-            } else {
-                shell.cp(srcFile, destFile);
-                checkLastCommand();
-            }
-        } else {
-            xcodeproj.removeResourceFile(path.join('Resources', path.basename(src)));
-            shell.rm('-rf', destFile);
-            checkLastCommand();
-        }
-    });
-
-    frameworks && frameworks.forEach(function (framework) {
-        var src = framework.attrib['src'],
-            weak = framework.attrib['weak'];
-        if (action == 'install') {
-            var opt = { weak: (weak && weak.toLowerCase() == 'true') };
-            xcodeproj.addFramework(src, opt);
-        } else {
-            xcodeproj.removeFramework(src);
-        }
-    });
-
-    // write out xcodeproj file
-    fs.writeFileSync(pbxPath, xcodeproj.writeSync());
-
-    // add plugin and whitelisted hosts
-    try {
-      updateConfig(action, config_file, plugin_et);
-    } catch(e) {
-      throw {
-        name: "ConfigurationError",
-        level: "ERROR",
-        message: "Error updating configuration: "+e.message,
-      }
-    }
-    
-    if (action == 'install') {
-        variables['PACKAGE_NAME'] = plist.parseFileSync(projectPListPath).CFBundleIdentifier;
-        searchAndReplace(pbxPath, variables);
-        searchAndReplace(projectPListPath, variables);
-        searchAndReplace(config_file, variables);
-    }
-
-    // Remove all assets and JS modules installed by this plugin.
-    if (action == 'uninstall') {
-        var assets = plugin_et.findall('./asset');
-        assets && assets.forEach(function(asset) {
-            var target = asset.attrib.target;
-            shell.rm('-rf', path.join(project_dir, 'www', target));
-        });
-
-        shell.rm('-rf', path.join(project_dir, 'www', 'plugins', plugin_id));
-    }
-
-}
-
-function getRelativeDir(file) {
-    var targetDir = file.attrib['target-dir'],
-        preserveDirs = file.attrib['preserve-dirs'];
-
-    if (preserveDirs && preserveDirs.toLowerCase() == 'true') {
-        return path.dirname(file.attrib['src']);
-    } else if (targetDir) {
-        return targetDir;
-    } else {
-        return '';
-    }
-}
-
-// determine if a plist file is binary
-function isBinaryPlist(filename) {
-    // I wish there was a synchronous way to read only the first 6 bytes of a
-    // file. This is wasteful :/ 
-    var buf = '' + fs.readFileSync(filename, 'utf8');
-    // binary plists start with a magic header, "bplist"
-    return buf.substring(0, 6) === 'bplist';
-}
-
-function updatePlistFile(action, config_path, plugin_et) {
-    var hosts = plugin_et.findall('./access'),
-        platformTag = plugin_et.find('./platform[@name="ios"]'), // FIXME: can probably do better than this
-        plistEle = platformTag.find('./plugins-plist'),
-        external_hosts = [];
-
-    // determine if this is a binary or ascii plist and choose the parser
-    // this is temporary until binary support is added to node-plist
-    var pl = (isBinaryPlist(config_path) ? bplist : plist);
-
-    var plistObj = pl.parseFileSync(config_path);
-    
-    if (action == 'install') {
-        // add hosts to whitelist (ExternalHosts) in plist
-        hosts && hosts.forEach(function(host) {
-            plistObj.ExternalHosts.push(host.attrib['origin']);
-        });
-
-        // add plugin to plist
-        plistObj.Plugins[plistEle.attrib['key']] = plistEle.attrib['string'];
-    } else {
-        // remove hosts from whitelist (ExternalHosts) in plist
-        // check each entry in external hosts, only add it to the plist if
-        // it's not an entry added by this plugin 
-        for(i=0; i < plistObj.ExternalHosts.length;i++) {
-            matched = false;
-            hosts && hosts.forEach(function(host) {
-                if(host === plistObj.ExternalHosts[i]) {
-                    matched = true;
-                }
-            });
-            if (!matched) {
-                external_hosts.push(plistObj.ExternalHosts[i]);
-            }
-        }
-
-        // filtered the external hosts entries out, copy result
-        plistObj.ExternalHosts = external_hosts;
-
-        delete plistObj.Plugins[plistEle.attrib['key']];
-    }
-    
-    // write out plist
-    fs.writeFileSync(config_path, plist.build(plistObj));
-}
-
-function pluginInstalled(plugin_et, config_path) {
-    var config_tag = plugin_et.find('./platform[@name="ios"]/config-file[@target="config.xml"]/plugin') ||
-                     plugin_et.find('./platform[@name="ios"]/plugins-plist');
-    if (!config_tag) {
-        return false;
-    }
-    var plugin_name = config_tag.attrib.name || config_tag.attrib.key;
-    return (fs.readFileSync(config_path, 'utf8').match(new RegExp(plugin_name, "g")) != null);
-}
-
-function updateConfigXml(action, config_path, plugin_et) {
-    var hosts = plugin_et.findall('./access'),
-        platformTag = plugin_et.find('./platform[@name="ios"]'), // FIXME: can probably do better than this
-        plistEle = platformTag.find('./plugins-plist'), // use this for older that have plugins-plist
-        configChanges = getConfigChanges(platformTag),
-        base_config_path = path.basename(config_path);
-
-    // edit configuration files
-    var xmlDoc = xml_helpers.parseElementtreeSync(config_path),
-        output,
-		    pListOnly = plistEle;
-    
-    if (configChanges[path.basename(config_path)]) {	
-      configChanges[path.basename(config_path)].forEach(function (val) {
-        if (val.find("plugin")) pListOnly = false;
-      });
-    }
-
-    if (pListOnly) {
-        // if the plugin supports the old plugins-plist element only
-        var name = plistEle.attrib.key;
-        var value = plistEle.attrib.string;
-        var pluginsEl = xmlDoc.find('plugins');
-        if ( action == 'install') {
-            var new_plugin = new et.Element('plugin');
-            new_plugin.attrib.name = name;
-            new_plugin.attrib.value = value;
-            pluginsEl.append(new_plugin);
-        } else {
-            var culprit = pluginsEl.find("plugin[@name='"+name+"']");
-            pluginsEl.remove(0, culprit);
-        }
-    }
-
-    // add whitelist hosts
-    root = et.Element("config-file");
-    root.attrib['parent'] = '.'
-      hosts.forEach(function (tag) {
-      root.append(tag);
-    });
-
-    if (root.len()) {
-      (configChanges[path.basename(config_path)]) ?
-            configChanges[path.basename(config_path)].push(root) :
-            configChanges[path.basename(config_path)] = [root];
-    }
-
-    if (configChanges[path.basename(config_path)]) {
-
-        configChanges[base_config_path].forEach(function (configNode) {
-            var selector = configNode.attrib["parent"],
-                children = configNode.findall('*');
-            if( action == 'install') {
-                if (!xml_helpers.graftXML(xmlDoc, children, selector)) {
-                    throw new Error('failed to add children to ' + selector + ' in ' + config_path);
-                }
-            } else {
-                if (!xml_helpers.pruneXML(xmlDoc, children, selector)) {
-                    throw new Error('failed to remove children from ' + selector + ' in ' + config_path);
-                }
-            }
-      });
-    }
-
-    output = xmlDoc.write({indent: 4});
-    fs.writeFileSync(config_path, output);
-}
-
-// updates plist file and/or config.xml
-function updateConfig(action, config_path, plugin_et) {
-    if(path.basename(config_path) == "config.xml") {
-        updateConfigXml(action, config_path, plugin_et);
-    } else {
-        updatePlistFile(action, config_path, plugin_et);
-    }
-}
-// throws error if last command returns code != 0
-function checkLastCommand() {
-    if(shell.error() != null) throw {name: "ShellError", message: shell.error()};
-}
-
-exports.www_dir = function(project_dir) {
-    return path.join(project_dir, 'www');
-};
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/plugman.js
----------------------------------------------------------------------
diff --git a/plugman.js b/plugman.js
index a5bfb82..5ab22a2 100755
--- a/plugman.js
+++ b/plugman.js
@@ -18,136 +18,11 @@
 */
 
 // copyright (c) 2013 Andrew Lunny, Adobe Systems
-var fs = require('fs')
-  , path = require('path')
-  , url = require('url')
-  , package = require(path.join(__dirname, 'package'))
-  , et = require('elementtree')
-  , plugins = require('./util/plugins')
-  , shell = require('shelljs')
-  , plugin_loader = require('./util/plugin_loader')
-  , platform_modules = {
-        'android': require('./platforms/android'),
-        'ios': require('./platforms/ios'),
-        'blackberry': require('./platforms/blackberry')
-    };
 
-
-function execAction(action, platform, project_dir, plugin_dir, plugins_dir, cli_variables) {
-    var xml_path     = path.join(plugin_dir, 'plugin.xml')
-      , xml_text     = fs.readFileSync(xml_path, 'utf-8')
-      , plugin_et    = new et.ElementTree(et.XML(xml_text))
-      , filtered_variables = {};
-
-    if (action == 'install') {
-        // checking preferences
-        prefs = plugin_et.findall('./preference') || [];
-        prefs = prefs.concat(plugin_et.findall('./platform[@name="'+platform+'"]/preference'));
-        var missing_vars = [];
-        prefs.forEach(function (pref) {
-            var key = pref.attrib["name"].toUpperCase();
-            if (cli_variables[key] == undefined)
-                missing_vars.push(key)
-            else
-                filtered_variables[key] = cli_variables[key]
-        })
-        if (missing_vars.length > 0) {
-            console.error('Variable missing: ' + missing_vars.join(", "));
-            return;
-        }
-
-        if((info = plugin_et.find('./platform[@name="'+platform+'"]/info'))) {
-            console.log(info.text);
-        }
-    }
-
-    // run the platform-specific function
-    try {
-      platform_modules[platform].handlePlugin(action, project_dir, plugin_dir, plugin_et, filtered_variables);
-      exports.handlePrepare(project_dir, platform, plugins_dir);
-      console.log('plugin ' + action + 'ed');
-    } catch(e) {
-        var revert = (action == "install" ? "force-uninstall" : "force-install" );
-        console.error("An error occurred for action", action, ":", e.message, "\nTrying to revert changes...");
-        try {
-          platform_modules[platform].handlePlugin(revert, project_dir, plugin_dir, plugin_et, filtered_variables);
-        } catch(e) {
-          console.log("Changes might have not been reverted: "+e.message);
-        }
-    }
-}
-
-exports.fetchPlugin = function(plugin_dir, plugins_dir, link) {
-    // Ensure the containing directory exists.
-    shell.mkdir('-p', plugins_dir);
-
-    // clone from git repository
-    if(plugin_dir.indexOf('https://') == 0 || plugin_dir.indexOf('git://') == 0) {
-        if (link) {
-            throw new Error('--link is not supported for git URLs');
-        }
-        plugin_dir = plugins.clonePluginGitRepo(plugin_dir, plugins_dir);
-    } else { // Copy from the local filesystem.
-        var dest = path.join(plugins_dir, path.basename(dest));
-
-        shell.rm('-rf', dest);
-        if (link) {
-            fs.symlinkSync(path.resolve(plugin_dir), dest, 'dir');
-        } else {
-            shell.cp('-R', plugin_dir, plugins_dir); // Yes, not dest.
-        }
-
-        plugin_dir = dest;
-    }
-};
-
-exports.removePlugin = function(name, plugins_dir) {
-    var target = path.join(plugins_dir, name);
-    var stat = fs.lstatSync(target);
-
-    if (stat.isSymbolicLink()) {
-        fs.unlinkSync(target);
-    } else {
-        shell.rm('-rf', target);
-    }
-    console.log('Plugin ' + name + ' deleted.');
-};
-
-exports.handlePlugin = function(action, platform, project_dir, name, plugins_dir, cli_variables) {
-    var plugin_xml_path, async;
-
-    // Check that the plugin has already been fetched.
-    var plugin_dir = path.join(plugins_dir, name);
-    if (!fs.existsSync(plugin_dir)) {
-        throw new Error('Plugin ' + name + ' not found. You may need to --fetch it.');
-    }
-
-    plugin_xml_path = path.join(plugin_dir, 'plugin.xml');
-    if (!fs.existsSync(plugin_xml_path)) {
-        // try querying the plugin database
-        async = true;
-        plugins.getPluginInfo(plugin_dir,
-            function(plugin_info) {
-                execAction(action, platform, project_dir, plugins.clonePluginGitRepo(plugin_info.url), plugins_dir, cli_variables);
-            },
-            function(e) {
-                throw new Error(action + ' failed. "' + plugin_xml_path + '" not found');
-            }
-        );
-    }
-
-    if (!platform_modules[platform]) {
-        throw { name: "Platform Error", message: platform + " not supported" }
-    }
-
-    // check arguments and resolve file paths
-    if(!async) {
-        execAction(action, platform, project_dir, plugin_dir, plugins_dir, cli_variables);
-    }
+module.exports = {
+    install:  require('./src/install'),
+    uninstall:require('./src/uninstall'),
+    remove:   require('./src/remove'),
+    fetch:    require('./src/fetch'),
+    prepare:  require('./src/prepare')
 };
-
-exports.handlePrepare = function(project_dir, platform, plugins_dir) {
-    var www_dir = platform_modules[platform].www_dir(project_dir);
-    plugin_loader.handlePrepare(project_dir, plugins_dir, www_dir, platform);
-};
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/fetch.js
----------------------------------------------------------------------
diff --git a/src/fetch.js b/src/fetch.js
new file mode 100644
index 0000000..1cbdaca
--- /dev/null
+++ b/src/fetch.js
@@ -0,0 +1,29 @@
+var shell   = require('shelljs'),
+    fs      = require('fs'),
+    plugins = require('./util/plugins'),
+    path    = require('path');
+
+module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, callback) {
+    // Ensure the containing directory exists.
+    shell.mkdir('-p', plugins_dir);
+
+    // clone from git repository
+    if(plugin_dir.indexOf('https://') == 0 || plugin_dir.indexOf('git://') == 0) {
+        if (link) {
+            throw new Error('--link is not supported for git URLs');
+        }
+        plugins.clonePluginGitRepo(plugin_dir, plugins_dir, callback);
+    } else { // Copy from the local filesystem.
+        var dest = path.join(plugins_dir, path.basename(dest));
+
+        shell.rm('-rf', dest);
+        if (link) {
+            fs.symlinkSync(path.resolve(plugin_dir), dest, 'dir');
+        } else {
+            shell.cp('-R', plugin_dir, plugins_dir); // Yes, not dest.
+        }
+
+        plugin_dir = dest;
+        if (callback) callback(null);
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
new file mode 100644
index 0000000..d246b15
--- /dev/null
+++ b/src/install.js
@@ -0,0 +1,94 @@
+var path = require('path'),
+    fs   = require('fs'),
+    et   = require('elementtree'),
+    platform_modules = require('./platforms');
+
+function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, callback) {
+    var xml_path     = path.join(plugin_dir, 'plugin.xml')
+      , xml_text     = fs.readFileSync(xml_path, 'utf-8')
+      , plugin_et    = new et.ElementTree(et.XML(xml_text))
+      , filtered_variables = {}
+      , info;
+
+    // checking preferences
+    prefs = plugin_et.findall('./preference') || [];
+    prefs = prefs.concat(plugin_et.findall('./platform[@name="'+platform+'"]/preference'));
+    var missing_vars = [];
+    prefs.forEach(function (pref) {
+        var key = pref.attrib["name"].toUpperCase();
+        if (cli_variables[key] == undefined)
+            missing_vars.push(key)
+        else
+            filtered_variables[key] = cli_variables[key]
+    })
+    if (missing_vars.length > 0) {
+        var err = new Error('Variable(s) missing: ' + missing_vars.join(", "));
+        if (callback) {
+            callback(err);
+            return;
+        }
+        else throw err;
+    }
+
+    // Log out plugin INFO element contents in case additional install steps are necessary
+    if((info = plugin_et.find('./platform[@name="'+platform+'"]/info'))) {
+        console.log(info.text);
+    }
+
+    // run the platform-specific function
+    try {
+        platform_modules[platform].handleInstall(project_dir, plugin_dir, plugin_et, filtered_variables);
+        require('./../plugman').prepare(project_dir, platform, plugins_dir);
+        if (callback) callback();
+    } catch(e) {
+        var err;
+        try {
+            platform_modules[platform].forceUninstall(project_dir, plugin_dir, plugin_et, filtered_variables);
+            err = new Error('Error during installation of plugin, reverted all changes. Install error: ' + e.message + '\n' + e.stack + '\n----');
+        } catch(etwo) {
+            err = new Error('Error during installation of plugin, reverting changes also caused issues! Reversion probably incomplete. Install error: ' + e.message + '\n' + e.stack + ', reversion error: ' + etwo.message + '\n' + etwo.stack + '\n----');
+        }
+        if (callback) callback(err);
+        else throw err;
+    }
+}
+
+module.exports = function installPlugin(platform, project_dir, name, plugins_dir, cli_variables, callback) {
+    if (!platform_modules[platform]) {
+        var err = new Error(platform + " not supported.");
+        if (callback) { 
+            callback(err);
+            return;
+        }
+        else throw err;
+    }
+
+    var plugin_dir = path.join(plugins_dir, name);
+    var plugin_xml_path = path.join(plugin_dir, 'plugin.xml');
+
+    // Check that the plugin has already been fetched.
+    if (!fs.existsSync(plugin_dir) || !fs.existsSync(plugin_xml_path)) {
+        // try querying the plugin database
+        plugins.getPluginInfo(plugin_dir,
+            function(err, plugin_info) {
+                if (err) {
+                    var err_obj = new Error('install failed. plugin.xml at "' + plugin_xml_path + '" not found and couldnt query remote server for information about the plugin because ' + err.message);
+                    if (callback) callback(err_obj);
+                    else throw err_obj;
+                } else {
+                    plugins.clonePluginGitRepo(plugin_info.url, plugins_dir, function(err, plugin_dir) {
+                        if (err) {
+                            var err_obj = new Error('install failed. plugin.xml at "' + plugin_xml_path + '" not found and couldnt clone plugin repo because ' + err.message);
+                            if (callback) callback(err_obj);
+                            else throw err_obj;
+                        } else {
+                            runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, callback);
+                        }
+                    });
+                }
+            }
+        );
+    } else {
+        runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, callback);
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/platforms.js
----------------------------------------------------------------------
diff --git a/src/platforms.js b/src/platforms.js
new file mode 100644
index 0000000..4ac29e3
--- /dev/null
+++ b/src/platforms.js
@@ -0,0 +1,5 @@
+module.exports = {
+    'android': require('./platforms/android'),
+    'ios': require('./platforms/ios'),
+    'blackberry': require('./platforms/blackberry')
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/platforms/android.js
----------------------------------------------------------------------
diff --git a/src/platforms/android.js b/src/platforms/android.js
new file mode 100644
index 0000000..ac5bd3a
--- /dev/null
+++ b/src/platforms/android.js
@@ -0,0 +1,229 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed 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.
+ *
+*/
+
+var fs = require('fs')  // use existsSync in 0.6.x
+   , path = require('path')
+   , util = require('util')
+   , shell = require('shelljs')
+   , et = require('elementtree')
+   , getConfigChanges = require(path.join(__dirname, '..', 'util', 'config-changes'))
+   , searchAndReplace = require(path.join(__dirname, '..', 'util', 'search-and-replace'))
+   , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'))
+   , assetsDir = path.join('assets','www')
+   , sourceDir = 'src';
+
+module.exports = {
+    handleInstall:function(project_dir, plugin_dir, plugin_et, variables) {
+        handlePlugin('install', project_dir, plugin_dir, plugin_et, variables);
+    },
+    handleUninstall:function(project_dir, plugin_dir, plugin_et, variables) {
+        handlePlugin('uninstall', project_dir, plugin_dir, plugin_et, variables);
+    },
+    forceInstall:function(project_dir, plugin_dir, plugin_et, variables) {
+        handlePlugin('force-install', project_dir, plugin_dir, plugin_et, variables);
+    },
+    forceUninstall:function(project_dir, plugin_dir, plugin_et, variables) {
+        handlePlugin('force-uninstall', project_dir, plugin_dir, plugin_et, variables);
+    },
+    www_dir:function(project_dir) {
+        return path.join(project_dir, 'assets', 'www');
+    }
+};
+
+function handlePlugin(action, project_dir, plugin_dir, plugin_et, variables) {
+    var plugin_id = plugin_et._root.attrib['id']
+      , version = plugin_et._root.attrib['version']
+      , external_hosts = []
+      , i = 0
+      , PACKAGE_NAME = androidPackageName(project_dir)
+
+
+    var platformTag = plugin_et.find('./platform[@name="android"]');
+    if (!platformTag) {
+        // Either this plugin doesn't support this platform, or it's a JS-only plugin.
+        // Either way, return now.
+        return;
+    }
+
+    var sourceFiles = platformTag.findall('./source-file')
+      , libFiles = platformTag.findall('./library-file')
+      , configChanges = getConfigChanges(platformTag);
+
+    variables = variables || {}
+
+	  // get config.xml filename
+	var config_xml_filename = 'res/xml/config.xml';
+    if(fs.existsSync(path.resolve(project_dir, 'res/xml/plugins.xml'))) {
+        config_xml_filename = 'res/xml/plugins.xml';
+    }
+
+    // collision detection 
+    if(action.match(/force-/) == null) {
+        if(action == "install" && pluginInstalled(plugin_et, project_dir, config_xml_filename)) {
+            throw new Error("Plugin "+plugin_id+" already installed");
+        } else if(action == "uninstall" && !pluginInstalled(plugin_et, project_dir, config_xml_filename)) {
+            throw new Error("Plugin "+plugin_id+" not installed");
+        }
+    } else {
+        action = action.replace('force-', '');
+    }
+
+    root = et.Element("config-file");
+    root.attrib['parent'] = '.'
+        plugin_et.findall('./access').forEach(function (tag) { 
+        root.append(tag);
+    });
+
+    if (root.len()) {
+        (configChanges[config_xml_filename]) ?
+            configChanges[config_xml_filename].push(root) :
+            configChanges[config_xml_filename] = [root];
+    }
+
+    // find which config-files we're interested in
+    Object.keys(configChanges).forEach(function (configFile) {
+        if (!fs.existsSync(path.resolve(project_dir, configFile))) {
+            delete configChanges[configFile];
+        }
+    });
+
+    // move source files
+    sourceFiles.forEach(function (sourceFile) {
+        
+        var srcDir = path.resolve(project_dir,
+                                sourceFile.attrib['target-dir'])
+          , destFile = path.resolve(srcDir,
+                                path.basename(sourceFile.attrib['src']));
+
+        if (action == 'install') {
+            shell.mkdir('-p', srcDir);
+            var srcFile = srcPath(plugin_dir, sourceFile.attrib['src']);
+            shell.cp(srcFile, destFile);
+        } else {
+            fs.unlinkSync(destFile);
+            // check if directory is empty
+            var curDir = srcDir;
+            while(curDir !== path.join(project_dir, 'src')) {
+                if(fs.readdirSync(curDir).length == 0) {
+                    fs.rmdirSync(curDir);
+                    curDir = path.resolve(path.join(curDir, '..'));
+                } else {
+                    // directory not empty...do nothing
+                    break;
+                }
+            }
+        }
+    })
+
+    // move library files
+    libFiles.forEach(function (libFile) {
+        var libDir = path.resolve(project_dir,
+                                libFile.attrib['target-dir'])
+
+        if (action == 'install') {
+            shell.mkdir('-p', libDir);
+            var src = path.resolve(plugin_dir,
+                                        libFile.attrib['src']),
+                dest = path.resolve(libDir,
+                                path.basename(libFile.attrib['src']));
+
+            shell.cp(src, dest);
+        } else {
+            var destFile = path.resolve(libDir,
+                            path.basename(libFile.attrib['src']));
+
+            fs.unlinkSync(destFile);
+            // check if directory is empty
+            var files = fs.readdirSync(libDir);
+            if(files.length == 0) {
+                shell.rm('-rf', libDir);
+            }
+        }
+    })
+
+    // edit configuration files
+    Object.keys(configChanges).forEach(function (filename) {
+        var filepath = path.resolve(project_dir, filename),
+            xmlDoc = xml_helpers.parseElementtreeSync(filepath),
+            output;
+
+        configChanges[filename].forEach(function (configNode) {
+            var selector = configNode.attrib["parent"],
+                children = configNode.findall('*');
+
+            if( action == 'install') {
+                if (!xml_helpers.graftXML(xmlDoc, children, selector)) {
+                    throw new Error('failed to add children to ' + filename);
+                }
+            } else {
+                if (!xml_helpers.pruneXML(xmlDoc, children, selector)) {
+                    throw new Error('failed to remove children from' + filename);
+                }
+            }
+        });
+
+        output = xmlDoc.write({indent: 4});
+        fs.writeFileSync(filepath, output);
+    });
+
+    if (action == 'install') {
+        variables['PACKAGE_NAME'] = androidPackageName(project_dir);
+        searchAndReplace(path.resolve(project_dir, config_xml_filename), variables);
+        searchAndReplace(path.resolve(project_dir, 'AndroidManifest.xml'), variables);
+    }
+
+    // Remove all assets and JS modules installed by this plugin.
+    if (action == 'uninstall') {
+        var assets = plugin_et.findall('./asset');
+        assets && assets.forEach(function(asset) {
+            var target = asset.attrib.target;
+            shell.rm('-rf', path.join(project_dir, assetsDir, target));
+        });
+
+        shell.rm('-rf', path.join(project_dir, assetsDir, 'plugins', plugin_id));
+    }
+}
+
+
+function srcPath(pluginPath, filename) {
+    return path.resolve(pluginPath, filename);
+}
+
+// reads the package name out of the Android Manifest file
+// @param string project_dir the absolute path to the directory containing the project
+// @return string the name of the package
+function androidPackageName(project_dir) {
+    var mDoc = xml_helpers.parseElementtreeSync(
+            path.resolve(project_dir, 'AndroidManifest.xml'));
+
+    return mDoc._root.attrib['package'];
+}
+
+function pluginInstalled(plugin_et, project_dir, config_xml_filename) {
+    var tag_xpath = util.format('./platform[@name="android"]/config-file[@target="%s"]/plugin', config_xml_filename);
+
+    var plugin_tag = plugin_et.find(tag_xpath);
+    if (!plugin_tag) {
+        return false;
+    }
+    var plugin_name = plugin_tag.attrib.name;
+
+    return (fs.readFileSync(path.resolve(project_dir, config_xml_filename), 'utf8')
+           .match(new RegExp(plugin_name, "g")) != null);
+}

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/platforms/blackberry.js
----------------------------------------------------------------------
diff --git a/src/platforms/blackberry.js b/src/platforms/blackberry.js
new file mode 100644
index 0000000..03652b5
--- /dev/null
+++ b/src/platforms/blackberry.js
@@ -0,0 +1,172 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed 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.
+ *
+*/
+
+var fs = require('fs')  // use existsSync in 0.6.x
+   , path = require('path')
+   , shell = require('shelljs')
+   , et = require('elementtree')
+   , getConfigChanges = require('../util/config-changes')
+   , sourceDir = 'src'
+   , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'));
+
+
+exports.handlePlugin = function (action, project_dir, plugin_dir, plugin_et) {
+    var plugin_id = plugin_et._root.attrib['id']
+      , version = plugin_et._root.attrib['version']
+      , external_hosts = []
+      , platformTag = plugin_et.find('./platform[@name="blackberry"]')
+
+    if (!platformTag) {
+        // Either this plugin doesn't support this platform, or it's a JS-only plugin.
+        // Either way, return now.
+        return;
+    }
+
+    var sourceFiles = platformTag.findall('./source-file')
+      , libFiles = platformTag.findall('./library-file')
+      , configChanges = getConfigChanges(platformTag);
+    // find which config-files we're interested in
+    Object.keys(configChanges).forEach(function (configFile) {
+        if (!fs.existsSync(path.resolve(project_dir, configFile))) {
+            delete configChanges[configFile];
+        }
+    });
+
+    // collision detection 
+    if(action.match(/force-/) == null) {
+      if(action == "install" && pluginInstalled(plugin_et, project_dir)) {
+          throw new Error("Plugin "+plugin_id+" already installed");
+      } else if(action == "uninstall" && !pluginInstalled(plugin_et, project_dir)) {
+          throw new Error("Plugin "+plugin_id+" not installed");
+      }
+    } else {
+      action = action.replace('force-', '');
+    }
+    
+    // move source files
+    sourceFiles.forEach(function (sourceFile) {
+        var srcDir = path.resolve(project_dir,
+                                sourceFile.attrib['target-dir'])
+          , destFile = path.resolve(srcDir,
+                                path.basename(sourceFile.attrib['src']));
+
+        if (action == 'install') {
+            shell.mkdir('-p', srcDir);
+            var srcFile = srcPath(plugin_dir, sourceFile.attrib['src']);
+            shell.cp(srcFile, destFile);
+        } else {
+            fs.unlinkSync(destFile);
+            // check if directory is empty
+            var curDir = srcDir;
+            while(curDir !== path.join(project_dir, 'src')) {
+                if(fs.readdirSync(curDir).length == 0) {
+                    fs.rmdirSync(curDir);
+                    curDir = path.resolve(curDir, '..');
+                } else {
+                    // directory not empty...do nothing
+                    break;
+                }
+            }
+        }
+    })
+
+    // move library files
+    libFiles.forEach(function (libFile) {
+        var libDir = path.resolve(project_dir,
+                                libFile.attrib['target-dir'])
+
+        if (action == 'install') {
+            shell.mkdir('-p', libDir);
+
+            var src = path.resolve(plugin_dir,
+                                        libFile.attrib['src']),
+                dest = path.resolve(libDir,
+                                path.basename(libFile.attrib['src']));
+            
+            shell.cp(src, dest);
+        } else {
+            var destFile = path.resolve(libDir,
+                            path.basename(libFile.attrib['src']));
+
+            fs.unlinkSync(destFile);
+            // check if directory is empty
+            var files = fs.readdirSync(libDir);
+            if(files.length == 0) {
+                shell.rm('-rf', libDir);
+            }
+        }
+    })
+
+
+    // edit configuration files
+    Object.keys(configChanges).forEach(function (filename) {
+        var filepath = path.resolve(project_dir, filename),
+            xmlDoc = xml_helpers.parseElementtreeSync(filepath),
+            output;
+
+        configChanges[filename].forEach(function (configNode) {
+            var selector = configNode.attrib["parent"],
+                children = configNode.findall('*');
+
+            if( action == 'install') {
+                if (!xml_helpers.graftXML(xmlDoc, children, selector)) {
+                    throw new Error('failed to add children to ' + filename);
+                }
+            } else {
+                if (!xml_helpers.pruneXML(xmlDoc, children, selector)) {
+                    throw new Error('failed to remove children from' + filename);
+                }
+            }
+        });
+
+        output = xmlDoc.write({indent: 4});
+        fs.writeFileSync(filepath, output);
+    });
+
+    // Remove all assets and JS modules installed by this plugin.
+    if (action == 'uninstall') {
+        var assets = plugin_et.findall('./asset');
+        assets && assets.forEach(function(asset) {
+            var target = asset.attrib.target;
+            shell.rm('-rf', path.join(project_dir, 'www', target));
+        });
+
+        shell.rm('-rf', path.join(project_dir, 'www', 'plugins', plugin_id));
+    }
+}
+
+
+function srcPath(pluginPath, filename) {
+    return path.resolve(pluginPath, filename);
+}
+
+function pluginInstalled(plugin_et, project_dir) {
+    var config_tag = plugin_et.find('./platform[@name="blackberry"]/config-file[@target="config.xml"]/feature');
+    if (!config_tag) {
+        return false;
+    }
+    var plugin_name = config_tag.attrib.id;
+    return (fs.readFileSync(path.resolve(project_dir, 'config.xml'), 'utf8')
+           .match(new RegExp(plugin_name, "g")) != null);
+}
+
+exports.www_dir = function(project_dir) {
+    return path.join(project_dir, 'www');
+};
+

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/platforms/ios.js
----------------------------------------------------------------------
diff --git a/src/platforms/ios.js b/src/platforms/ios.js
new file mode 100644
index 0000000..dda2110
--- /dev/null
+++ b/src/platforms/ios.js
@@ -0,0 +1,370 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed 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.
+ *
+*/
+
+var path = require('path')
+  , fs = require('../util/fs')  // use existsSync in 0.6.x
+  , glob = require('glob')
+  , et = require('elementtree')
+  , xcode = require('xcode')
+  , plist = require('plist')
+  , bplist = require('bplist-parser')
+  , shell = require('shelljs')
+  , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'))
+  , searchAndReplace = require(path.join(__dirname, '..', 'util', 'search-and-replace'))
+  , getConfigChanges = require(path.join(__dirname, '..', 'util', 'config-changes'));
+  
+exports.handlePlugin = function (action, project_dir, plugin_dir, plugin_et, variables) {
+    var plugin_id = plugin_et._root.attrib['id']
+      , version = plugin_et._root.attrib['version']
+      , i = 0
+      , matched;
+
+    variables = variables || {}
+
+    var platformTag = plugin_et.find('./platform[@name="ios"]');
+    
+    if (!platformTag) {
+       // Either this plugin doesn't support this platform, or it's a JS-only plugin.
+       // Either way, return now.
+       return;
+    }
+    
+    var sourceFiles = platformTag.findall('./source-file'),
+           headerFiles = platformTag.findall('./header-file'),
+           resourceFiles = platformTag.findall('./resource-file'),
+           frameworks = platformTag.findall('./framework');
+           
+    // grab and parse pbxproj
+    // we don't want CordovaLib's xcode project
+    var project_files = glob.sync(path.join(project_dir, '*.xcodeproj', 'project.pbxproj'));
+    
+    if (!project_files.length) throw new Error("does not appear to be an xcode project (no xcode project file)");
+    var pbxPath = project_files[0];
+
+    var xcodeproj = xcode.project(project_files[0]);
+    xcodeproj.parseSync();
+
+    // grab and parse plist file or config.xml
+    var config_files = (glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist')).length == 0 ? 
+                        glob.sync(path.join(project_dir, '**', 'config.xml')) :
+                        glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist'))
+                       );
+
+    config_files = config_files.filter(function (val) {
+        return !(/^build\//.test(val));
+    });
+
+    if (!config_files.length) {
+        throw new Error("does not appear to be a PhoneGap project");
+    }
+
+    var config_file = config_files[0];
+    var xcode_dir = path.dirname(config_file);
+    var pluginsDir = path.resolve(xcode_dir, 'Plugins');
+    var resourcesDir = path.resolve(xcode_dir, 'Resources');
+    
+    // get project plist for package name
+    var project_plists = glob.sync(xcode_dir + '/*-Info.plist');
+    var projectPListPath = project_plists[0];
+    
+    // collision detection 
+    if(action.match(/force-/) == null) {
+      if(action == "install" && pluginInstalled(plugin_et, config_file)) {
+          throw new Error("Plugin "+plugin_id+" already installed");
+      } else if(action == "uninstall" && !pluginInstalled(plugin_et, config_file)) {
+          throw new Error("Plugin "+plugin_id+" not installed");
+      }
+    } else {
+      action = action.replace('force-', '');
+    }
+
+    // move native files (source/header/resource)
+    sourceFiles && sourceFiles.forEach(function (sourceFile) {
+        var src = sourceFile.attrib['src'],
+            srcFile = path.resolve(plugin_dir, src),
+            targetDir = path.resolve(pluginsDir, getRelativeDir(sourceFile)),
+            destFile = path.resolve(targetDir, path.basename(src));
+         
+        if (action == 'install') {
+            xcodeproj.addSourceFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
+            shell.mkdir('-p', targetDir);
+            checkLastCommand();
+            shell.cp(srcFile, destFile);
+            checkLastCommand();
+        } else {
+            xcodeproj.removeSourceFile(path.join('Plugins', path.basename(src)));
+            if(fs.existsSync(destFile))
+                fs.unlinkSync(destFile);
+            shell.rm('-rf', targetDir);    
+            checkLastCommand();
+        }
+    });
+
+    headerFiles && headerFiles.forEach(function (headerFile) {
+        var src = headerFile.attrib['src'],
+            srcFile = path.resolve(plugin_dir, src),
+            targetDir = path.resolve(pluginsDir, getRelativeDir(headerFile)),
+            destFile = path.resolve(targetDir, path.basename(src));
+         
+        if (action == 'install') {     
+            xcodeproj.addHeaderFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
+            shell.mkdir('-p', targetDir);
+            checkLastCommand();
+            shell.cp(srcFile, destFile);
+            checkLastCommand();
+        } else {
+            xcodeproj.removeHeaderFile(path.join('Plugins', path.basename(src)));
+            if(fs.existsSync(destFile))
+                fs.unlinkSync(destFile);
+            shell.rm('-rf', targetDir);
+            checkLastCommand();
+        }
+    });
+
+    resourceFiles && resourceFiles.forEach(function (resource) {
+        var src = resource.attrib['src'],
+            srcFile = path.resolve(plugin_dir, src),
+            destFile = path.resolve(resourcesDir, path.basename(src));
+
+        if (action == 'install') {
+            xcodeproj.addResourceFile(path.join('Resources', path.basename(src)));
+            var st = fs.statSync(srcFile);
+            shell.mkdir('-p', resourcesDir);
+            if (st.isDirectory()) {
+                shell.cp('-R', srcFile, resourcesDir);
+                checkLastCommand();
+            } else {
+                shell.cp(srcFile, destFile);
+                checkLastCommand();
+            }
+        } else {
+            xcodeproj.removeResourceFile(path.join('Resources', path.basename(src)));
+            shell.rm('-rf', destFile);
+            checkLastCommand();
+        }
+    });
+
+    frameworks && frameworks.forEach(function (framework) {
+        var src = framework.attrib['src'],
+            weak = framework.attrib['weak'];
+        if (action == 'install') {
+            var opt = { weak: (weak && weak.toLowerCase() == 'true') };
+            xcodeproj.addFramework(src, opt);
+        } else {
+            xcodeproj.removeFramework(src);
+        }
+    });
+
+    // write out xcodeproj file
+    fs.writeFileSync(pbxPath, xcodeproj.writeSync());
+
+    // add plugin and whitelisted hosts
+    try {
+      updateConfig(action, config_file, plugin_et);
+    } catch(e) {
+      throw {
+        name: "ConfigurationError",
+        level: "ERROR",
+        message: "Error updating configuration: "+e.message,
+      }
+    }
+    
+    if (action == 'install') {
+        variables['PACKAGE_NAME'] = plist.parseFileSync(projectPListPath).CFBundleIdentifier;
+        searchAndReplace(pbxPath, variables);
+        searchAndReplace(projectPListPath, variables);
+        searchAndReplace(config_file, variables);
+    }
+
+    // Remove all assets and JS modules installed by this plugin.
+    if (action == 'uninstall') {
+        var assets = plugin_et.findall('./asset');
+        assets && assets.forEach(function(asset) {
+            var target = asset.attrib.target;
+            shell.rm('-rf', path.join(project_dir, 'www', target));
+        });
+
+        shell.rm('-rf', path.join(project_dir, 'www', 'plugins', plugin_id));
+    }
+
+}
+
+function getRelativeDir(file) {
+    var targetDir = file.attrib['target-dir'],
+        preserveDirs = file.attrib['preserve-dirs'];
+
+    if (preserveDirs && preserveDirs.toLowerCase() == 'true') {
+        return path.dirname(file.attrib['src']);
+    } else if (targetDir) {
+        return targetDir;
+    } else {
+        return '';
+    }
+}
+
+// determine if a plist file is binary
+function isBinaryPlist(filename) {
+    // I wish there was a synchronous way to read only the first 6 bytes of a
+    // file. This is wasteful :/ 
+    var buf = '' + fs.readFileSync(filename, 'utf8');
+    // binary plists start with a magic header, "bplist"
+    return buf.substring(0, 6) === 'bplist';
+}
+
+function updatePlistFile(action, config_path, plugin_et) {
+    var hosts = plugin_et.findall('./access'),
+        platformTag = plugin_et.find('./platform[@name="ios"]'), // FIXME: can probably do better than this
+        plistEle = platformTag.find('./plugins-plist'),
+        external_hosts = [];
+
+    // determine if this is a binary or ascii plist and choose the parser
+    // this is temporary until binary support is added to node-plist
+    var pl = (isBinaryPlist(config_path) ? bplist : plist);
+
+    var plistObj = pl.parseFileSync(config_path);
+    
+    if (action == 'install') {
+        // add hosts to whitelist (ExternalHosts) in plist
+        hosts && hosts.forEach(function(host) {
+            plistObj.ExternalHosts.push(host.attrib['origin']);
+        });
+
+        // add plugin to plist
+        plistObj.Plugins[plistEle.attrib['key']] = plistEle.attrib['string'];
+    } else {
+        // remove hosts from whitelist (ExternalHosts) in plist
+        // check each entry in external hosts, only add it to the plist if
+        // it's not an entry added by this plugin 
+        for(i=0; i < plistObj.ExternalHosts.length;i++) {
+            matched = false;
+            hosts && hosts.forEach(function(host) {
+                if(host === plistObj.ExternalHosts[i]) {
+                    matched = true;
+                }
+            });
+            if (!matched) {
+                external_hosts.push(plistObj.ExternalHosts[i]);
+            }
+        }
+
+        // filtered the external hosts entries out, copy result
+        plistObj.ExternalHosts = external_hosts;
+
+        delete plistObj.Plugins[plistEle.attrib['key']];
+    }
+    
+    // write out plist
+    fs.writeFileSync(config_path, plist.build(plistObj));
+}
+
+function pluginInstalled(plugin_et, config_path) {
+    var config_tag = plugin_et.find('./platform[@name="ios"]/config-file[@target="config.xml"]/plugin') ||
+                     plugin_et.find('./platform[@name="ios"]/plugins-plist');
+    if (!config_tag) {
+        return false;
+    }
+    var plugin_name = config_tag.attrib.name || config_tag.attrib.key;
+    return (fs.readFileSync(config_path, 'utf8').match(new RegExp(plugin_name, "g")) != null);
+}
+
+function updateConfigXml(action, config_path, plugin_et) {
+    var hosts = plugin_et.findall('./access'),
+        platformTag = plugin_et.find('./platform[@name="ios"]'), // FIXME: can probably do better than this
+        plistEle = platformTag.find('./plugins-plist'), // use this for older that have plugins-plist
+        configChanges = getConfigChanges(platformTag),
+        base_config_path = path.basename(config_path);
+
+    // edit configuration files
+    var xmlDoc = xml_helpers.parseElementtreeSync(config_path),
+        output,
+		    pListOnly = plistEle;
+    
+    if (configChanges[path.basename(config_path)]) {	
+      configChanges[path.basename(config_path)].forEach(function (val) {
+        if (val.find("plugin")) pListOnly = false;
+      });
+    }
+
+    if (pListOnly) {
+        // if the plugin supports the old plugins-plist element only
+        var name = plistEle.attrib.key;
+        var value = plistEle.attrib.string;
+        var pluginsEl = xmlDoc.find('plugins');
+        if ( action == 'install') {
+            var new_plugin = new et.Element('plugin');
+            new_plugin.attrib.name = name;
+            new_plugin.attrib.value = value;
+            pluginsEl.append(new_plugin);
+        } else {
+            var culprit = pluginsEl.find("plugin[@name='"+name+"']");
+            pluginsEl.remove(0, culprit);
+        }
+    }
+
+    // add whitelist hosts
+    root = et.Element("config-file");
+    root.attrib['parent'] = '.'
+      hosts.forEach(function (tag) {
+      root.append(tag);
+    });
+
+    if (root.len()) {
+      (configChanges[path.basename(config_path)]) ?
+            configChanges[path.basename(config_path)].push(root) :
+            configChanges[path.basename(config_path)] = [root];
+    }
+
+    if (configChanges[path.basename(config_path)]) {
+
+        configChanges[base_config_path].forEach(function (configNode) {
+            var selector = configNode.attrib["parent"],
+                children = configNode.findall('*');
+            if( action == 'install') {
+                if (!xml_helpers.graftXML(xmlDoc, children, selector)) {
+                    throw new Error('failed to add children to ' + selector + ' in ' + config_path);
+                }
+            } else {
+                if (!xml_helpers.pruneXML(xmlDoc, children, selector)) {
+                    throw new Error('failed to remove children from ' + selector + ' in ' + config_path);
+                }
+            }
+      });
+    }
+
+    output = xmlDoc.write({indent: 4});
+    fs.writeFileSync(config_path, output);
+}
+
+// updates plist file and/or config.xml
+function updateConfig(action, config_path, plugin_et) {
+    if(path.basename(config_path) == "config.xml") {
+        updateConfigXml(action, config_path, plugin_et);
+    } else {
+        updatePlistFile(action, config_path, plugin_et);
+    }
+}
+// throws error if last command returns code != 0
+function checkLastCommand() {
+    if(shell.error() != null) throw {name: "ShellError", message: shell.error()};
+}
+
+exports.www_dir = function(project_dir) {
+    return path.join(project_dir, 'www');
+};
+

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/prepare.js
----------------------------------------------------------------------
diff --git a/src/prepare.js b/src/prepare.js
new file mode 100644
index 0000000..cb5474d
--- /dev/null
+++ b/src/prepare.js
@@ -0,0 +1,152 @@
+/**
+    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.
+*/
+
+var platform_modules = require('./platforms'),
+    path            = require('path'),
+    fs              = require('fs'),
+    shell           = require('shelljs'),
+    ls              = fs.readdirSync,
+    util            = require('util'),
+    exec            = require('child_process').exec,
+    et              = require('elementtree');
+
+// Called on --prepare.
+// Sets up each plugin's Javascript code to be loaded properly.
+// Expects a path to the project (platforms/android in CLI, . in plugman-only),
+// a path to where the plugins are downloaded, the www dir, and the platform ('android', 'ios', etc.).
+module.exports = function handlePrepare(project_dir, platform, plugins_dir) {
+    // Process:
+    // - List all plugins in plugins_dir
+    // - Load and parse their plugin.xml files.
+    // - Skip those without support for this platform. (No <platform> tags means JS-only!)
+    // - Build a list of all their js-modules, including platform-specific js-modules.
+    // - For each js-module (general first, then platform) build up an object storing the path and any clobbers, merges and runs for it.
+    // - Write this object into www/plugins.json.
+    // - Cordova.js contains code to load them at runtime from that file.
+
+    var wwwDir = platform_modules[platform].www_dir(project_dir);
+    var plugins = ls(plugins_dir);
+
+    // This array holds all the metadata for each module and ends up in cordova_plugins.json
+    var moduleObjects = [];
+
+    plugins && plugins.forEach(function(plugin) {
+        var pluginDir = path.join(plugins_dir, plugin);
+        if(fs.statSync(pluginDir).isDirectory()){
+            var xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(pluginDir, 'plugin.xml'), 'utf-8')));
+    
+            var plugin_id = xml.getroot().attrib.id;
+    
+            // Copy all the <asset>s into the platform's www/
+            var assets = xml.findall('./asset');
+            assets && assets.forEach(function(asset) {
+                var target = asset.attrib.target;
+               
+                var lastSlash = target.lastIndexOf('/');
+                var dirname  = lastSlash < 0 ? ''     : target.substring(0, lastSlash);
+                var basename = lastSlash < 0 ? target : target.substring(lastSlash + 1);
+    
+                var targetDir = path.join(wwwDir, dirname);
+                 
+                shell.mkdir('-p', targetDir);
+    
+                var srcFile = path.join(pluginDir, asset.attrib.src);
+                var targetFile = path.join(targetDir, basename);
+    
+                var cpOptions = '-f';
+                
+                if(fs.statSync(srcFile).isDirectory()){
+                    shell.mkdir('-p',targetFile);
+                    srcFile = srcFile+'/*';
+                    cpOptions = '-Rf';
+                }
+    
+                shell.cp(cpOptions, [srcFile], targetFile);
+                
+            });
+    
+            // And then add the plugins dir to the platform's www.
+            var platformPluginsDir = path.join(wwwDir, 'plugins');
+            shell.mkdir('-p', platformPluginsDir);
+    
+            var generalModules = xml.findall('./js-module');
+            var platformTag = xml.find(util.format('./platform[@name="%s"]', platform));
+    
+            generalModules = generalModules || [];
+            var platformModules = platformTag ? platformTag.findall('./js-module') : [];
+            var allModules = generalModules.concat(platformModules);
+    
+            allModules.forEach(function(module) {
+                // Copy the plugin's files into the www directory.
+                var dirname = module.attrib.src;
+                var lastSlash = dirname.lastIndexOf('/');
+                if (lastSlash >= 0) {
+                    dirname = dirname.substring(0, lastSlash);
+                } else {
+                    dirname = ''; // Just the file, no subdir.
+                }
+    
+                var dir = path.join(platformPluginsDir, plugin_id, dirname);
+                shell.mkdir('-p', dir);
+    
+                // Read in the file, prepend the cordova.define, and write it back out.
+                var moduleName = plugin_id + '.';
+                if (module.attrib.name) {
+                    moduleName += module.attrib.name;
+                } else {
+                    var result = module.attrib.src.match(/([^\/]+)\.js/);
+                    moduleName += result[1];
+                }
+    
+                var scriptContent = fs.readFileSync(path.join(pluginDir, module.attrib.src), 'utf-8');
+                scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {' + scriptContent + '});\n';
+                fs.writeFileSync(path.join(platformPluginsDir, plugin_id, module.attrib.src), scriptContent, 'utf-8');
+    
+                // Prepare the object for cordova_plugins.json.
+                var obj = {
+                    file: path.join('plugins', plugin_id, module.attrib.src),
+                    id: moduleName
+                };
+    
+                // Loop over the children of the js-module tag, collecting clobbers, merges and runs.
+                module.getchildren().forEach(function(child) {
+                    if (child.tag.toLowerCase() == 'clobbers') {
+                        if (!obj.clobbers) {
+                            obj.clobbers = [];
+                        }
+                        obj.clobbers.push(child.attrib.target);
+                    } else if (child.tag.toLowerCase() == 'merges') {
+                        if (!obj.merges) {
+                            obj.merges = [];
+                        }
+                        obj.merges.push(child.attrib.target);
+                    } else if (child.tag.toLowerCase() == 'runs') {
+                        obj.runs = true;
+                    }
+                });
+    
+                // Add it to the list of module objects bound for cordova_plugins.json
+                moduleObjects.push(obj);
+            });
+        }
+    });
+
+    // Write out moduleObjects as JSON to cordova_plugins.json
+    fs.writeFileSync(path.join(wwwDir, 'cordova_plugins.json'), JSON.stringify(moduleObjects), 'utf-8');
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/remove.js
----------------------------------------------------------------------
diff --git a/src/remove.js b/src/remove.js
new file mode 100644
index 0000000..ada9afa
--- /dev/null
+++ b/src/remove.js
@@ -0,0 +1,14 @@
+var shell = require('shelljs'),
+    fs    = require('fs'),
+    path  = require('path');
+
+module.exports = function removePlugin(name, plugins_dir) {
+    var target = path.join(plugins_dir, name);
+    var stat = fs.lstatSync(target);
+
+    if (stat.isSymbolicLink()) {
+        fs.unlinkSync(target);
+    } else {
+        shell.rm('-rf', target);
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5f4bb9d5/src/uninstall.js
----------------------------------------------------------------------
diff --git a/src/uninstall.js b/src/uninstall.js
new file mode 100644
index 0000000..b51487c
--- /dev/null
+++ b/src/uninstall.js
@@ -0,0 +1,75 @@
+var path = require('path'),
+    fs   = require('fs'),
+    platform_modules = require('./platforms');
+
+function runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, callback) {
+    var xml_path     = path.join(plugin_dir, 'plugin.xml')
+      , xml_text     = fs.readFileSync(xml_path, 'utf-8')
+      , plugin_et    = new et.ElementTree(et.XML(xml_text))
+
+    // run the platform-specific function
+    try {
+        platform_modules[platform].handleUninstall(project_dir, plugin_dir, plugin_et);
+        require('./../plugman').prepare(project_dir, platform, plugins_dir);
+        if (callback) callback();
+    } catch(e) {
+        var err;
+        try {
+            platform_modules[platform].forceInstall(project_dir, plugin_dir, plugin_et, {});
+            err = new Error('Error during uninstallation of plugin, reverted all changes. Uninstall error: ' + e.message);
+        } catch(etwo) {
+            err = new Error('Error during uninstallation of plugin, reverting changes also caused issues! Reversion probably incomplete. Uninstall error: ' + e.message + ', reversion error: ' + etwo.message);
+        }
+        if (callback) callback(err);
+        else throw err;
+    }
+}
+
+module.exports = function uninstallPlugin(platform, project_dir, name, plugins_dir, cli_variables, callback) {
+    if (!platform_modules[platform]) {
+        var err = new Error(platform + " not supported.");
+        if (callback) {
+            callback(err);
+            return;
+        }
+        else throw err;
+    }
+
+    // Check that the plugin has already been fetched.
+    var plugin_dir = path.join(plugins_dir, name);
+    var plugin_xml_path = path.join(plugin_dir, 'plugin.xml');
+
+    if (!fs.existsSync(plugin_dir)) {
+        var err = new Error('Plugin ' + name + ' not found. Already uninstalled?');
+        if (callback) {
+            callback(err);
+            return;
+        }
+        else throw err;
+    }
+
+    if (!fs.existsSync(plugin_xml_path)) {
+        // try querying the plugin database
+        plugins.getPluginInfo(plugin_dir,
+            function(err, plugin_info) {
+                if (err) {
+                    var err_obj = new Error('uninstall failed. plugin.xml at "' + plugin_xml_path + '" not found and couldnt query remote server for information about the plugin because ' + err.message);
+                    if (callback) callback(err_obj);
+                    else throw err_obj;
+                } else {
+                    plugins.clonePluginGitRepo(plugin_info.url, plugins_dir, function(err, plugin_dir) {
+                        if (err) {
+                            var err_obj = new Error('uninstall failed. plugin.xml at "' + plugin_xml_path + '" not found and couldnt clone plugin repo because ' + err.message);
+                            if (callback) callback(err_obj);
+                            else throw err_obj;
+                        } else {
+                            runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, callback);
+                        }
+                    });
+                }
+            }
+        );
+    } else {
+        runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, callback);
+    }
+};


Mime
View raw message