http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/rm.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/rm.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/rm.js new file mode 100644 index 0000000..bd608cb --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/rm.js @@ -0,0 +1,163 @@ +var common = require('./common'); +var fs = require('fs'); + +// Recursively removes 'dir' +// Adapted from https://github.com/ryanmcgrath/wrench-js +// +// Copyright (c) 2010 Ryan McGrath +// Copyright (c) 2012 Artur Adib +// +// Licensed under the MIT License +// http://www.opensource.org/licenses/mit-license.php +function rmdirSyncRecursive(dir, force) { + var files; + + files = fs.readdirSync(dir); + + // Loop through and delete everything in the sub-tree after checking it + for(var i = 0; i < files.length; i++) { + var file = dir + "/" + files[i], + currFile = fs.lstatSync(file); + + if(currFile.isDirectory()) { // Recursive function back to the beginning + rmdirSyncRecursive(file, force); + } + + else if(currFile.isSymbolicLink()) { // Unlink symlinks + if (force || isWriteable(file)) { + try { + common.unlinkSync(file); + } catch (e) { + common.error('could not remove file (code '+e.code+'): ' + file, true); + } + } + } + + else // Assume it's a file - perhaps a try/catch belongs here? + if (force || isWriteable(file)) { + try { + common.unlinkSync(file); + } catch (e) { + common.error('could not remove file (code '+e.code+'): ' + file, true); + } + } + } + + // Now that we know everything in the sub-tree has been deleted, we can delete the main directory. + // Huzzah for the shopkeep. + + var result; + try { + // Retry on windows, sometimes it takes a little time before all the files in the directory are gone + var start = Date.now(); + while (true) { + try { + result = fs.rmdirSync(dir); + if (fs.existsSync(dir)) throw { code: "EAGAIN" } + break; + } catch(er) { + // In addition to error codes, also check if the directory still exists and loop again if true + if (process.platform === "win32" && (er.code === "ENOTEMPTY" || er.code === "EBUSY" || er.code === "EPERM" || er.code === "EAGAIN")) { + if (Date.now() - start > 1000) throw er; + } else if (er.code === "ENOENT") { + // Directory did not exist, deletion was successful + break; + } else { + throw er; + } + } + } + } catch(e) { + common.error('could not remove directory (code '+e.code+'): ' + dir, true); + } + + return result; +} // rmdirSyncRecursive + +// Hack to determine if file has write permissions for current user +// Avoids having to check user, group, etc, but it's probably slow +function isWriteable(file) { + var writePermission = true; + try { + var __fd = fs.openSync(file, 'a'); + fs.closeSync(__fd); + } catch(e) { + writePermission = false; + } + + return writePermission; +} + +//@ +//@ ### rm([options ,] file [, file ...]) +//@ ### rm([options ,] file_array) +//@ Available options: +//@ +//@ + `-f`: force +//@ + `-r, -R`: recursive +//@ +//@ Examples: +//@ +//@ ```javascript +//@ rm('-rf', '/tmp/*'); +//@ rm('some_file.txt', 'another_file.txt'); +//@ rm(['some_file.txt', 'another_file.txt']); // same as above +//@ ``` +//@ +//@ Removes files. The wildcard `*` is accepted. +function _rm(options, files) { + options = common.parseOptions(options, { + 'f': 'force', + 'r': 'recursive', + 'R': 'recursive' + }); + if (!files) + common.error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 1); + // if it's array leave it as it is + + files = common.expand(files); + + files.forEach(function(file) { + if (!fs.existsSync(file)) { + // Path does not exist, no force flag given + if (!options.force) + common.error('no such file or directory: '+file, true); + + return; // skip file + } + + // If here, path exists + + var stats = fs.lstatSync(file); + if (stats.isFile() || stats.isSymbolicLink()) { + + // Do not check for file writing permissions + if (options.force) { + common.unlinkSync(file); + return; + } + + if (isWriteable(file)) + common.unlinkSync(file); + else + common.error('permission denied: '+file, true); + + return; + } // simple file + + // Path is an existing directory, but no -r flag given + if (stats.isDirectory() && !options.recursive) { + common.error('path is a directory', true); + return; // skip path + } + + // Recursively remove existing directory + if (stats.isDirectory() && options.recursive) { + rmdirSyncRecursive(file, options.force); + } + }); // forEach(file) +} // rm +module.exports = _rm;
http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/sed.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/sed.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/sed.js new file mode 100644 index 0000000..65f7cb4 --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/sed.js @@ -0,0 +1,43 @@ +var common = require('./common'); +var fs = require('fs'); + +//@ +//@ ### sed([options ,] search_regex, replacement, file) +//@ Available options: +//@ +//@ + `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_ +//@ +//@ Examples: +//@ +//@ ```javascript +//@ sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js'); +//@ sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js'); +//@ ``` +//@ +//@ Reads an input string from `file` and performs a JavaScript `replace()` on the input +//@ using the given search regex and replacement string or function. Returns the new string after replacement. +function _sed(options, regex, replacement, file) { + options = common.parseOptions(options, { + 'i': 'inplace' + }); + + if (typeof replacement === 'string' || typeof replacement === 'function') + replacement = replacement; // no-op + else if (typeof replacement === 'number') + replacement = replacement.toString(); // fallback + else + common.error('invalid replacement string'); + + if (!file) + common.error('no file given'); + + if (!fs.existsSync(file)) + common.error('no such file or directory: ' + file); + + var result = fs.readFileSync(file, 'utf8').replace(regex, replacement); + if (options.inplace) + fs.writeFileSync(file, result, 'utf8'); + + return common.ShellString(result); +} +module.exports = _sed; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/tempdir.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/tempdir.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/tempdir.js new file mode 100644 index 0000000..45953c2 --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/tempdir.js @@ -0,0 +1,56 @@ +var common = require('./common'); +var os = require('os'); +var fs = require('fs'); + +// Returns false if 'dir' is not a writeable directory, 'dir' otherwise +function writeableDir(dir) { + if (!dir || !fs.existsSync(dir)) + return false; + + if (!fs.statSync(dir).isDirectory()) + return false; + + var testFile = dir+'/'+common.randomFileName(); + try { + fs.writeFileSync(testFile, ' '); + common.unlinkSync(testFile); + return dir; + } catch (e) { + return false; + } +} + + +//@ +//@ ### tempdir() +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var tmp = tempdir(); // "/tmp" for most *nix platforms +//@ ``` +//@ +//@ Searches and returns string containing a writeable, platform-dependent temporary directory. +//@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir). +function _tempDir() { + var state = common.state; + if (state.tempDir) + return state.tempDir; // from cache + + state.tempDir = writeableDir(os.tempDir && os.tempDir()) || // node 0.8+ + writeableDir(process.env['TMPDIR']) || + writeableDir(process.env['TEMP']) || + writeableDir(process.env['TMP']) || + writeableDir(process.env['Wimp$ScrapDir']) || // RiscOS + writeableDir('C:\\TEMP') || // Windows + writeableDir('C:\\TMP') || // Windows + writeableDir('\\TEMP') || // Windows + writeableDir('\\TMP') || // Windows + writeableDir('/tmp') || + writeableDir('/var/tmp') || + writeableDir('/usr/tmp') || + writeableDir('.'); // last resort + + return state.tempDir; +} +module.exports = _tempDir; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/test.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/test.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/test.js new file mode 100644 index 0000000..8a4ac7d --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/test.js @@ -0,0 +1,85 @@ +var common = require('./common'); +var fs = require('fs'); + +//@ +//@ ### test(expression) +//@ Available expression primaries: +//@ +//@ + `'-b', 'path'`: true if path is a block device +//@ + `'-c', 'path'`: true if path is a character device +//@ + `'-d', 'path'`: true if path is a directory +//@ + `'-e', 'path'`: true if path exists +//@ + `'-f', 'path'`: true if path is a regular file +//@ + `'-L', 'path'`: true if path is a symboilc link +//@ + `'-p', 'path'`: true if path is a pipe (FIFO) +//@ + `'-S', 'path'`: true if path is a socket +//@ +//@ Examples: +//@ +//@ ```javascript +//@ if (test('-d', path)) { /* do something with dir */ }; +//@ if (!test('-f', path)) continue; // skip if it's a regular file +//@ ``` +//@ +//@ Evaluates expression using the available primaries and returns corresponding value. +function _test(options, path) { + if (!path) + common.error('no path given'); + + // hack - only works with unary primaries + options = common.parseOptions(options, { + 'b': 'block', + 'c': 'character', + 'd': 'directory', + 'e': 'exists', + 'f': 'file', + 'L': 'link', + 'p': 'pipe', + 'S': 'socket' + }); + + var canInterpret = false; + for (var key in options) + if (options[key] === true) { + canInterpret = true; + break; + } + + if (!canInterpret) + common.error('could not interpret expression'); + + if (options.link) { + try { + return fs.lstatSync(path).isSymbolicLink(); + } catch(e) { + return false; + } + } + + if (!fs.existsSync(path)) + return false; + + if (options.exists) + return true; + + var stats = fs.statSync(path); + + if (options.block) + return stats.isBlockDevice(); + + if (options.character) + return stats.isCharacterDevice(); + + if (options.directory) + return stats.isDirectory(); + + if (options.file) + return stats.isFile(); + + if (options.pipe) + return stats.isFIFO(); + + if (options.socket) + return stats.isSocket(); +} // test +module.exports = _test; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/to.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/to.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/to.js new file mode 100644 index 0000000..f029999 --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/to.js @@ -0,0 +1,29 @@ +var common = require('./common'); +var fs = require('fs'); +var path = require('path'); + +//@ +//@ ### 'string'.to(file) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ cat('input.txt').to('output.txt'); +//@ ``` +//@ +//@ Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as +//@ those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_ +function _to(options, file) { + if (!file) + common.error('wrong arguments'); + + if (!fs.existsSync( path.dirname(file) )) + common.error('no such file or directory: ' + path.dirname(file)); + + try { + fs.writeFileSync(file, this.toString(), 'utf8'); + } catch(e) { + common.error('could not write to file (code '+e.code+'): '+file, true); + } +} +module.exports = _to; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/toEnd.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/toEnd.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/toEnd.js new file mode 100644 index 0000000..f6d099d --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/toEnd.js @@ -0,0 +1,29 @@ +var common = require('./common'); +var fs = require('fs'); +var path = require('path'); + +//@ +//@ ### 'string'.toEnd(file) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ cat('input.txt').toEnd('output.txt'); +//@ ``` +//@ +//@ Analogous to the redirect-and-append operator `>>` in Unix, but works with JavaScript strings (such as +//@ those returned by `cat`, `grep`, etc). +function _toEnd(options, file) { + if (!file) + common.error('wrong arguments'); + + if (!fs.existsSync( path.dirname(file) )) + common.error('no such file or directory: ' + path.dirname(file)); + + try { + fs.appendFileSync(file, this.toString(), 'utf8'); + } catch(e) { + common.error('could not append to file (code '+e.code+'): '+file, true); + } +} +module.exports = _toEnd; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/node_modules/shelljs/src/which.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/node_modules/shelljs/src/which.js b/bin/node_modules/cordova-common/node_modules/shelljs/src/which.js new file mode 100644 index 0000000..2822ecf --- /dev/null +++ b/bin/node_modules/cordova-common/node_modules/shelljs/src/which.js @@ -0,0 +1,83 @@ +var common = require('./common'); +var fs = require('fs'); +var path = require('path'); + +// Cross-platform method for splitting environment PATH variables +function splitPath(p) { + for (i=1;i<2;i++) {} + + if (!p) + return []; + + if (common.platform === 'win') + return p.split(';'); + else + return p.split(':'); +} + +function checkPath(path) { + return fs.existsSync(path) && fs.statSync(path).isDirectory() == false; +} + +//@ +//@ ### which(command) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var nodeExec = which('node'); +//@ ``` +//@ +//@ Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions. +//@ Returns string containing the absolute path to the command. +function _which(options, cmd) { + if (!cmd) + common.error('must specify command'); + + var pathEnv = process.env.path || process.env.Path || process.env.PATH, + pathArray = splitPath(pathEnv), + where = null; + + // No relative/absolute paths provided? + if (cmd.search(/\//) === -1) { + // Search for command in PATH + pathArray.forEach(function(dir) { + if (where) + return; // already found it + + var attempt = path.resolve(dir + '/' + cmd); + if (checkPath(attempt)) { + where = attempt; + return; + } + + if (common.platform === 'win') { + var baseAttempt = attempt; + attempt = baseAttempt + '.exe'; + if (checkPath(attempt)) { + where = attempt; + return; + } + attempt = baseAttempt + '.cmd'; + if (checkPath(attempt)) { + where = attempt; + return; + } + attempt = baseAttempt + '.bat'; + if (checkPath(attempt)) { + where = attempt; + return; + } + } // if 'win' + }); + } + + // Command not found anywhere? + if (!checkPath(cmd) && !where) + return null; + + where = where || path.resolve(cmd); + + return common.ShellString(where); +} +module.exports = _which; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/package.json ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/package.json b/bin/node_modules/cordova-common/package.json new file mode 100644 index 0000000..7f64eff --- /dev/null +++ b/bin/node_modules/cordova-common/package.json @@ -0,0 +1,89 @@ +{ + "author": { + "name": "Apache Software Foundation" + }, + "name": "cordova-common", + "description": "Apache Cordova tools and platforms shared routines", + "license": "Apache-2.0", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "git://git-wip-us.apache.org/repos/asf/cordova-common.git" + }, + "bugs": { + "url": "https://issues.apache.org/jira/browse/CB", + "email": "[email protected]" + }, + "main": "cordova-common.js", + "engines": { + "node": ">=0.9.9" + }, + "scripts": { + "test": "npm run jshint && npm run jasmine", + "jshint": "node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint spec", + "jasmine": "node node_modules/jasmine-node/bin/jasmine-node --captureExceptions --color spec", + "cover": "node node_modules/istanbul/lib/cli.js cover --root src --print detail node_modules/jasmine-node/bin/jasmine-node -- spec" + }, + "engineStrict": true, + "dependencies": { + "bplist-parser": "^0.1.0", + "cordova-registry-mapper": "^1.1.8", + "elementtree": "^0.1.6", + "glob": "^5.0.13", + "osenv": "^0.1.3", + "plist": "^1.1.0", + "q": "^1.4.1", + "semver": "^5.0.1", + "shelljs": "^0.5.1", + "underscore": "^1.8.3", + "unorm": "^1.3.3" + }, + "devDependencies": { + "istanbul": "^0.3.17", + "jasmine-node": "^1.14.5", + "jshint": "^2.8.0" + }, + "contributors": [], + "_id": "[email protected]", + "_shasum": "b21947e89a4a89292ec563abf9ee6ccb2b9f3aef", + "_resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-1.0.0.tgz", + "_from": "cordova-common@", + "_npmVersion": "2.14.7", + "_nodeVersion": "4.2.1", + "_npmUser": { + "name": "kotikov.vladimir", + "email": "[email protected]" + }, + "dist": { + "shasum": "b21947e89a4a89292ec563abf9ee6ccb2b9f3aef", + "tarball": "http://registry.npmjs.org/cordova-common/-/cordova-common-1.0.0.tgz" + }, + "maintainers": [ + { + "name": "bowserj", + "email": "[email protected]" + }, + { + "name": "kotikov.vladimir", + "email": "[email protected]" + }, + { + "name": "purplecabbage", + "email": "[email protected]" + }, + { + "name": "shazron", + "email": "[email protected]" + }, + { + "name": "stevegill", + "email": "[email protected]" + }, + { + "name": "timbarham", + "email": "[email protected]" + } + ], + "directories": {}, + "readme": "ERROR: No README data found!" +} http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/.jshintrc ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/.jshintrc b/bin/node_modules/cordova-common/src/.jshintrc new file mode 100644 index 0000000..89a121c --- /dev/null +++ b/bin/node_modules/cordova-common/src/.jshintrc @@ -0,0 +1,10 @@ +{ + "node": true + , "bitwise": true + , "undef": true + , "trailing": true + , "quotmark": true + , "indent": 4 + , "unused": "vars" + , "latedef": "nofunc" +} http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ActionStack.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ActionStack.js b/bin/node_modules/cordova-common/src/ActionStack.js new file mode 100644 index 0000000..5ef6f84 --- /dev/null +++ b/bin/node_modules/cordova-common/src/ActionStack.js @@ -0,0 +1,85 @@ +/** + 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. +*/ + +/* jshint quotmark:false */ + +var events = require('./events'), + Q = require('q'); + +function ActionStack() { + this.stack = []; + this.completed = []; +} + +ActionStack.prototype = { + createAction:function(handler, action_params, reverter, revert_params) { + return { + handler:{ + run:handler, + params:action_params + }, + reverter:{ + run:reverter, + params:revert_params + } + }; + }, + push:function(tx) { + this.stack.push(tx); + }, + // Returns a promise. + process:function(platform) { + events.emit('verbose', 'Beginning processing of action stack for ' + platform + ' project...'); + + while (this.stack.length) { + var action = this.stack.shift(); + var handler = action.handler.run; + var action_params = action.handler.params; + + try { + handler.apply(null, action_params); + } catch(e) { + events.emit('warn', 'Error during processing of action! Attempting to revert...'); + this.stack.unshift(action); + var issue = 'Uh oh!\n'; + // revert completed tasks + while(this.completed.length) { + var undo = this.completed.shift(); + var revert = undo.reverter.run; + var revert_params = undo.reverter.params; + + try { + revert.apply(null, revert_params); + } catch(err) { + events.emit('warn', 'Error during reversion of action! We probably really messed up your project now, sorry! D:'); + issue += 'A reversion action failed: ' + err.message + '\n'; + } + } + e.message = issue + e.message; + return Q.reject(e); + } + this.completed.push(action); + } + events.emit('verbose', 'Action stack processing complete.'); + + return Q(); + } +}; + +module.exports = ActionStack; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js b/bin/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js new file mode 100644 index 0000000..a38fca6 --- /dev/null +++ b/bin/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js @@ -0,0 +1,325 @@ +/* + * + * 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. + * +*/ + +/* + * This module deals with shared configuration / dependency "stuff". That is: + * - XML configuration files such as config.xml, AndroidManifest.xml or WMAppManifest.xml. + * - plist files in iOS + * Essentially, any type of shared resources that we need to handle with awareness + * of how potentially multiple plugins depend on a single shared resource, should be + * handled in this module. + * + * The implementation uses an object as a hash table, with "leaves" of the table tracking + * reference counts. + */ + +/* jshint sub:true */ + +var fs = require('fs'), + path = require('path'), + et = require('elementtree'), + semver = require('semver'), + events = require('../events'), + ConfigKeeper = require('./ConfigKeeper'); + +var mungeutil = require('./munge-util'); + +exports.PlatformMunger = PlatformMunger; + +exports.process = function(plugins_dir, project_dir, platform, platformJson, pluginInfoProvider) { + var munger = new PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider); + munger.process(plugins_dir); + munger.save_all(); +}; + +/****************************************************************************** +* PlatformMunger class +* +* Can deal with config file of a single project. +* Parsed config files are cached in a ConfigKeeper object. +******************************************************************************/ +function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) { + this.platform = platform; + this.project_dir = project_dir; + this.config_keeper = new ConfigKeeper(project_dir); + this.platformJson = platformJson; + this.pluginInfoProvider = pluginInfoProvider; +} + +// Write out all unsaved files. +PlatformMunger.prototype.save_all = PlatformMunger_save_all; +function PlatformMunger_save_all() { + this.config_keeper.save_all(); + this.platformJson.save(); +} + +// Apply a munge object to a single config file. +// The remove parameter tells whether to add the change or remove it. +PlatformMunger.prototype.apply_file_munge = PlatformMunger_apply_file_munge; +function PlatformMunger_apply_file_munge(file, munge, remove) { + var self = this; + + for (var selector in munge.parents) { + for (var xml_child in munge.parents[selector]) { + // this xml child is new, graft it (only if config file exists) + var config_file = self.config_keeper.get(self.project_dir, self.platform, file); + if (config_file.exists) { + if (remove) config_file.prune_child(selector, munge.parents[selector][xml_child]); + else config_file.graft_child(selector, munge.parents[selector][xml_child]); + } + } + } +} + + +PlatformMunger.prototype.remove_plugin_changes = remove_plugin_changes; +function remove_plugin_changes(pluginInfo, is_top_level) { + var self = this; + var platform_config = self.platformJson.root; + var plugin_vars = is_top_level ? + platform_config.installed_plugins[pluginInfo.id] : + platform_config.dependent_plugins[pluginInfo.id]; + + // get config munge, aka how did this plugin change various config files + var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars); + // global munge looks at all plugins' changes to config files + var global_munge = platform_config.config_munge; + var munge = mungeutil.decrement_munge(global_munge, config_munge); + + for (var file in munge.files) { + // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins. + if (self.platform == 'windows' && file == 'package.appxmanifest' && + !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) { + // New windows template separate manifest files for Windows8, Windows8.1 and WP8.1 + var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows80.appxmanifest', 'package.windows10.appxmanifest']; + /* jshint loopfunc:true */ + substs.forEach(function(subst) { + events.emit('verbose', 'Applying munge to ' + subst); + self.apply_file_munge(subst, munge.files[file], true); + }); + /* jshint loopfunc:false */ + } + self.apply_file_munge(file, munge.files[file], /* remove = */ true); + } + + // Remove from installed_plugins + self.platformJson.removePlugin(pluginInfo.id, is_top_level); + return self; +} + + +PlatformMunger.prototype.add_plugin_changes = add_plugin_changes; +function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increment) { + var self = this; + var platform_config = self.platformJson.root; + + // get config munge, aka how should this plugin change various config files + var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars); + // global munge looks at all plugins' changes to config files + + // TODO: The should_increment param is only used by cordova-cli and is going away soon. + // If should_increment is set to false, avoid modifying the global_munge (use clone) + // and apply the entire config_munge because it's already a proper subset of the global_munge. + var munge, global_munge; + if (should_increment) { + global_munge = platform_config.config_munge; + munge = mungeutil.increment_munge(global_munge, config_munge); + } else { + global_munge = mungeutil.clone_munge(platform_config.config_munge); + munge = config_munge; + } + + for (var file in munge.files) { + // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins. + if (self.platform == 'windows' && file == 'package.appxmanifest' && + !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) { + var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows80.appxmanifest', 'package.windows10.appxmanifest']; + /* jshint loopfunc:true */ + substs.forEach(function(subst) { + events.emit('verbose', 'Applying munge to ' + subst); + self.apply_file_munge(subst, munge.files[file]); + }); + /* jshint loopfunc:false */ + } + self.apply_file_munge(file, munge.files[file]); + } + + // Move to installed/dependent_plugins + self.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level); + return self; +} + + +// Load the global munge from platform json and apply all of it. +// Used by cordova prepare to re-generate some config file from platform +// defaults and the global munge. +PlatformMunger.prototype.reapply_global_munge = reapply_global_munge ; +function reapply_global_munge () { + var self = this; + + var platform_config = self.platformJson.root; + var global_munge = platform_config.config_munge; + for (var file in global_munge.files) { + self.apply_file_munge(file, global_munge.files[file]); + } + + return self; +} + + +// generate_plugin_config_munge +// Generate the munge object from plugin.xml + vars +PlatformMunger.prototype.generate_plugin_config_munge = generate_plugin_config_munge; +function generate_plugin_config_munge(pluginInfo, vars) { + var self = this; + + vars = vars || {}; + var munge = { files: {} }; + var changes = pluginInfo.getConfigFiles(self.platform); + + // Demux 'package.appxmanifest' into relevant platform-specific appx manifests. + // Only spend the cycles if there are version-specific plugin settings + if (self.platform === 'windows' && + changes.some(function(change) { + return ((typeof change.versions !== 'undefined') || + (typeof change.deviceTarget !== 'undefined')); + })) + { + var manifests = { + 'windows': { + '8.0.0': 'package.windows80.appxmanifest', + '8.1.0': 'package.windows.appxmanifest', + '10.0.0': 'package.windows10.appxmanifest' + }, + 'phone': { + '8.1.0': 'package.phone.appxmanifest', + '10.0.0': 'package.windows10.appxmanifest' + }, + 'all': { + '8.0.0': 'package.windows80.appxmanifest', + '8.1.0': ['package.windows.appxmanifest', 'package.phone.appxmanifest'], + '10.0.0': 'package.windows10.appxmanifest' + } + }; + + var oldChanges = changes; + changes = []; + + oldChanges.forEach(function(change, changeIndex) { + // Only support semver/device-target demux for package.appxmanifest + // Pass through in case something downstream wants to use it + if (change.target !== 'package.appxmanifest') { + changes.push(change); + return; + } + + var hasVersion = (typeof change.versions !== 'undefined'); + var hasTargets = (typeof change.deviceTarget !== 'undefined'); + + // No semver/device-target for this config-file, pass it through + if (!(hasVersion || hasTargets)) { + changes.push(change); + return; + } + + var targetDeviceSet = hasTargets ? change.deviceTarget : 'all'; + if (['windows', 'phone', 'all'].indexOf(targetDeviceSet) === -1) { + // target-device couldn't be resolved, fix it up here to a valid value + targetDeviceSet = 'all'; + } + var knownWindowsVersionsForTargetDeviceSet = Object.keys(manifests[targetDeviceSet]); + + // at this point, 'change' targets package.appxmanifest and has a version attribute + knownWindowsVersionsForTargetDeviceSet.forEach(function(winver) { + // This is a local function that creates the new replacement representing the + // mutation. Used to save code further down. + var createReplacement = function(manifestFile, originalChange) { + var replacement = { + target: manifestFile, + parent: originalChange.parent, + after: originalChange.after, + xmls: originalChange.xmls, + versions: originalChange.versions, + deviceTarget: originalChange.deviceTarget + }; + return replacement; + }; + + // version doesn't satisfy, so skip + if (hasVersion && !semver.satisfies(winver, change.versions)) { + return; + } + + var versionSpecificManifests = manifests[targetDeviceSet][winver]; + if (versionSpecificManifests.constructor === Array) { + // e.g. all['8.1.0'] === ['pkg.windows.appxmanifest', 'pkg.phone.appxmanifest'] + versionSpecificManifests.forEach(function(manifestFile) { + changes.push(createReplacement(manifestFile, change)); + }); + } + else { + // versionSpecificManifests is actually a single string + changes.push(createReplacement(versionSpecificManifests, change)); + } + }); + }); + } + + changes.forEach(function(change) { + change.xmls.forEach(function(xml) { + // 1. stringify each xml + var stringified = (new et.ElementTree(xml)).write({xml_declaration:false}); + // interp vars + if (vars) { + Object.keys(vars).forEach(function(key) { + var regExp = new RegExp('\\$' + key, 'g'); + stringified = stringified.replace(regExp, vars[key]); + }); + } + // 2. add into munge + mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after }); + }); + }); + return munge; +} + +// Go over the prepare queue and apply the config munges for each plugin +// that has been (un)installed. +PlatformMunger.prototype.process = PlatformMunger_process; +function PlatformMunger_process(plugins_dir) { + var self = this; + var platform_config = self.platformJson.root; + + // Uninstallation first + platform_config.prepare_queue.uninstalled.forEach(function(u) { + var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin)); + self.remove_plugin_changes(pluginInfo, u.topLevel); + }); + + // Now handle installation + platform_config.prepare_queue.installed.forEach(function(u) { + var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin)); + self.add_plugin_changes(pluginInfo, u.vars, u.topLevel, true); + }); + + // Empty out installed/ uninstalled queues. + platform_config.prepare_queue.uninstalled = []; + platform_config.prepare_queue.installed = []; +} +/**** END of PlatformMunger ****/ http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js b/bin/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js new file mode 100644 index 0000000..dd9ebbc --- /dev/null +++ b/bin/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js @@ -0,0 +1,208 @@ +/* + * 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'); +var path = require('path'); + +var bplist = require('bplist-parser'); +var et = require('elementtree'); +var glob = require('glob'); +var plist = require('plist'); + +var plist_helpers = require('../util/plist-helpers'); +var xml_helpers = require('../util/xml-helpers'); + +/****************************************************************************** +* ConfigFile class +* +* Can load and keep various types of config files. Provides some functionality +* specific to some file types such as grafting XML children. In most cases it +* should be instantiated by ConfigKeeper. +* +* For plugin.xml files use as: +* plugin_config = self.config_keeper.get(plugin_dir, '', 'plugin.xml'); +* +* TODO: Consider moving it out to a separate file and maybe partially with +* overrides in platform handlers. +******************************************************************************/ +function ConfigFile(project_dir, platform, file_tag) { + this.project_dir = project_dir; + this.platform = platform; + this.file_tag = file_tag; + this.is_changed = false; + + this.load(); +} + +// ConfigFile.load() +ConfigFile.prototype.load = ConfigFile_load; +function ConfigFile_load() { + var self = this; + + // config file may be in a place not exactly specified in the target + var filepath = self.filepath = resolveConfigFilePath(self.project_dir, self.platform, self.file_tag); + + if ( !filepath || !fs.existsSync(filepath) ) { + self.exists = false; + return; + } + self.exists = true; + self.mtime = fs.statSync(self.filepath).mtime; + + var ext = path.extname(filepath); + // Windows8 uses an appxmanifest, and wp8 will likely use + // the same in a future release + if (ext == '.xml' || ext == '.appxmanifest') { + self.type = 'xml'; + self.data = xml_helpers.parseElementtreeSync(filepath); + } else { + // plist file + self.type = 'plist'; + // TODO: isBinaryPlist() reads the file and then parse re-reads it again. + // We always write out text plist, not binary. + // Do we still need to support binary plist? + // If yes, use plist.parseStringSync() and read the file once. + self.data = isBinaryPlist(filepath) ? + bplist.parseBuffer(fs.readFileSync(filepath)) : + plist.parse(fs.readFileSync(filepath, 'utf8')); + } +} + +ConfigFile.prototype.save = function ConfigFile_save() { + var self = this; + if (self.type === 'xml') { + fs.writeFileSync(self.filepath, self.data.write({indent: 4}), 'utf-8'); + } else { + // plist + var regExp = new RegExp('<string>[ \t\r\n]+?</string>', 'g'); + fs.writeFileSync(self.filepath, plist.build(self.data).replace(regExp, '<string></string>')); + } + self.is_changed = false; +}; + +ConfigFile.prototype.graft_child = function ConfigFile_graft_child(selector, xml_child) { + var self = this; + var filepath = self.filepath; + var result; + if (self.type === 'xml') { + var xml_to_graft = [et.XML(xml_child.xml)]; + result = xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after); + if ( !result) { + throw new Error('grafting xml at selector "' + selector + '" from "' + filepath + '" during config install went bad :('); + } + } else { + // plist file + result = plist_helpers.graftPLIST(self.data, xml_child.xml, selector); + if ( !result ) { + throw new Error('grafting to plist "' + filepath + '" during config install went bad :('); + } + } + self.is_changed = true; +}; + +ConfigFile.prototype.prune_child = function ConfigFile_prune_child(selector, xml_child) { + var self = this; + var filepath = self.filepath; + var result; + if (self.type === 'xml') { + var xml_to_graft = [et.XML(xml_child.xml)]; + result = xml_helpers.pruneXML(self.data, xml_to_graft, selector); + } else { + // plist file + result = plist_helpers.prunePLIST(self.data, xml_child.xml, selector); + } + if (!result) { + var err_msg = 'Pruning at selector "' + selector + '" from "' + filepath + '" went bad.'; + throw new Error(err_msg); + } + self.is_changed = true; +}; + +// Some config-file target attributes are not qualified with a full leading directory, or contain wildcards. +// Resolve to a real path in this function. +// TODO: getIOSProjectname is slow because of glob, try to avoid calling it several times per project. +function resolveConfigFilePath(project_dir, platform, file) { + var filepath = path.join(project_dir, file); + var matches; + + if (file.indexOf('*') > -1) { + // handle wildcards in targets using glob. + matches = glob.sync(path.join(project_dir, '**', file)); + if (matches.length) filepath = matches[0]; + + // [CB-5989] multiple Info.plist files may exist. default to $PROJECT_NAME-Info.plist + if(matches.length > 1 && file.indexOf('-Info.plist')>-1){ + var plistName = getIOSProjectname(project_dir)+'-Info.plist'; + for (var i=0; i < matches.length; i++) { + if(matches[i].indexOf(plistName) > -1){ + filepath = matches[i]; + break; + } + } + } + return filepath; + } + + // special-case config.xml target that is just "config.xml". This should be resolved to the real location of the file. + // TODO: move the logic that contains the locations of config.xml from cordova CLI into plugman. + if (file == 'config.xml') { + if (platform == 'ubuntu') { + filepath = path.join(project_dir, 'config.xml'); + } else if (platform == 'ios') { + var iospath = getIOSProjectname(project_dir); + filepath = path.join(project_dir,iospath, 'config.xml'); + } else if (platform == 'android') { + filepath = path.join(project_dir, 'res', 'xml', 'config.xml'); + } else { + matches = glob.sync(path.join(project_dir, '**', 'config.xml')); + if (matches.length) filepath = matches[0]; + } + return filepath; + } + + // None of the special cases matched, returning project_dir/file. + return filepath; +} + +// Find out the real name of an iOS project +// TODO: glob is slow, need a better way or caching, or avoid using more than once. +function getIOSProjectname(project_dir) { + var matches = glob.sync(path.join(project_dir, '*.xcodeproj')); + var iospath; + if (matches.length === 1) { + iospath = path.basename(matches[0],'.xcodeproj'); + } else { + var msg; + if (matches.length === 0) { + msg = 'Does not appear to be an xcode project, no xcode project file in ' + project_dir; + } else { + msg = 'There are multiple *.xcodeproj dirs in ' + project_dir; + } + throw new Error(msg); + } + return iospath; +} + +// 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'; +} + +module.exports = ConfigFile; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js b/bin/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js new file mode 100644 index 0000000..894e922 --- /dev/null +++ b/bin/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js @@ -0,0 +1,65 @@ +/* + * 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. + * +*/ +/* jshint sub:true */ + +var path = require('path'); +var ConfigFile = require('./ConfigFile'); + +/****************************************************************************** +* ConfigKeeper class +* +* Used to load and store config files to avoid re-parsing and writing them out +* multiple times. +* +* The config files are referred to by a fake path constructed as +* project_dir/platform/file +* where file is the name used for the file in config munges. +******************************************************************************/ +function ConfigKeeper(project_dir, plugins_dir) { + this.project_dir = project_dir; + this.plugins_dir = plugins_dir; + this._cached = {}; +} + +ConfigKeeper.prototype.get = function ConfigKeeper_get(project_dir, platform, file) { + var self = this; + + // This fixes a bug with older plugins - when specifying config xml instead of res/xml/config.xml + // https://issues.apache.org/jira/browse/CB-6414 + if(file == 'config.xml' && platform == 'android'){ + file = 'res/xml/config.xml'; + } + var fake_path = path.join(project_dir, platform, file); + + if (self._cached[fake_path]) { + return self._cached[fake_path]; + } + // File was not cached, need to load. + var config_file = new ConfigFile(project_dir, platform, file); + self._cached[fake_path] = config_file; + return config_file; +}; + + +ConfigKeeper.prototype.save_all = function ConfigKeeper_save_all() { + var self = this; + Object.keys(self._cached).forEach(function (fake_path) { + var config_file = self._cached[fake_path]; + if (config_file.is_changed) config_file.save(); + }); +}; + +module.exports = ConfigKeeper; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ConfigChanges/munge-util.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ConfigChanges/munge-util.js b/bin/node_modules/cordova-common/src/ConfigChanges/munge-util.js new file mode 100644 index 0000000..307b3c1 --- /dev/null +++ b/bin/node_modules/cordova-common/src/ConfigChanges/munge-util.js @@ -0,0 +1,160 @@ +/* + * 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. + * +*/ +/* jshint sub:true */ + +var _ = require('underscore'); + +// add the count of [key1][key2]...[keyN] to obj +// return true if it didn't exist before +exports.deep_add = function deep_add(obj, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + + return exports.process_munge(obj, true/*createParents*/, function (parentArray, k) { + var found = _.find(parentArray, function(element) { + return element.xml == k.xml; + }); + if (found) { + found.after = found.after || k.after; + found.count += k.count; + } else { + parentArray.push(k); + } + return !found; + }, keys); +}; + +// decrement the count of [key1][key2]...[keyN] from obj and remove if it reaches 0 +// return true if it was removed or not found +exports.deep_remove = function deep_remove(obj, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + + var result = exports.process_munge(obj, false/*createParents*/, function (parentArray, k) { + var index = -1; + var found = _.find(parentArray, function (element) { + index++; + return element.xml == k.xml; + }); + if (found) { + found.count -= k.count; + if (found.count > 0) { + return false; + } + else { + parentArray.splice(index, 1); + } + } + return undefined; + }, keys); + + return typeof result === 'undefined' ? true : result; +}; + +// search for [key1][key2]...[keyN] +// return the object or undefined if not found +exports.deep_find = function deep_find(obj, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + + return exports.process_munge(obj, false/*createParents?*/, function (parentArray, k) { + return _.find(parentArray, function (element) { + return element.xml == (k.xml || k); + }); + }, keys); +}; + +// Execute func passing it the parent array and the xmlChild key. +// When createParents is true, add the file and parent items they are missing +// When createParents is false, stop and return undefined if the file and/or parent items are missing + +exports.process_munge = function process_munge(obj, createParents, func, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + var k = keys[0]; + if (keys.length == 1) { + return func(obj, k); + } else if (keys.length == 2) { + if (!obj.parents[k] && !createParents) { + return undefined; + } + obj.parents[k] = obj.parents[k] || []; + return exports.process_munge(obj.parents[k], createParents, func, keys.slice(1)); + } else if (keys.length == 3){ + if (!obj.files[k] && !createParents) { + return undefined; + } + obj.files[k] = obj.files[k] || { parents: {} }; + return exports.process_munge(obj.files[k], createParents, func, keys.slice(1)); + } else { + throw new Error('Invalid key format. Must contain at most 3 elements (file, parent, xmlChild).'); + } +}; + +// All values from munge are added to base as +// base[file][selector][child] += munge[file][selector][child] +// Returns a munge object containing values that exist in munge +// but not in base. +exports.increment_munge = function increment_munge(base, munge) { + var diff = { files: {} }; + + for (var file in munge.files) { + for (var selector in munge.files[file].parents) { + for (var xml_child in munge.files[file].parents[selector]) { + var val = munge.files[file].parents[selector][xml_child]; + // if node not in base, add it to diff and base + // else increment it's value in base without adding to diff + var newlyAdded = exports.deep_add(base, [file, selector, val]); + if (newlyAdded) { + exports.deep_add(diff, file, selector, val); + } + } + } + } + return diff; +}; + +// Update the base munge object as +// base[file][selector][child] -= munge[file][selector][child] +// nodes that reached zero value are removed from base and added to the returned munge +// object. +exports.decrement_munge = function decrement_munge(base, munge) { + var zeroed = { files: {} }; + + for (var file in munge.files) { + for (var selector in munge.files[file].parents) { + for (var xml_child in munge.files[file].parents[selector]) { + var val = munge.files[file].parents[selector][xml_child]; + // if node not in base, add it to diff and base + // else increment it's value in base without adding to diff + var removed = exports.deep_remove(base, [file, selector, val]); + if (removed) { + exports.deep_add(zeroed, file, selector, val); + } + } + } + } + return zeroed; +}; + +// For better readability where used +exports.clone_munge = function clone_munge(munge) { + return exports.increment_munge({}, munge); +}; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ConfigParser/ConfigParser.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ConfigParser/ConfigParser.js b/bin/node_modules/cordova-common/src/ConfigParser/ConfigParser.js new file mode 100644 index 0000000..7abddf6 --- /dev/null +++ b/bin/node_modules/cordova-common/src/ConfigParser/ConfigParser.js @@ -0,0 +1,499 @@ +/** + 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. +*/ + +/* jshint sub:true */ + +var et = require('elementtree'), + xml= require('../util/xml-helpers'), + CordovaError = require('../CordovaError/CordovaError'), + fs = require('fs'), + events = require('../events'); + + +/** Wraps a config.xml file */ +function ConfigParser(path) { + this.path = path; + try { + this.doc = xml.parseElementtreeSync(path); + this.cdvNamespacePrefix = getCordovaNamespacePrefix(this.doc); + et.register_namespace(this.cdvNamespacePrefix, 'http://cordova.apache.org/ns/1.0'); + } catch (e) { + console.error('Parsing '+path+' failed'); + throw e; + } + var r = this.doc.getroot(); + if (r.tag !== 'widget') { + throw new CordovaError(path + ' has incorrect root node name (expected "widget", was "' + r.tag + '")'); + } +} + +function getNodeTextSafe(el) { + return el && el.text && el.text.trim(); +} + +function findOrCreate(doc, name) { + var ret = doc.find(name); + if (!ret) { + ret = new et.Element(name); + doc.getroot().append(ret); + } + return ret; +} + +function getCordovaNamespacePrefix(doc){ + var rootAtribs = Object.getOwnPropertyNames(doc.getroot().attrib); + var prefix = 'cdv'; + for (var j = 0; j < rootAtribs.length; j++ ) { + if(rootAtribs[j].indexOf('xmlns:') === 0 && + doc.getroot().attrib[rootAtribs[j]] === 'http://cordova.apache.org/ns/1.0'){ + var strings = rootAtribs[j].split(':'); + prefix = strings[1]; + break; + } + } + return prefix; +} + +/** + * Finds the value of an element's attribute + * @param {String} attributeName Name of the attribute to search for + * @param {Array} elems An array of ElementTree nodes + * @return {String} + */ +function findElementAttributeValue(attributeName, elems) { + + elems = Array.isArray(elems) ? elems : [ elems ]; + + var value = elems.filter(function (elem) { + return elem.attrib.name.toLowerCase() === attributeName.toLowerCase(); + }).map(function (filteredElems) { + return filteredElems.attrib.value; + }).pop(); + + return value ? value : ''; +} + +ConfigParser.prototype = { + packageName: function(id) { + return this.doc.getroot().attrib['id']; + }, + setPackageName: function(id) { + this.doc.getroot().attrib['id'] = id; + }, + android_packageName: function() { + return this.doc.getroot().attrib['android-packageName']; + }, + android_activityName: function() { + return this.doc.getroot().attrib['android-activityName']; + }, + ios_CFBundleIdentifier: function() { + return this.doc.getroot().attrib['ios-CFBundleIdentifier']; + }, + name: function() { + return getNodeTextSafe(this.doc.find('name')); + }, + setName: function(name) { + var el = findOrCreate(this.doc, 'name'); + el.text = name; + }, + description: function() { + return getNodeTextSafe(this.doc.find('description')); + }, + setDescription: function(text) { + var el = findOrCreate(this.doc, 'description'); + el.text = text; + }, + version: function() { + return this.doc.getroot().attrib['version']; + }, + windows_packageVersion: function() { + return this.doc.getroot().attrib('windows-packageVersion'); + }, + android_versionCode: function() { + return this.doc.getroot().attrib['android-versionCode']; + }, + ios_CFBundleVersion: function() { + return this.doc.getroot().attrib['ios-CFBundleVersion']; + }, + setVersion: function(value) { + this.doc.getroot().attrib['version'] = value; + }, + author: function() { + return getNodeTextSafe(this.doc.find('author')); + }, + getGlobalPreference: function (name) { + return findElementAttributeValue(name, this.doc.findall('preference')); + }, + setGlobalPreference: function (name, value) { + var pref = this.doc.find('preference[@name="' + name + '"]'); + if (!pref) { + pref = new et.Element('preference'); + pref.attrib.name = name; + this.doc.getroot().append(pref); + } + pref.attrib.value = value; + }, + getPlatformPreference: function (name, platform) { + return findElementAttributeValue(name, this.doc.findall('platform[@name=\'' + platform + '\']/preference')); + }, + getPreference: function(name, platform) { + + var platformPreference = ''; + + if (platform) { + platformPreference = this.getPlatformPreference(name, platform); + } + + return platformPreference ? platformPreference : this.getGlobalPreference(name); + + }, + /** + * Returns all resources for the platform specified. + * @param {String} platform The platform. + * @param {string} resourceName Type of static resources to return. + * "icon" and "splash" currently supported. + * @return {Array} Resources for the platform specified. + */ + getStaticResources: function(platform, resourceName) { + var ret = [], + staticResources = []; + if (platform) { // platform specific icons + this.doc.findall('platform[@name=\'' + platform + '\']/' + resourceName).forEach(function(elt){ + elt.platform = platform; // mark as platform specific resource + staticResources.push(elt); + }); + } + // root level resources + staticResources = staticResources.concat(this.doc.findall(resourceName)); + // parse resource elements + var that = this; + staticResources.forEach(function (elt) { + var res = {}; + res.src = elt.attrib.src; + res.density = elt.attrib['density'] || elt.attrib[that.cdvNamespacePrefix+':density'] || elt.attrib['gap:density']; + res.platform = elt.platform || null; // null means icon represents default icon (shared between platforms) + res.width = +elt.attrib.width || undefined; + res.height = +elt.attrib.height || undefined; + + // default icon + if (!res.width && !res.height && !res.density) { + ret.defaultResource = res; + } + ret.push(res); + }); + + /** + * Returns resource with specified width and/or height. + * @param {number} width Width of resource. + * @param {number} height Height of resource. + * @return {Resource} Resource object or null if not found. + */ + ret.getBySize = function(width, height) { + return ret.filter(function(res) { + if (!res.width && !res.height) { + return false; + } + return ((!res.width || (width == res.width)) && + (!res.height || (height == res.height))); + })[0] || null; + }; + + /** + * Returns resource with specified density. + * @param {string} density Density of resource. + * @return {Resource} Resource object or null if not found. + */ + ret.getByDensity = function(density) { + return ret.filter(function(res) { + return res.density == density; + })[0] || null; + }; + + /** Returns default icons */ + ret.getDefault = function() { + return ret.defaultResource; + }; + + return ret; + }, + + /** + * Returns all icons for specific platform. + * @param {string} platform Platform name + * @return {Resource[]} Array of icon objects. + */ + getIcons: function(platform) { + return this.getStaticResources(platform, 'icon'); + }, + + /** + * Returns all splash images for specific platform. + * @param {string} platform Platform name + * @return {Resource[]} Array of Splash objects. + */ + getSplashScreens: function(platform) { + return this.getStaticResources(platform, 'splash'); + }, + + /** + * Returns all hook scripts for the hook type specified. + * @param {String} hook The hook type. + * @param {Array} platforms Platforms to look for scripts into (root scripts will be included as well). + * @return {Array} Script elements. + */ + getHookScripts: function(hook, platforms) { + var self = this; + var scriptElements = self.doc.findall('./hook'); + + if(platforms) { + platforms.forEach(function (platform) { + scriptElements = scriptElements.concat(self.doc.findall('./platform[@name="' + platform + '"]/hook')); + }); + } + + function filterScriptByHookType(el) { + return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook; + } + + return scriptElements.filter(filterScriptByHookType); + }, + /** + * Returns a list of plugin (IDs). + * + * This function also returns any plugin's that + * were defined using the legacy <feature> tags. + * @return {string[]} Array of plugin IDs + */ + getPluginIdList: function () { + var plugins = this.doc.findall('plugin'); + var result = plugins.map(function(plugin){ + return plugin.attrib.name; + }); + var features = this.doc.findall('feature'); + features.forEach(function(element ){ + var idTag = element.find('./param[@name="id"]'); + if(idTag){ + result.push(idTag.attrib.value); + } + }); + return result; + }, + getPlugins: function () { + return this.getPluginIdList().map(function (pluginId) { + return this.getPlugin(pluginId); + }, this); + }, + /** + * Adds a plugin element. Does not check for duplicates. + * @name addPlugin + * @function + * @param {object} attributes name and spec are supported + * @param {Array|object} variables name, value or arbitary object + */ + addPlugin: function (attributes, variables) { + if (!attributes && !attributes.name) return; + var el = new et.Element('plugin'); + el.attrib.name = attributes.name; + if (attributes.spec) { + el.attrib.spec = attributes.spec; + } + + // support arbitrary object as variables source + if (variables && typeof variables === 'object' && !Array.isArray(variables)) { + variables = Object.keys(variables) + .map(function (variableName) { + return {name: variableName, value: variables[variableName]}; + }); + } + + if (variables) { + variables.forEach(function (variable) { + el.append(new et.Element('variable', { name: variable.name, value: variable.value })); + }); + } + this.doc.getroot().append(el); + }, + /** + * Retrives the plugin with the given id or null if not found. + * + * This function also returns any plugin's that + * were defined using the legacy <feature> tags. + * @name getPlugin + * @function + * @param {String} id + * @returns {object} plugin including any variables + */ + getPlugin: function(id){ + if(!id){ + return undefined; + } + var pluginElement = this.doc.find('./plugin/[@name="' + id + '"]'); + if (null === pluginElement) { + var legacyFeature = this.doc.find('./feature/param[@name="id"][@value="' + id + '"]/..'); + if(legacyFeature){ + events.emit('log', 'Found deprecated feature entry for ' + id +' in config.xml.'); + return featureToPlugin(legacyFeature); + } + return undefined; + } + var plugin = {}; + + plugin.name = pluginElement.attrib.name; + plugin.spec = pluginElement.attrib.spec || pluginElement.attrib.src || pluginElement.attrib.version; + plugin.variables = {}; + var variableElements = pluginElement.findall('variable'); + variableElements.forEach(function(varElement){ + var name = varElement.attrib.name; + var value = varElement.attrib.value; + if(name){ + plugin.variables[name] = value; + } + }); + return plugin; + }, + /** + * Remove the plugin entry with give name (id). + * + * This function also operates on any plugin's that + * were defined using the legacy <feature> tags. + * @name removePlugin + * @function + * @param id name of the plugin + */ + removePlugin: function(id){ + if(id){ + var plugins = this.doc.findall('./plugin/[@name="' + id + '"]') + .concat(this.doc.findall('./feature/param[@name="id"][@value="' + id + '"]/..')); + var children = this.doc.getroot().getchildren(); + plugins.forEach(function (plugin) { + var idx = children.indexOf(plugin); + if (idx > -1) { + children.splice(idx, 1); + } + }); + } + }, + + // Add any element to the root + addElement: function(name, attributes) { + var el = et.Element(name); + for (var a in attributes) { + el.attrib[a] = attributes[a]; + } + this.doc.getroot().append(el); + }, + + /** + * Adds an engine. Does not check for duplicates. + * @param {String} name the engine name + * @param {String} spec engine source location or version (optional) + */ + addEngine: function(name, spec){ + if(!name) return; + var el = et.Element('engine'); + el.attrib.name = name; + if(spec){ + el.attrib.spec = spec; + } + this.doc.getroot().append(el); + }, + /** + * Removes all the engines with given name + * @param {String} name the engine name. + */ + removeEngine: function(name){ + var engines = this.doc.findall('./engine/[@name="' +name+'"]'); + for(var i=0; i < engines.length; i++){ + var children = this.doc.getroot().getchildren(); + var idx = children.indexOf(engines[i]); + if(idx > -1){ + children.splice(idx,1); + } + } + }, + getEngines: function(){ + var engines = this.doc.findall('./engine'); + return engines.map(function(engine){ + var spec = engine.attrib.spec || engine.attrib.version; + return { + 'name': engine.attrib.name, + 'spec': spec ? spec : null + }; + }); + }, + /* Get all the access tags */ + getAccesses: function() { + var accesses = this.doc.findall('./access'); + return accesses.map(function(access){ + var minimum_tls_version = access.attrib['minimum-tls-version']; /* String */ + var requires_forward_secrecy = access.attrib['requires-forward-secrecy']; /* Boolean */ + return { + 'origin': access.attrib.origin, + 'minimum_tls_version': minimum_tls_version, + 'requires_forward_secrecy' : requires_forward_secrecy + }; + }); + }, + /* Get all the allow-navigation tags */ + getAllowNavigations: function() { + var allow_navigations = this.doc.findall('./allow-navigation'); + return allow_navigations.map(function(allow_navigation){ + var minimum_tls_version = allow_navigation.attrib['minimum-tls-version']; /* String */ + var requires_forward_secrecy = allow_navigation.attrib['requires-forward-secrecy']; /* Boolean */ + return { + 'href': allow_navigation.attrib.href, + 'minimum_tls_version': minimum_tls_version, + 'requires_forward_secrecy' : requires_forward_secrecy + }; + }); + }, + write:function() { + fs.writeFileSync(this.path, this.doc.write({indent: 4}), 'utf-8'); + } +}; + +function featureToPlugin(featureElement) { + var plugin = {}; + plugin.variables = []; + var pluginVersion, + pluginSrc; + + var nodes = featureElement.findall('param'); + nodes.forEach(function (element) { + var n = element.attrib.name; + var v = element.attrib.value; + if (n === 'id') { + plugin.name = v; + } else if (n === 'version') { + pluginVersion = v; + } else if (n === 'url' || n === 'installPath') { + pluginSrc = v; + } else { + plugin.variables[n] = v; + } + }); + + var spec = pluginSrc || pluginVersion; + if (spec) { + plugin.spec = spec; + } + + return plugin; +} +module.exports = ConfigParser; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/ConfigParser/README.md ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/ConfigParser/README.md b/bin/node_modules/cordova-common/src/ConfigParser/README.md new file mode 100644 index 0000000..e5cd1bf --- /dev/null +++ b/bin/node_modules/cordova-common/src/ConfigParser/README.md @@ -0,0 +1,86 @@ +<!-- +# +# 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. +# +--> + +# Cordova-Lib + +## ConfigParser + +wraps a valid cordova config.xml file + +### Usage + +### Include the ConfigParser module in a projet + + var ConfigParser = require('cordova-lib').configparser; + +### Create a new ConfigParser + + var config = new ConfigParser('path/to/config/xml/'); + +### Utility Functions + +#### packageName(id) +returns document root 'id' attribute value +#### Usage + + config.packageName: function(id) + +/* + * sets document root element 'id' attribute to @id + * + * @id - new id value + * + */ +#### setPackageName(id) +set document root 'id' attribute to + function(id) { + this.doc.getroot().attrib['id'] = id; + }, + +### + name: function() { + return getNodeTextSafe(this.doc.find('name')); + }, + setName: function(name) { + var el = findOrCreate(this.doc, 'name'); + el.text = name; + }, + +### read the description element + + config.description() + + var text = "New and improved description of App" + setDescription(text) + +### version management + version() + android_versionCode() + ios_CFBundleVersion() + setVersion() + +### read author element + + config.author(); + +### read preference + + config.getPreference(name); http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/CordovaError/CordovaError.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/CordovaError/CordovaError.js b/bin/node_modules/cordova-common/src/CordovaError/CordovaError.js new file mode 100644 index 0000000..7262448 --- /dev/null +++ b/bin/node_modules/cordova-common/src/CordovaError/CordovaError.js @@ -0,0 +1,91 @@ +/** + 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. +*/ + +/* jshint proto:true */ + +var EOL = require('os').EOL; + +/** + * A derived exception class. See usage example in cli.js + * Based on: + * stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript/8460753#8460753 + * @param {String} message Error message + * @param {Number} [code=0] Error code + * @param {CordovaExternalToolErrorContext} [context] External tool error context object + * @constructor + */ +function CordovaError(message, code, context) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = message; + this.code = code || CordovaError.UNKNOWN_ERROR; + this.context = context; +} +CordovaError.prototype.__proto__ = Error.prototype; + +// TODO: Extend error codes according the projects specifics +CordovaError.UNKNOWN_ERROR = 0; +CordovaError.EXTERNAL_TOOL_ERROR = 1; + +/** + * Translates instance's error code number into error code name, e.g. 0 -> UNKNOWN_ERROR + * @returns {string} Error code string name + */ +CordovaError.prototype.getErrorCodeName = function() { + for(var key in CordovaError) { + if(CordovaError.hasOwnProperty(key)) { + if(CordovaError[key] === this.code) { + return key; + } + } + } +}; + +/** + * Converts CordovaError instance to string representation + * @param {Boolean} [isVerbose] Set up verbose mode. Used to provide more + * details including information about error code name and context + * @return {String} Stringified error representation + */ +CordovaError.prototype.toString = function(isVerbose) { + var message = '', codePrefix = ''; + + if(this.code !== CordovaError.UNKNOWN_ERROR) { + codePrefix = 'code: ' + this.code + (isVerbose ? (' (' + this.getErrorCodeName() + ')') : '') + ' '; + } + + if(this.code === CordovaError.EXTERNAL_TOOL_ERROR) { + if(typeof this.context !== 'undefined') { + if(isVerbose) { + message = codePrefix + EOL + this.context.toString(isVerbose) + '\n failed with an error: ' + + this.message + EOL + 'Stack trace: ' + this.stack; + } else { + message = codePrefix + '\'' + this.context.toString(isVerbose) + '\' ' + this.message; + } + } else { + message = 'External tool failed with an error: ' + this.message; + } + } else { + message = isVerbose ? codePrefix + this.stack : codePrefix + this.message; + } + + return message; +}; + +module.exports = CordovaError; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js b/bin/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js new file mode 100644 index 0000000..ca9a4aa --- /dev/null +++ b/bin/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js @@ -0,0 +1,48 @@ +/** + 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. + */ + +/* jshint proto:true */ + +var path = require('path'); + +/** + * @param {String} cmd Command full path + * @param {String[]} args Command args + * @param {String} [cwd] Command working directory + * @constructor + */ +function CordovaExternalToolErrorContext(cmd, args, cwd) { + this.cmd = cmd; + // Helper field for readability + this.cmdShortName = path.basename(cmd); + this.args = args; + this.cwd = cwd; +} + +CordovaExternalToolErrorContext.prototype.toString = function(isVerbose) { + if(isVerbose) { + return 'External tool \'' + this.cmdShortName + '\'' + + '\nCommand full path: ' + this.cmd + '\nCommand args: ' + this.args + + (typeof this.cwd !== 'undefined' ? '\nCommand cwd: ' + this.cwd : ''); + } + + return this.cmdShortName; +}; + +module.exports = CordovaExternalToolErrorContext; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/26cca47e/bin/node_modules/cordova-common/src/PlatformJson.js ---------------------------------------------------------------------- diff --git a/bin/node_modules/cordova-common/src/PlatformJson.js b/bin/node_modules/cordova-common/src/PlatformJson.js new file mode 100644 index 0000000..793e976 --- /dev/null +++ b/bin/node_modules/cordova-common/src/PlatformJson.js @@ -0,0 +1,155 @@ +/* + * 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. + * +*/ +/* jshint sub:true */ + +var fs = require('fs'); +var path = require('path'); +var shelljs = require('shelljs'); +var mungeutil = require('./ConfigChanges/munge-util'); +var pluginMappernto = require('cordova-registry-mapper').newToOld; +var pluginMapperotn = require('cordova-registry-mapper').oldToNew; + +function PlatformJson(filePath, platform, root) { + this.filePath = filePath; + this.platform = platform; + this.root = fix_munge(root || {}); +} + +PlatformJson.load = function(plugins_dir, platform) { + var filePath = path.join(plugins_dir, platform + '.json'); + var root = null; + if (fs.existsSync(filePath)) { + root = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } + return new PlatformJson(filePath, platform, root); +}; + +PlatformJson.prototype.save = function() { + shelljs.mkdir('-p', path.dirname(this.filePath)); + fs.writeFileSync(this.filePath, JSON.stringify(this.root, null, 4), 'utf-8'); +}; + +/** + * Indicates whether the specified plugin is installed as a top-level (not as + * dependency to others) + * @method function + * @param {String} pluginId A plugin id to check for. + * @return {Boolean} true if plugin installed as top-level, otherwise false. + */ +PlatformJson.prototype.isPluginTopLevel = function(pluginId) { + var installedPlugins = this.root.installed_plugins; + return installedPlugins[pluginId] || + installedPlugins[pluginMappernto[pluginId]] || + installedPlugins[pluginMapperotn[pluginId]]; +}; + +/** + * Indicates whether the specified plugin is installed as a dependency to other + * plugin. + * @method function + * @param {String} pluginId A plugin id to check for. + * @return {Boolean} true if plugin installed as a dependency, otherwise false. + */ +PlatformJson.prototype.isPluginDependent = function(pluginId) { + var dependentPlugins = this.root.dependent_plugins; + return dependentPlugins[pluginId] || + dependentPlugins[pluginMappernto[pluginId]] || + dependentPlugins[pluginMapperotn[pluginId]]; +}; + +/** + * Indicates whether plugin is installed either as top-level or as dependency. + * @method function + * @param {String} pluginId A plugin id to check for. + * @return {Boolean} true if plugin installed, otherwise false. + */ +PlatformJson.prototype.isPluginInstalled = function(pluginId) { + return this.isPluginTopLevel(pluginId) || + this.isPluginDependent(pluginId); +}; + +PlatformJson.prototype.addPlugin = function(pluginId, variables, isTopLevel) { + var pluginsList = isTopLevel ? + this.root.installed_plugins : + this.root.dependent_plugins; + + pluginsList[pluginId] = variables; + + return this; +}; + +PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) { + var pluginsList = isTopLevel ? + this.root.installed_plugins : + this.root.dependent_plugins; + + delete pluginsList[pluginId]; + + return this; +}; + +PlatformJson.prototype.addInstalledPluginToPrepareQueue = function(pluginDirName, vars, is_top_level) { + this.root.prepare_queue.installed.push({'plugin':pluginDirName, 'vars':vars, 'topLevel':is_top_level}); +}; + +PlatformJson.prototype.addUninstalledPluginToPrepareQueue = function(pluginId, is_top_level) { + this.root.prepare_queue.uninstalled.push({'plugin':pluginId, 'id':pluginId, 'topLevel':is_top_level}); +}; + +/** + * Moves plugin, specified by id to top-level plugins. If plugin is top-level + * already, then does nothing. + * @method function + * @param {String} pluginId A plugin id to make top-level. + * @return {PlatformJson} PlatformJson instance. + */ +PlatformJson.prototype.makeTopLevel = function(pluginId) { + var plugin = this.root.dependent_plugins[pluginId]; + if (plugin) { + delete this.root.dependent_plugins[pluginId]; + this.root.installed_plugins[pluginId] = plugin; + } + return this; +}; + +// convert a munge from the old format ([file][parent][xml] = count) to the current one +function fix_munge(root) { + root.prepare_queue = root.prepare_queue || {installed:[], uninstalled:[]}; + root.config_munge = root.config_munge || {files: {}}; + root.installed_plugins = root.installed_plugins || {}; + root.dependent_plugins = root.dependent_plugins || {}; + + var munge = root.config_munge; + if (!munge.files) { + var new_munge = { files: {} }; + for (var file in munge) { + for (var selector in munge[file]) { + for (var xml_child in munge[file][selector]) { + var val = parseInt(munge[file][selector][xml_child]); + for (var i = 0; i < val; i++) { + mungeutil.deep_add(new_munge, [file, selector, { xml: xml_child, count: val }]); + } + } + } + } + root.config_munge = new_munge; + } + + return root; +} + +module.exports = PlatformJson; + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
