@@ -1,261 +1,19 @@ | |||
## Ignore Visual Studio temporary files, build results, and | |||
## files generated by popular Visual Studio add-ons. | |||
# User-specific files | |||
*.suo | |||
*.user | |||
*.userosscache | |||
*.sln.docstates | |||
# User-specific files (MonoDevelop/Xamarin Studio) | |||
*.userprefs | |||
# Build results | |||
[Dd]ebug/ | |||
[Dd]ebugPublic/ | |||
[Rr]elease/ | |||
[Rr]eleases/ | |||
x64/ | |||
x86/ | |||
bld/ | |||
[Bb]in/ | |||
[Oo]bj/ | |||
# Visual Studio 2015 cache/options directory | |||
.vs/ | |||
.vs | |||
# Uncomment if you have tasks that create the project's static files in wwwroot | |||
#wwwroot/ | |||
# MSTest test Results | |||
[Tt]est[Rr]esult*/ | |||
[Bb]uild[Ll]og.* | |||
# NUNIT | |||
*.VisualState.xml | |||
TestResult.xml | |||
# Build Results of an ATL Project | |||
[Dd]ebugPS/ | |||
[Rr]eleasePS/ | |||
dlldata.c | |||
# DNX | |||
project.lock.json | |||
project.fragment.lock.json | |||
artifacts/ | |||
*_i.c | |||
*_p.c | |||
*_i.h | |||
*.ilk | |||
*.meta | |||
*.obj | |||
*.pch | |||
*.pdb | |||
*.pgc | |||
*.pgd | |||
*.rsp | |||
*.sbr | |||
*.tlb | |||
*.tli | |||
*.tlh | |||
*.tmp | |||
*.tmp_proj | |||
*.log | |||
*.vspscc | |||
*.vssscc | |||
.builds | |||
*.pidb | |||
*.svclog | |||
*.scc | |||
# Chutzpah Test files | |||
_Chutzpah* | |||
# Visual C++ cache files | |||
ipch/ | |||
*.aps | |||
*.clw | |||
*.dca | |||
*.dsw | |||
*.hlp | |||
*.incr | |||
*.ncb | |||
*.opendb | |||
*.opensdf | |||
*.sdf | |||
*.cachefile | |||
*.VC.db | |||
*.VC.VC.opendb | |||
# Visual Studio profiler | |||
*.psess | |||
*.vsp | |||
*.vspx | |||
*.sap | |||
# TFS 2012 Local Workspace | |||
$tf/ | |||
# Guidance Automation Toolkit | |||
*.gpState | |||
# ReSharper is a .NET coding add-in | |||
_ReSharper*/ | |||
*.[Rr]e[Ss]harper | |||
*.DotSettings.user | |||
# JustCode is a .NET coding add-in | |||
.JustCode | |||
# TeamCity is a build add-in | |||
_TeamCity* | |||
# DotCover is a Code Coverage Tool | |||
*.dotCover | |||
# NCrunch | |||
_NCrunch_* | |||
.*crunch*.local.xml | |||
nCrunchTemp_* | |||
# MightyMoose | |||
*.mm.* | |||
AutoTest.Net/ | |||
# Web workbench (sass) | |||
.sass-cache/ | |||
# Installshield output folder | |||
[Ee]xpress/ | |||
# DocProject is a documentation generator add-in | |||
DocProject/buildhelp/ | |||
DocProject/Help/*.HxT | |||
DocProject/Help/*.HxC | |||
DocProject/Help/*.hhc | |||
DocProject/Help/*.hhk | |||
DocProject/Help/*.hhp | |||
DocProject/Help/Html2 | |||
DocProject/Help/html | |||
# Click-Once directory | |||
publish/ | |||
# Publish Web Output | |||
*.[Pp]ublish.xml | |||
*.azurePubxml | |||
# TODO: Comment the next line if you want to checkin your web deploy settings | |||
# but database connection strings (with potential passwords) will be unencrypted | |||
#*.pubxml | |||
*.publishproj | |||
# Microsoft Azure Web App publish settings. Comment the next line if you want to | |||
# checkin your Azure Web App publish settings, but sensitive information contained | |||
# in these scripts will be unencrypted | |||
PublishScripts/ | |||
# NuGet Packages | |||
# The packages folder can be ignored because of Package Restore | |||
# except build/, which is used as an MSBuild target. | |||
# Uncomment if necessary however generally it will be regenerated when needed | |||
#!**/packages/repositories.config | |||
# NuGet v3's project.json files produces more ignoreable files | |||
*.nuget.props | |||
*.nuget.targets | |||
# Microsoft Azure Build Output | |||
csx/ | |||
*.build.csdef | |||
# Microsoft Azure Emulator | |||
ecf/ | |||
rcf/ | |||
# Windows Store app package directories and files | |||
AppPackages/ | |||
BundleArtifacts/ | |||
Package.StoreAssociation.xml | |||
_pkginfo.txt | |||
# Visual Studio cache files | |||
# files ending in .cache can be ignored | |||
*.[Cc]ache | |||
# but keep track of directories ending in .cache | |||
!*.[Cc]ache/ | |||
# Others | |||
ClientBin/ | |||
~$* | |||
*~ | |||
*.dbmdl | |||
*.dbproj.schemaview | |||
*.jfm | |||
*.pfx | |||
*.publishsettings | |||
node_modules/ | |||
orleans.codegen.cs | |||
# Since there are multiple workflows, uncomment next line to ignore bower_components | |||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) | |||
#bower_components/ | |||
# RIA/Silverlight projects | |||
Generated_Code/ | |||
# Backup & report files from converting an old project file | |||
# to a newer Visual Studio version. Backup files are not needed, | |||
# because we have git ;-) | |||
_UpgradeReport_Files/ | |||
Backup*/ | |||
UpgradeLog*.XML | |||
UpgradeLog*.htm | |||
# SQL Server files | |||
*.mdf | |||
*.ldf | |||
# Business Intelligence projects | |||
*.rdl.data | |||
*.bim.layout | |||
*.bim_*.settings | |||
# Microsoft Fakes | |||
FakesAssemblies/ | |||
# GhostDoc plugin setting file | |||
*.GhostDoc.xml | |||
# Node.js Tools for Visual Studio | |||
.ntvs_analysis.dat | |||
# Visual Studio 6 build log | |||
*.plg | |||
# Visual Studio 6 workspace options file | |||
*.opt | |||
# Visual Studio LightSwitch build output | |||
**/*.HTMLClient/GeneratedArtifacts | |||
**/*.DesktopClient/GeneratedArtifacts | |||
**/*.DesktopClient/ModelManifest.xml | |||
**/*.Server/GeneratedArtifacts | |||
**/*.Server/ModelManifest.xml | |||
_Pvt_Extensions | |||
# Paket dependency manager | |||
.paket/paket.exe | |||
paket-files/ | |||
# FAKE - F# Make | |||
.fake/ | |||
# JetBrains Rider | |||
.idea/ | |||
*.sln.iml | |||
# CodeRush | |||
.cr/ | |||
# Python Tools for Visual Studio (PTVS) | |||
__pycache__/ | |||
*.pyc | |||
*.pdb | |||
*.plg | |||
*.scc | |||
*.suo | |||
*.user | |||
*.vbw | |||
*.webuser | |||
bin/ | |||
debug/ | |||
obj/ | |||
release/ | |||
.vs/ |
@@ -0,0 +1,15 @@ | |||
The ISC License | |||
Copyright (c) Isaac Z. Schlueter and Contributors | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR | |||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
@@ -0,0 +1,23 @@ | |||
# abbrev-js | |||
Just like [ruby's Abbrev](http://apidock.com/ruby/Abbrev). | |||
Usage: | |||
var abbrev = require("abbrev"); | |||
abbrev("foo", "fool", "folding", "flop"); | |||
// returns: | |||
{ fl: 'flop' | |||
, flo: 'flop' | |||
, flop: 'flop' | |||
, fol: 'folding' | |||
, fold: 'folding' | |||
, foldi: 'folding' | |||
, foldin: 'folding' | |||
, folding: 'folding' | |||
, foo: 'foo' | |||
, fool: 'fool' | |||
} | |||
This is handy for command-line scripts, or other cases where you want to be able to accept shorthands. |
@@ -0,0 +1,62 @@ | |||
module.exports = exports = abbrev.abbrev = abbrev | |||
abbrev.monkeyPatch = monkeyPatch | |||
function monkeyPatch () { | |||
Object.defineProperty(Array.prototype, 'abbrev', { | |||
value: function () { return abbrev(this) }, | |||
enumerable: false, configurable: true, writable: true | |||
}) | |||
Object.defineProperty(Object.prototype, 'abbrev', { | |||
value: function () { return abbrev(Object.keys(this)) }, | |||
enumerable: false, configurable: true, writable: true | |||
}) | |||
} | |||
function abbrev (list) { | |||
if (arguments.length !== 1 || !Array.isArray(list)) { | |||
list = Array.prototype.slice.call(arguments, 0) | |||
} | |||
for (var i = 0, l = list.length, args = [] ; i < l ; i ++) { | |||
args[i] = typeof list[i] === "string" ? list[i] : String(list[i]) | |||
} | |||
// sort them lexicographically, so that they're next to their nearest kin | |||
args = args.sort(lexSort) | |||
// walk through each, seeing how much it has in common with the next and previous | |||
var abbrevs = {} | |||
, prev = "" | |||
for (var i = 0, l = args.length ; i < l ; i ++) { | |||
var current = args[i] | |||
, next = args[i + 1] || "" | |||
, nextMatches = true | |||
, prevMatches = true | |||
if (current === next) continue | |||
for (var j = 0, cl = current.length ; j < cl ; j ++) { | |||
var curChar = current.charAt(j) | |||
nextMatches = nextMatches && curChar === next.charAt(j) | |||
prevMatches = prevMatches && curChar === prev.charAt(j) | |||
if (!nextMatches && !prevMatches) { | |||
j ++ | |||
break | |||
} | |||
} | |||
prev = current | |||
if (j === cl) { | |||
abbrevs[current] = current | |||
continue | |||
} | |||
for (var a = current.substr(0, j) ; j <= cl ; j ++) { | |||
abbrevs[a] = current | |||
a += current.charAt(j) | |||
} | |||
} | |||
return abbrevs | |||
} | |||
function lexSort (a, b) { | |||
return a === b ? 0 : a > b ? 1 : -1 | |||
} |
@@ -0,0 +1,90 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "abbrev@1", | |||
"scope": null, | |||
"escapedName": "abbrev", | |||
"name": "abbrev", | |||
"rawSpec": "1", | |||
"spec": ">=1.0.0 <2.0.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\nopt" | |||
] | |||
], | |||
"_from": "abbrev@>=1.0.0 <2.0.0", | |||
"_id": "abbrev@1.0.9", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/abbrev", | |||
"_nodeVersion": "4.4.4", | |||
"_npmOperationalInternal": { | |||
"host": "packages-16-east.internal.npmjs.com", | |||
"tmp": "tmp/abbrev-1.0.9.tgz_1466016055839_0.7825860097073019" | |||
}, | |||
"_npmUser": { | |||
"name": "isaacs", | |||
"email": "i@izs.me" | |||
}, | |||
"_npmVersion": "3.9.1", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "abbrev@1", | |||
"scope": null, | |||
"escapedName": "abbrev", | |||
"name": "abbrev", | |||
"rawSpec": "1", | |||
"spec": ">=1.0.0 <2.0.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/nopt" | |||
], | |||
"_resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", | |||
"_shasum": "91b4792588a7738c25f35dd6f63752a2f8776135", | |||
"_shrinkwrap": null, | |||
"_spec": "abbrev@1", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\nopt", | |||
"author": { | |||
"name": "Isaac Z. Schlueter", | |||
"email": "i@izs.me" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/isaacs/abbrev-js/issues" | |||
}, | |||
"dependencies": {}, | |||
"description": "Like ruby's abbrev module, but in js", | |||
"devDependencies": { | |||
"tap": "^5.7.2" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "91b4792588a7738c25f35dd6f63752a2f8776135", | |||
"tarball": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" | |||
}, | |||
"files": [ | |||
"abbrev.js" | |||
], | |||
"gitHead": "c386cd9dbb1d8d7581718c54d4ba944cc9298d6f", | |||
"homepage": "https://github.com/isaacs/abbrev-js#readme", | |||
"license": "ISC", | |||
"main": "abbrev.js", | |||
"maintainers": [ | |||
{ | |||
"name": "isaacs", | |||
"email": "i@izs.me" | |||
} | |||
], | |||
"name": "abbrev", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git+ssh://git@github.com/isaacs/abbrev-js.git" | |||
}, | |||
"scripts": { | |||
"test": "tap test.js --cov" | |||
}, | |||
"version": "1.0.9" | |||
} |
@@ -0,0 +1,4 @@ | |||
{ | |||
"laxcomma": true, | |||
"asi": true | |||
} |
@@ -0,0 +1 @@ | |||
node_modules |
@@ -0,0 +1,23 @@ | |||
0.3.1 / 2016-01-14 | |||
================== | |||
* add MIT LICENSE file (#23, @kasicka) | |||
* preserve chaining after redundant style-method calls (#19, @drewblaisdell) | |||
* package: add "license" field (#16, @BenjaminTsai) | |||
0.3.0 / 2014-05-09 | |||
================== | |||
* package: remove "test" script and "devDependencies" | |||
* package: remove "engines" section | |||
* pacakge: remove "bin" section | |||
* package: beautify | |||
* examples: remove `starwars` example (#15) | |||
* Documented goto, horizontalAbsolute, and eraseLine methods in README.md (#12, @Jammerwoch) | |||
* add `.jshintrc` file | |||
< 0.3.0 | |||
======= | |||
* Prehistoric |
@@ -0,0 +1,24 @@ | |||
(The MIT License) | |||
Copyright (c) 2012 Nathan Rajlich <nathan@tootallnate.net> | |||
Permission is hereby granted, free of charge, to any person | |||
obtaining a copy of this software and associated documentation | |||
files (the "Software"), to deal in the Software without | |||
restriction, including without limitation the rights to use, | |||
copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the | |||
Software is furnished to do so, subject to the following | |||
conditions: | |||
The above copyright notice and this permission notice shall be | |||
included in all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |||
OTHER DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,98 @@ | |||
ansi.js | |||
========= | |||
### Advanced ANSI formatting tool for Node.js | |||
`ansi.js` is a module for Node.js that provides an easy-to-use API for | |||
writing ANSI escape codes to `Stream` instances. ANSI escape codes are used to do | |||
fancy things in a terminal window, like render text in colors, delete characters, | |||
lines, the entire window, or hide and show the cursor, and lots more! | |||
#### Features: | |||
* 256 color support for the terminal! | |||
* Make a beep sound from your terminal! | |||
* Works with *any* writable `Stream` instance. | |||
* Allows you to move the cursor anywhere on the terminal window. | |||
* Allows you to delete existing contents from the terminal window. | |||
* Allows you to hide and show the cursor. | |||
* Converts CSS color codes and RGB values into ANSI escape codes. | |||
* Low-level; you are in control of when escape codes are used, it's not abstracted. | |||
Installation | |||
------------ | |||
Install with `npm`: | |||
``` bash | |||
$ npm install ansi | |||
``` | |||
Example | |||
------- | |||
``` js | |||
var ansi = require('ansi') | |||
, cursor = ansi(process.stdout) | |||
// You can chain your calls forever: | |||
cursor | |||
.red() // Set font color to red | |||
.bg.grey() // Set background color to grey | |||
.write('Hello World!') // Write 'Hello World!' to stdout | |||
.bg.reset() // Reset the bgcolor before writing the trailing \n, | |||
// to avoid Terminal glitches | |||
.write('\n') // And a final \n to wrap things up | |||
// Rendering modes are persistent: | |||
cursor.hex('#660000').bold().underline() | |||
// You can use the regular logging functions, text will be green: | |||
console.log('This is blood red, bold text') | |||
// To reset just the foreground color: | |||
cursor.fg.reset() | |||
console.log('This will still be bold') | |||
// to go to a location (x,y) on the console | |||
// note: 1-indexed, not 0-indexed: | |||
cursor.goto(10, 5).write('Five down, ten over') | |||
// to clear the current line: | |||
cursor.horizontalAbsolute(0).eraseLine().write('Starting again') | |||
// to go to a different column on the current line: | |||
cursor.horizontalAbsolute(5).write('column five') | |||
// Clean up after yourself! | |||
cursor.reset() | |||
``` | |||
License | |||
------- | |||
(The MIT License) | |||
Copyright (c) 2012 Nathan Rajlich <nathan@tootallnate.net> | |||
Permission is hereby granted, free of charge, to any person obtaining | |||
a copy of this software and associated documentation files (the | |||
'Software'), to deal in the Software without restriction, including | |||
without limitation the rights to use, copy, modify, merge, publish, | |||
distribute, sublicense, and/or sell copies of the Software, and to | |||
permit persons to whom the Software is furnished to do so, subject to | |||
the following conditions: | |||
The above copyright notice and this permission notice shall be | |||
included in all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | |||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,16 @@ | |||
#!/usr/bin/env node | |||
/** | |||
* Invokes the terminal "beep" sound once per second on every exact second. | |||
*/ | |||
process.title = 'beep' | |||
var cursor = require('../../')(process.stdout) | |||
function beep () { | |||
cursor.beep() | |||
setTimeout(beep, 1000 - (new Date()).getMilliseconds()) | |||
} | |||
setTimeout(beep, 1000 - (new Date()).getMilliseconds()) |
@@ -0,0 +1,15 @@ | |||
#!/usr/bin/env node | |||
/** | |||
* Like GNU ncurses "clear" command. | |||
* https://github.com/mscdex/node-ncurses/blob/master/deps/ncurses/progs/clear.c | |||
*/ | |||
process.title = 'clear' | |||
function lf () { return '\n' } | |||
require('../../')(process.stdout) | |||
.write(Array.apply(null, Array(process.stdout.getWindowSize()[1])).map(lf).join('')) | |||
.eraseData(2) | |||
.goto(1, 1) |
@@ -0,0 +1,32 @@ | |||
#!/usr/bin/env node | |||
var tty = require('tty') | |||
var cursor = require('../')(process.stdout) | |||
// listen for the queryPosition report on stdin | |||
process.stdin.resume() | |||
raw(true) | |||
process.stdin.once('data', function (b) { | |||
var match = /\[(\d+)\;(\d+)R$/.exec(b.toString()) | |||
if (match) { | |||
var xy = match.slice(1, 3).reverse().map(Number) | |||
console.error(xy) | |||
} | |||
// cleanup and close stdin | |||
raw(false) | |||
process.stdin.pause() | |||
}) | |||
// send the query position request code to stdout | |||
cursor.queryPosition() | |||
function raw (mode) { | |||
if (process.stdin.setRawMode) { | |||
process.stdin.setRawMode(mode) | |||
} else { | |||
tty.setRawMode(mode) | |||
} | |||
} |
@@ -0,0 +1,87 @@ | |||
#!/usr/bin/env node | |||
var assert = require('assert') | |||
, ansi = require('../../') | |||
function Progress (stream, width) { | |||
this.cursor = ansi(stream) | |||
this.delta = this.cursor.newlines | |||
this.width = width | 0 || 10 | |||
this.open = '[' | |||
this.close = ']' | |||
this.complete = '█' | |||
this.incomplete = '_' | |||
// initial render | |||
this.progress = 0 | |||
} | |||
Object.defineProperty(Progress.prototype, 'progress', { | |||
get: get | |||
, set: set | |||
, configurable: true | |||
, enumerable: true | |||
}) | |||
function get () { | |||
return this._progress | |||
} | |||
function set (v) { | |||
this._progress = Math.max(0, Math.min(v, 100)) | |||
var w = this.width - this.complete.length - this.incomplete.length | |||
, n = w * (this._progress / 100) | 0 | |||
, i = w - n | |||
, com = c(this.complete, n) | |||
, inc = c(this.incomplete, i) | |||
, delta = this.cursor.newlines - this.delta | |||
assert.equal(com.length + inc.length, w) | |||
if (delta > 0) { | |||
this.cursor.up(delta) | |||
this.delta = this.cursor.newlines | |||
} | |||
this.cursor | |||
.horizontalAbsolute(0) | |||
.eraseLine(2) | |||
.fg.white() | |||
.write(this.open) | |||
.fg.grey() | |||
.bold() | |||
.write(com) | |||
.resetBold() | |||
.write(inc) | |||
.fg.white() | |||
.write(this.close) | |||
.fg.reset() | |||
.write('\n') | |||
} | |||
function c (char, length) { | |||
return Array.apply(null, Array(length)).map(function () { | |||
return char | |||
}).join('') | |||
} | |||
// Usage | |||
var width = parseInt(process.argv[2], 10) || process.stdout.getWindowSize()[0] / 2 | |||
, p = new Progress(process.stdout, width) | |||
;(function tick () { | |||
p.progress += Math.random() * 5 | |||
p.cursor | |||
.eraseLine(2) | |||
.write('Progress: ') | |||
.bold().write(p.progress.toFixed(2)) | |||
.write('%') | |||
.resetBold() | |||
.write('\n') | |||
if (p.progress < 100) | |||
setTimeout(tick, 100) | |||
})() |
@@ -0,0 +1,405 @@ | |||
/** | |||
* References: | |||
* | |||
* - http://en.wikipedia.org/wiki/ANSI_escape_code | |||
* - http://www.termsys.demon.co.uk/vtansi.htm | |||
* | |||
*/ | |||
/** | |||
* Module dependencies. | |||
*/ | |||
var emitNewlineEvents = require('./newlines') | |||
, prefix = '\x1b[' // For all escape codes | |||
, suffix = 'm' // Only for color codes | |||
/** | |||
* The ANSI escape sequences. | |||
*/ | |||
var codes = { | |||
up: 'A' | |||
, down: 'B' | |||
, forward: 'C' | |||
, back: 'D' | |||
, nextLine: 'E' | |||
, previousLine: 'F' | |||
, horizontalAbsolute: 'G' | |||
, eraseData: 'J' | |||
, eraseLine: 'K' | |||
, scrollUp: 'S' | |||
, scrollDown: 'T' | |||
, savePosition: 's' | |||
, restorePosition: 'u' | |||
, queryPosition: '6n' | |||
, hide: '?25l' | |||
, show: '?25h' | |||
} | |||
/** | |||
* Rendering ANSI codes. | |||
*/ | |||
var styles = { | |||
bold: 1 | |||
, italic: 3 | |||
, underline: 4 | |||
, inverse: 7 | |||
} | |||
/** | |||
* The negating ANSI code for the rendering modes. | |||
*/ | |||
var reset = { | |||
bold: 22 | |||
, italic: 23 | |||
, underline: 24 | |||
, inverse: 27 | |||
} | |||
/** | |||
* The standard, styleable ANSI colors. | |||
*/ | |||
var colors = { | |||
white: 37 | |||
, black: 30 | |||
, blue: 34 | |||
, cyan: 36 | |||
, green: 32 | |||
, magenta: 35 | |||
, red: 31 | |||
, yellow: 33 | |||
, grey: 90 | |||
, brightBlack: 90 | |||
, brightRed: 91 | |||
, brightGreen: 92 | |||
, brightYellow: 93 | |||
, brightBlue: 94 | |||
, brightMagenta: 95 | |||
, brightCyan: 96 | |||
, brightWhite: 97 | |||
} | |||
/** | |||
* Creates a Cursor instance based off the given `writable stream` instance. | |||
*/ | |||
function ansi (stream, options) { | |||
if (stream._ansicursor) { | |||
return stream._ansicursor | |||
} else { | |||
return stream._ansicursor = new Cursor(stream, options) | |||
} | |||
} | |||
module.exports = exports = ansi | |||
/** | |||
* The `Cursor` class. | |||
*/ | |||
function Cursor (stream, options) { | |||
if (!(this instanceof Cursor)) { | |||
return new Cursor(stream, options) | |||
} | |||
if (typeof stream != 'object' || typeof stream.write != 'function') { | |||
throw new Error('a valid Stream instance must be passed in') | |||
} | |||
// the stream to use | |||
this.stream = stream | |||
// when 'enabled' is false then all the functions are no-ops except for write() | |||
this.enabled = options && options.enabled | |||
if (typeof this.enabled === 'undefined') { | |||
this.enabled = stream.isTTY | |||
} | |||
this.enabled = !!this.enabled | |||
// then `buffering` is true, then `write()` calls are buffered in | |||
// memory until `flush()` is invoked | |||
this.buffering = !!(options && options.buffering) | |||
this._buffer = [] | |||
// controls the foreground and background colors | |||
this.fg = this.foreground = new Colorer(this, 0) | |||
this.bg = this.background = new Colorer(this, 10) | |||
// defaults | |||
this.Bold = false | |||
this.Italic = false | |||
this.Underline = false | |||
this.Inverse = false | |||
// keep track of the number of "newlines" that get encountered | |||
this.newlines = 0 | |||
emitNewlineEvents(stream) | |||
stream.on('newline', function () { | |||
this.newlines++ | |||
}.bind(this)) | |||
} | |||
exports.Cursor = Cursor | |||
/** | |||
* Helper function that calls `write()` on the underlying Stream. | |||
* Returns `this` instead of the write() return value to keep | |||
* the chaining going. | |||
*/ | |||
Cursor.prototype.write = function (data) { | |||
if (this.buffering) { | |||
this._buffer.push(arguments) | |||
} else { | |||
this.stream.write.apply(this.stream, arguments) | |||
} | |||
return this | |||
} | |||
/** | |||
* Buffer `write()` calls into memory. | |||
* | |||
* @api public | |||
*/ | |||
Cursor.prototype.buffer = function () { | |||
this.buffering = true | |||
return this | |||
} | |||
/** | |||
* Write out the in-memory buffer. | |||
* | |||
* @api public | |||
*/ | |||
Cursor.prototype.flush = function () { | |||
this.buffering = false | |||
var str = this._buffer.map(function (args) { | |||
if (args.length != 1) throw new Error('unexpected args length! ' + args.length); | |||
return args[0]; | |||
}).join(''); | |||
this._buffer.splice(0); // empty | |||
this.write(str); | |||
return this | |||
} | |||
/** | |||
* The `Colorer` class manages both the background and foreground colors. | |||
*/ | |||
function Colorer (cursor, base) { | |||
this.current = null | |||
this.cursor = cursor | |||
this.base = base | |||
} | |||
exports.Colorer = Colorer | |||
/** | |||
* Write an ANSI color code, ensuring that the same code doesn't get rewritten. | |||
*/ | |||
Colorer.prototype._setColorCode = function setColorCode (code) { | |||
var c = String(code) | |||
if (this.current === c) return | |||
this.cursor.enabled && this.cursor.write(prefix + c + suffix) | |||
this.current = c | |||
return this | |||
} | |||
/** | |||
* Set up the positional ANSI codes. | |||
*/ | |||
Object.keys(codes).forEach(function (name) { | |||
var code = String(codes[name]) | |||
Cursor.prototype[name] = function () { | |||
var c = code | |||
if (arguments.length > 0) { | |||
c = toArray(arguments).map(Math.round).join(';') + code | |||
} | |||
this.enabled && this.write(prefix + c) | |||
return this | |||
} | |||
}) | |||
/** | |||
* Set up the functions for the rendering ANSI codes. | |||
*/ | |||
Object.keys(styles).forEach(function (style) { | |||
var name = style[0].toUpperCase() + style.substring(1) | |||
, c = styles[style] | |||
, r = reset[style] | |||
Cursor.prototype[style] = function () { | |||
if (this[name]) return this | |||
this.enabled && this.write(prefix + c + suffix) | |||
this[name] = true | |||
return this | |||
} | |||
Cursor.prototype['reset' + name] = function () { | |||
if (!this[name]) return this | |||
this.enabled && this.write(prefix + r + suffix) | |||
this[name] = false | |||
return this | |||
} | |||
}) | |||
/** | |||
* Setup the functions for the standard colors. | |||
*/ | |||
Object.keys(colors).forEach(function (color) { | |||
var code = colors[color] | |||
Colorer.prototype[color] = function () { | |||
this._setColorCode(this.base + code) | |||
return this.cursor | |||
} | |||
Cursor.prototype[color] = function () { | |||
return this.foreground[color]() | |||
} | |||
}) | |||
/** | |||
* Makes a beep sound! | |||
*/ | |||
Cursor.prototype.beep = function () { | |||
this.enabled && this.write('\x07') | |||
return this | |||
} | |||
/** | |||
* Moves cursor to specific position | |||
*/ | |||
Cursor.prototype.goto = function (x, y) { | |||
x = x | 0 | |||
y = y | 0 | |||
this.enabled && this.write(prefix + y + ';' + x + 'H') | |||
return this | |||
} | |||
/** | |||
* Resets the color. | |||
*/ | |||
Colorer.prototype.reset = function () { | |||
this._setColorCode(this.base + 39) | |||
return this.cursor | |||
} | |||
/** | |||
* Resets all ANSI formatting on the stream. | |||
*/ | |||
Cursor.prototype.reset = function () { | |||
this.enabled && this.write(prefix + '0' + suffix) | |||
this.Bold = false | |||
this.Italic = false | |||
this.Underline = false | |||
this.Inverse = false | |||
this.foreground.current = null | |||
this.background.current = null | |||
return this | |||
} | |||
/** | |||
* Sets the foreground color with the given RGB values. | |||
* The closest match out of the 216 colors is picked. | |||
*/ | |||
Colorer.prototype.rgb = function (r, g, b) { | |||
var base = this.base + 38 | |||
, code = rgb(r, g, b) | |||
this._setColorCode(base + ';5;' + code) | |||
return this.cursor | |||
} | |||
/** | |||
* Same as `cursor.fg.rgb(r, g, b)`. | |||
*/ | |||
Cursor.prototype.rgb = function (r, g, b) { | |||
return this.foreground.rgb(r, g, b) | |||
} | |||
/** | |||
* Accepts CSS color codes for use with ANSI escape codes. | |||
* For example: `#FF000` would be bright red. | |||
*/ | |||
Colorer.prototype.hex = function (color) { | |||
return this.rgb.apply(this, hex(color)) | |||
} | |||
/** | |||
* Same as `cursor.fg.hex(color)`. | |||
*/ | |||
Cursor.prototype.hex = function (color) { | |||
return this.foreground.hex(color) | |||
} | |||
// UTIL FUNCTIONS // | |||
/** | |||
* Translates a 255 RGB value to a 0-5 ANSI RGV value, | |||
* then returns the single ANSI color code to use. | |||
*/ | |||
function rgb (r, g, b) { | |||
var red = r / 255 * 5 | |||
, green = g / 255 * 5 | |||
, blue = b / 255 * 5 | |||
return rgb5(red, green, blue) | |||
} | |||
/** | |||
* Turns rgb 0-5 values into a single ANSI color code to use. | |||
*/ | |||
function rgb5 (r, g, b) { | |||
var red = Math.round(r) | |||
, green = Math.round(g) | |||
, blue = Math.round(b) | |||
return 16 + (red*36) + (green*6) + blue | |||
} | |||
/** | |||
* Accepts a hex CSS color code string (# is optional) and | |||
* translates it into an Array of 3 RGB 0-255 values, which | |||
* can then be used with rgb(). | |||
*/ | |||
function hex (color) { | |||
var c = color[0] === '#' ? color.substring(1) : color | |||
, r = c.substring(0, 2) | |||
, g = c.substring(2, 4) | |||
, b = c.substring(4, 6) | |||
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)] | |||
} | |||
/** | |||
* Turns an array-like object into a real array. | |||
*/ | |||
function toArray (a) { | |||
var i = 0 | |||
, l = a.length | |||
, rtn = [] | |||
for (; i<l; i++) { | |||
rtn.push(a[i]) | |||
} | |||
return rtn | |||
} |
@@ -0,0 +1,71 @@ | |||
/** | |||
* Accepts any node Stream instance and hijacks its "write()" function, | |||
* so that it can count any newlines that get written to the output. | |||
* | |||
* When a '\n' byte is encountered, then a "newline" event will be emitted | |||
* on the stream, with no arguments. It is up to the listeners to determine | |||
* any necessary deltas required for their use-case. | |||
* | |||
* Ex: | |||
* | |||
* var cursor = ansi(process.stdout) | |||
* , ln = 0 | |||
* process.stdout.on('newline', function () { | |||
* ln++ | |||
* }) | |||
*/ | |||
/** | |||
* Module dependencies. | |||
*/ | |||
var assert = require('assert') | |||
var NEWLINE = '\n'.charCodeAt(0) | |||
function emitNewlineEvents (stream) { | |||
if (stream._emittingNewlines) { | |||
// already emitting newline events | |||
return | |||
} | |||
var write = stream.write | |||
stream.write = function (data) { | |||
// first write the data | |||
var rtn = write.apply(stream, arguments) | |||
if (stream.listeners('newline').length > 0) { | |||
var len = data.length | |||
, i = 0 | |||
// now try to calculate any deltas | |||
if (typeof data == 'string') { | |||
for (; i<len; i++) { | |||
processByte(stream, data.charCodeAt(i)) | |||
} | |||
} else { | |||
// buffer | |||
for (; i<len; i++) { | |||
processByte(stream, data[i]) | |||
} | |||
} | |||
} | |||
return rtn | |||
} | |||
stream._emittingNewlines = true | |||
} | |||
module.exports = emitNewlineEvents | |||
/** | |||
* Processes an individual byte being written to a stream | |||
*/ | |||
function processByte (stream, b) { | |||
assert.equal(typeof b, 'number') | |||
if (b === NEWLINE) { | |||
stream.emit('newline') | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "ansi@^0.3.1", | |||
"scope": null, | |||
"escapedName": "ansi", | |||
"name": "ansi", | |||
"rawSpec": "^0.3.1", | |||
"spec": ">=0.3.1 <0.4.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\cordova-common" | |||
] | |||
], | |||
"_from": "ansi@>=0.3.1 <0.4.0", | |||
"_id": "ansi@0.3.1", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/ansi", | |||
"_nodeVersion": "5.3.0", | |||
"_npmUser": { | |||
"name": "tootallnate", | |||
"email": "nathan@tootallnate.net" | |||
}, | |||
"_npmVersion": "3.3.12", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "ansi@^0.3.1", | |||
"scope": null, | |||
"escapedName": "ansi", | |||
"name": "ansi", | |||
"rawSpec": "^0.3.1", | |||
"spec": ">=0.3.1 <0.4.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/cordova-common" | |||
], | |||
"_resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", | |||
"_shasum": "0c42d4fb17160d5a9af1e484bace1c66922c1b21", | |||
"_shrinkwrap": null, | |||
"_spec": "ansi@^0.3.1", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\cordova-common", | |||
"author": { | |||
"name": "Nathan Rajlich", | |||
"email": "nathan@tootallnate.net", | |||
"url": "http://tootallnate.net" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/TooTallNate/ansi.js/issues" | |||
}, | |||
"dependencies": {}, | |||
"description": "Advanced ANSI formatting tool for Node.js", | |||
"devDependencies": {}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "0c42d4fb17160d5a9af1e484bace1c66922c1b21", | |||
"tarball": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" | |||
}, | |||
"gitHead": "4d0d4af94e0bdaa648bd7262acd3bde4b98d5246", | |||
"homepage": "https://github.com/TooTallNate/ansi.js#readme", | |||
"keywords": [ | |||
"ansi", | |||
"formatting", | |||
"cursor", | |||
"color", | |||
"terminal", | |||
"rgb", | |||
"256", | |||
"stream" | |||
], | |||
"license": "MIT", | |||
"main": "./lib/ansi.js", | |||
"maintainers": [ | |||
{ | |||
"name": "TooTallNate", | |||
"email": "nathan@tootallnate.net" | |||
}, | |||
{ | |||
"name": "tootallnate", | |||
"email": "nathan@tootallnate.net" | |||
} | |||
], | |||
"name": "ansi", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/TooTallNate/ansi.js.git" | |||
}, | |||
"scripts": {}, | |||
"version": "0.3.1" | |||
} |
@@ -0,0 +1,5 @@ | |||
test | |||
.gitignore | |||
.travis.yml | |||
Makefile | |||
example.js |
@@ -0,0 +1,21 @@ | |||
(MIT) | |||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
this software and associated documentation files (the "Software"), to deal in | |||
the Software without restriction, including without limitation the rights to | |||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |||
of the Software, and to permit persons to whom the Software is furnished to do | |||
so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,91 @@ | |||
# balanced-match | |||
Match balanced string pairs, like `{` and `}` or `<b>` and `</b>`. Supports regular expressions as well! | |||
[![build status](https://secure.travis-ci.org/juliangruber/balanced-match.svg)](http://travis-ci.org/juliangruber/balanced-match) | |||
[![downloads](https://img.shields.io/npm/dm/balanced-match.svg)](https://www.npmjs.org/package/balanced-match) | |||
[![testling badge](https://ci.testling.com/juliangruber/balanced-match.png)](https://ci.testling.com/juliangruber/balanced-match) | |||
## Example | |||
Get the first matching pair of braces: | |||
```js | |||
var balanced = require('balanced-match'); | |||
console.log(balanced('{', '}', 'pre{in{nested}}post')); | |||
console.log(balanced('{', '}', 'pre{first}between{second}post')); | |||
console.log(balanced(/\s+\{\s+/, /\s+\}\s+/, 'pre { in{nest} } post')); | |||
``` | |||
The matches are: | |||
```bash | |||
$ node example.js | |||
{ start: 3, end: 14, pre: 'pre', body: 'in{nested}', post: 'post' } | |||
{ start: 3, | |||
end: 9, | |||
pre: 'pre', | |||
body: 'first', | |||
post: 'between{second}post' } | |||
{ start: 3, end: 17, pre: 'pre', body: 'in{nest}', post: 'post' } | |||
``` | |||
## API | |||
### var m = balanced(a, b, str) | |||
For the first non-nested matching pair of `a` and `b` in `str`, return an | |||
object with those keys: | |||
* **start** the index of the first match of `a` | |||
* **end** the index of the matching `b` | |||
* **pre** the preamble, `a` and `b` not included | |||
* **body** the match, `a` and `b` not included | |||
* **post** the postscript, `a` and `b` not included | |||
If there's no match, `undefined` will be returned. | |||
If the `str` contains more `a` than `b` / there are unmatched pairs, the first match that was closed will be used. For example, `{{a}` will match `['{', 'a', '']`. | |||
### var r = balanced.range(a, b, str) | |||
For the first non-nested matching pair of `a` and `b` in `str`, return an | |||
array with indexes: `[ <a index>, <b index> ]`. | |||
If there's no match, `undefined` will be returned. | |||
If the `str` contains more `a` than `b` / there are unmatched pairs, the first match that was closed will be used. For example, `{{a}` will match `[ 1, 3 ]`. | |||
## Installation | |||
With [npm](https://npmjs.org) do: | |||
```bash | |||
npm install balanced-match | |||
``` | |||
## License | |||
(MIT) | |||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
this software and associated documentation files (the "Software"), to deal in | |||
the Software without restriction, including without limitation the rights to | |||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |||
of the Software, and to permit persons to whom the Software is furnished to do | |||
so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,58 @@ | |||
module.exports = balanced; | |||
function balanced(a, b, str) { | |||
if (a instanceof RegExp) a = maybeMatch(a, str); | |||
if (b instanceof RegExp) b = maybeMatch(b, str); | |||
var r = range(a, b, str); | |||
return r && { | |||
start: r[0], | |||
end: r[1], | |||
pre: str.slice(0, r[0]), | |||
body: str.slice(r[0] + a.length, r[1]), | |||
post: str.slice(r[1] + b.length) | |||
}; | |||
} | |||
function maybeMatch(reg, str) { | |||
var m = str.match(reg); | |||
return m ? m[0] : null; | |||
} | |||
balanced.range = range; | |||
function range(a, b, str) { | |||
var begs, beg, left, right, result; | |||
var ai = str.indexOf(a); | |||
var bi = str.indexOf(b, ai + 1); | |||
var i = ai; | |||
if (ai >= 0 && bi > 0) { | |||
begs = []; | |||
left = str.length; | |||
while (i < str.length && i >= 0 && ! result) { | |||
if (i == ai) { | |||
begs.push(i); | |||
ai = str.indexOf(a, i + 1); | |||
} else if (begs.length == 1) { | |||
result = [ begs.pop(), bi ]; | |||
} else { | |||
beg = begs.pop(); | |||
if (beg < left) { | |||
left = beg; | |||
right = bi; | |||
} | |||
bi = str.indexOf(b, i + 1); | |||
} | |||
i = ai < bi && ai >= 0 ? ai : bi; | |||
} | |||
if (begs.length) { | |||
result = [ left, right ]; | |||
} | |||
} | |||
return result; | |||
} |
@@ -0,0 +1,111 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "balanced-match@^0.4.1", | |||
"scope": null, | |||
"escapedName": "balanced-match", | |||
"name": "balanced-match", | |||
"rawSpec": "^0.4.1", | |||
"spec": ">=0.4.1 <0.5.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\brace-expansion" | |||
] | |||
], | |||
"_from": "balanced-match@>=0.4.1 <0.5.0", | |||
"_id": "balanced-match@0.4.1", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/balanced-match", | |||
"_nodeVersion": "6.0.0", | |||
"_npmOperationalInternal": { | |||
"host": "packages-12-west.internal.npmjs.com", | |||
"tmp": "tmp/balanced-match-0.4.1.tgz_1462129663650_0.39764496590942144" | |||
}, | |||
"_npmUser": { | |||
"name": "juliangruber", | |||
"email": "julian@juliangruber.com" | |||
}, | |||
"_npmVersion": "3.8.6", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "balanced-match@^0.4.1", | |||
"scope": null, | |||
"escapedName": "balanced-match", | |||
"name": "balanced-match", | |||
"rawSpec": "^0.4.1", | |||
"spec": ">=0.4.1 <0.5.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/brace-expansion" | |||
], | |||
"_resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.1.tgz", | |||
"_shasum": "19053e2e0748eadb379da6c09d455cf5e1039335", | |||
"_shrinkwrap": null, | |||
"_spec": "balanced-match@^0.4.1", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\brace-expansion", | |||
"author": { | |||
"name": "Julian Gruber", | |||
"email": "mail@juliangruber.com", | |||
"url": "http://juliangruber.com" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/juliangruber/balanced-match/issues" | |||
}, | |||
"dependencies": {}, | |||
"description": "Match balanced character pairs, like \"{\" and \"}\"", | |||
"devDependencies": { | |||
"tape": "~4.5.0" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "19053e2e0748eadb379da6c09d455cf5e1039335", | |||
"tarball": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.1.tgz" | |||
}, | |||
"gitHead": "7004b289baaaab6a832f4901735e29d37cc2a863", | |||
"homepage": "https://github.com/juliangruber/balanced-match", | |||
"keywords": [ | |||
"match", | |||
"regexp", | |||
"test", | |||
"balanced", | |||
"parse" | |||
], | |||
"license": "MIT", | |||
"main": "index.js", | |||
"maintainers": [ | |||
{ | |||
"name": "juliangruber", | |||
"email": "julian@juliangruber.com" | |||
} | |||
], | |||
"name": "balanced-match", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/juliangruber/balanced-match.git" | |||
}, | |||
"scripts": { | |||
"test": "make test" | |||
}, | |||
"testling": { | |||
"files": "test/*.js", | |||
"browsers": [ | |||
"ie/8..latest", | |||
"firefox/20..latest", | |||
"firefox/nightly", | |||
"chrome/25..latest", | |||
"chrome/canary", | |||
"opera/12..latest", | |||
"opera/next", | |||
"safari/5.1..latest", | |||
"ipad/6.0..latest", | |||
"iphone/6.0..latest", | |||
"android-browser/4.2..latest" | |||
] | |||
}, | |||
"version": "0.4.1" | |||
} |
@@ -0,0 +1,5 @@ | |||
language: node_js | |||
node_js: | |||
- "0.8" | |||
- "0.10" | |||
- "0.11" |
@@ -0,0 +1,21 @@ | |||
The MIT License (MIT) | |||
Copyright (c) 2014 | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. |
@@ -0,0 +1,31 @@ | |||
base64-js | |||
========= | |||
`base64-js` does basic base64 encoding/decoding in pure JS. | |||
[![build status](https://secure.travis-ci.org/beatgammit/base64-js.png)](http://travis-ci.org/beatgammit/base64-js) | |||
[![testling badge](https://ci.testling.com/beatgammit/base64-js.png)](https://ci.testling.com/beatgammit/base64-js) | |||
Many browsers already have base64 encoding/decoding functionality, but it is for text data, not all-purpose binary data. | |||
Sometimes encoding/decoding binary data in the browser is useful, and that is what this module does. | |||
## install | |||
With [npm](https://npmjs.org) do: | |||
`npm install base64-js` | |||
## methods | |||
`var base64 = require('base64-js')` | |||
`base64` has two exposed functions, `toByteArray` and `fromByteArray`, which both take a single argument. | |||
* `toByteArray` - Takes a base64 string and returns a byte array | |||
* `fromByteArray` - Takes a byte array and returns a base64 string | |||
## license | |||
MIT |
@@ -0,0 +1,19 @@ | |||
var random = require('crypto').pseudoRandomBytes | |||
var b64 = require('../') | |||
var fs = require('fs') | |||
var path = require('path') | |||
var data = random(1e6).toString('base64') | |||
//fs.readFileSync(path.join(__dirname, 'example.b64'), 'ascii').split('\n').join('') | |||
var start = Date.now() | |||
var raw = b64.toByteArray(data) | |||
var middle = Date.now() | |||
var data = b64.fromByteArray(raw) | |||
var end = Date.now() | |||
console.log('decode ms, decode ops/ms, encode ms, encode ops/ms') | |||
console.log( | |||
middle - start, data.length / (middle - start), | |||
end - middle, data.length / (end - middle)) | |||
//console.log(data) | |||
@@ -0,0 +1,124 @@ | |||
var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; | |||
;(function (exports) { | |||
'use strict'; | |||
var Arr = (typeof Uint8Array !== 'undefined') | |||
? Uint8Array | |||
: Array | |||
var PLUS = '+'.charCodeAt(0) | |||
var SLASH = '/'.charCodeAt(0) | |||
var NUMBER = '0'.charCodeAt(0) | |||
var LOWER = 'a'.charCodeAt(0) | |||
var UPPER = 'A'.charCodeAt(0) | |||
var PLUS_URL_SAFE = '-'.charCodeAt(0) | |||
var SLASH_URL_SAFE = '_'.charCodeAt(0) | |||
function decode (elt) { | |||
var code = elt.charCodeAt(0) | |||
if (code === PLUS || | |||
code === PLUS_URL_SAFE) | |||
return 62 // '+' | |||
if (code === SLASH || | |||
code === SLASH_URL_SAFE) | |||
return 63 // '/' | |||
if (code < NUMBER) | |||
return -1 //no match | |||
if (code < NUMBER + 10) | |||
return code - NUMBER + 26 + 26 | |||
if (code < UPPER + 26) | |||
return code - UPPER | |||
if (code < LOWER + 26) | |||
return code - LOWER + 26 | |||
} | |||
function b64ToByteArray (b64) { | |||
var i, j, l, tmp, placeHolders, arr | |||
if (b64.length % 4 > 0) { | |||
throw new Error('Invalid string. Length must be a multiple of 4') | |||
} | |||
// the number of equal signs (place holders) | |||
// if there are two placeholders, than the two characters before it | |||
// represent one byte | |||
// if there is only one, then the three characters before it represent 2 bytes | |||
// this is just a cheap hack to not do indexOf twice | |||
var len = b64.length | |||
placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0 | |||
// base64 is 4/3 + up to two characters of the original data | |||
arr = new Arr(b64.length * 3 / 4 - placeHolders) | |||
// if there are placeholders, only get up to the last complete 4 chars | |||
l = placeHolders > 0 ? b64.length - 4 : b64.length | |||
var L = 0 | |||
function push (v) { | |||
arr[L++] = v | |||
} | |||
for (i = 0, j = 0; i < l; i += 4, j += 3) { | |||
tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)) | |||
push((tmp & 0xFF0000) >> 16) | |||
push((tmp & 0xFF00) >> 8) | |||
push(tmp & 0xFF) | |||
} | |||
if (placeHolders === 2) { | |||
tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4) | |||
push(tmp & 0xFF) | |||
} else if (placeHolders === 1) { | |||
tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2) | |||
push((tmp >> 8) & 0xFF) | |||
push(tmp & 0xFF) | |||
} | |||
return arr | |||
} | |||
function uint8ToBase64 (uint8) { | |||
var i, | |||
extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes | |||
output = "", | |||
temp, length | |||
function encode (num) { | |||
return lookup.charAt(num) | |||
} | |||
function tripletToBase64 (num) { | |||
return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F) | |||
} | |||
// go through the array every three bytes, we'll deal with trailing stuff later | |||
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { | |||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) | |||
output += tripletToBase64(temp) | |||
} | |||
// pad the end with zeros, but make sure to not forget the extra bytes | |||
switch (extraBytes) { | |||
case 1: | |||
temp = uint8[uint8.length - 1] | |||
output += encode(temp >> 2) | |||
output += encode((temp << 4) & 0x3F) | |||
output += '==' | |||
break | |||
case 2: | |||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) | |||
output += encode(temp >> 10) | |||
output += encode((temp >> 4) & 0x3F) | |||
output += encode((temp << 2) & 0x3F) | |||
output += '=' | |||
break | |||
} | |||
return output | |||
} | |||
exports.toByteArray = b64ToByteArray | |||
exports.fromByteArray = uint8ToBase64 | |||
}(typeof exports === 'undefined' ? (this.base64js = {}) : exports)) |
@@ -0,0 +1,102 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "base64-js@0.0.8", | |||
"scope": null, | |||
"escapedName": "base64-js", | |||
"name": "base64-js", | |||
"rawSpec": "0.0.8", | |||
"spec": "0.0.8", | |||
"type": "version" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\plist" | |||
] | |||
], | |||
"_from": "base64-js@0.0.8", | |||
"_id": "base64-js@0.0.8", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/base64-js", | |||
"_nodeVersion": "0.10.35", | |||
"_npmUser": { | |||
"name": "feross", | |||
"email": "feross@feross.org" | |||
}, | |||
"_npmVersion": "2.1.16", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "base64-js@0.0.8", | |||
"scope": null, | |||
"escapedName": "base64-js", | |||
"name": "base64-js", | |||
"rawSpec": "0.0.8", | |||
"spec": "0.0.8", | |||
"type": "version" | |||
}, | |||
"_requiredBy": [ | |||
"/plist" | |||
], | |||
"_resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", | |||
"_shasum": "1101e9544f4a76b1bc3b26d452ca96d7a35e7978", | |||
"_shrinkwrap": null, | |||
"_spec": "base64-js@0.0.8", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\plist", | |||
"author": { | |||
"name": "T. Jameson Little", | |||
"email": "t.jameson.little@gmail.com" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/beatgammit/base64-js/issues" | |||
}, | |||
"dependencies": {}, | |||
"description": "Base64 encoding/decoding in pure JS", | |||
"devDependencies": { | |||
"tape": "~2.3.2" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "1101e9544f4a76b1bc3b26d452ca96d7a35e7978", | |||
"tarball": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz" | |||
}, | |||
"engines": { | |||
"node": ">= 0.4" | |||
}, | |||
"gitHead": "b4a8a5fa9b0caeddb5ad94dd1108253d8f2a315f", | |||
"homepage": "https://github.com/beatgammit/base64-js", | |||
"license": "MIT", | |||
"main": "lib/b64.js", | |||
"maintainers": [ | |||
{ | |||
"name": "beatgammit", | |||
"email": "t.jameson.little@gmail.com" | |||
}, | |||
{ | |||
"name": "feross", | |||
"email": "feross@feross.org" | |||
} | |||
], | |||
"name": "base64-js", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/beatgammit/base64-js.git" | |||
}, | |||
"scripts": { | |||
"test": "tape test/*.js" | |||
}, | |||
"testling": { | |||
"files": "test/*.js", | |||
"browsers": [ | |||
"ie/6..latest", | |||
"chrome/4..latest", | |||
"firefox/3..latest", | |||
"safari/5.1..latest", | |||
"opera/11.0..latest", | |||
"iphone/6", | |||
"ipad/6" | |||
] | |||
}, | |||
"version": "0.0.8" | |||
} |
@@ -0,0 +1,51 @@ | |||
var test = require('tape'), | |||
b64 = require('../lib/b64'), | |||
checks = [ | |||
'a', | |||
'aa', | |||
'aaa', | |||
'hi', | |||
'hi!', | |||
'hi!!', | |||
'sup', | |||
'sup?', | |||
'sup?!' | |||
]; | |||
test('convert to base64 and back', function (t) { | |||
t.plan(checks.length); | |||
for (var i = 0; i < checks.length; i++) { | |||
var check = checks[i], | |||
b64Str, | |||
arr, | |||
str; | |||
b64Str = b64.fromByteArray(map(check, function (char) { return char.charCodeAt(0); })); | |||
arr = b64.toByteArray(b64Str); | |||
str = map(arr, function (byte) { return String.fromCharCode(byte); }).join(''); | |||
t.equal(check, str, 'Checked ' + check); | |||
} | |||
}); | |||
function map (arr, callback) { | |||
var res = [], | |||
kValue, | |||
mappedValue; | |||
for (var k = 0, len = arr.length; k < len; k++) { | |||
if ((typeof arr === 'string' && !!arr.charAt(k))) { | |||
kValue = arr.charAt(k); | |||
mappedValue = callback(kValue, k, arr); | |||
res[k] = mappedValue; | |||
} else if (typeof arr !== 'string' && k in arr) { | |||
kValue = arr[k]; | |||
mappedValue = callback(kValue, k, arr); | |||
res[k] = mappedValue; | |||
} | |||
} | |||
return res; | |||
} |
@@ -0,0 +1,18 @@ | |||
var test = require('tape'), | |||
b64 = require('../lib/b64'); | |||
test('decode url-safe style base64 strings', function (t) { | |||
var expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; | |||
var actual = b64.toByteArray('//++/++/++//'); | |||
for (var i = 0; i < actual.length; i++) { | |||
t.equal(actual[i], expected[i]) | |||
} | |||
actual = b64.toByteArray('__--_--_--__'); | |||
for (var i = 0; i < actual.length; i++) { | |||
t.equal(actual[i], expected[i]) | |||
} | |||
t.end(); | |||
}); |
@@ -0,0 +1,33 @@ | |||
var bigInt=function(E){function k(a,b){if("undefined"===typeof a)return k[0];if("undefined"!==typeof b){var c;if(10===+b)c=l(a);else{c=b;var n=k[0],f=k[1],d=a.length;if(2<=c&&36>=c&&d<=ga/Math.log(c))c=new e(parseInt(a,c));else{c=l(c);var d=[],g,h="-"===a[0];for(g=h?1:0;g<a.length;g++){var q=a[g].toLowerCase(),u=q.charCodeAt(0);if(48<=u&&57>=u)d.push(l(q));else if(97<=u&&122>=u)d.push(l(q.charCodeAt(0)-87));else if("<"===q){q=g;do g++;while(">"!==a[g]);d.push(l(a.slice(q+1,g)))}else throw Error(q+ | |||
" is not a valid character");}d.reverse();for(g=0;g<d.length;g++)n=n.add(d[g].times(f)),f=f.times(c);c=h?n.negate():n}}return c}return l(a)}function d(a,b){this.value=a;this.sign=b;this.isSmall=!1}function e(a){this.value=a;this.sign=0>a;this.isSmall=!0}function w(a){return-9007199254740992<a&&9007199254740992>a}function z(a){return 1E7>a?[a]:1E14>a?[a%1E7,Math.floor(a/1E7)]:[a%1E7,Math.floor(a/1E7)%1E7,Math.floor(a/1E14)]}function y(a){D(a);var b=a.length;if(4>b&&0>A(a,P))switch(b){case 0:return 0; | |||
case 1:return a[0];case 2:return a[0]+1E7*a[1];default:return a[0]+1E7*(a[1]+1E7*a[2])}return a}function D(a){for(var b=a.length;0===a[--b];);a.length=b+1}function K(a){for(var b=Array(a),c=-1;++c<a;)b[c]=0;return b}function B(a){return 0<a?Math.floor(a):Math.ceil(a)}function S(a,b){var c=a.length,d=b.length,f=Array(c),m=0,g,e;for(e=0;e<d;e++)g=a[e]+b[e]+m,m=1E7<=g?1:0,f[e]=g-1E7*m;for(;e<c;)g=a[e]+m,m=1E7===g?1:0,f[e++]=g-1E7*m;0<m&&f.push(m);return f}function F(a,b){return a.length>=b.length?S(a, | |||
b):S(b,a)}function L(a,b){var c=a.length,d=Array(c),f,e;for(e=0;e<c;e++)f=a[e]-1E7+b,b=Math.floor(f/1E7),d[e]=f-1E7*b,b+=1;for(;0<b;)d[e++]=b%1E7,b=Math.floor(b/1E7);return d}function G(a,b){var c=a.length,d=b.length,f=Array(c),e=0,g,h;for(g=0;g<d;g++)h=a[g]-e-b[g],0>h?(h+=1E7,e=1):e=0,f[g]=h;for(g=d;g<c;g++){h=a[g]-e;if(0>h)h+=1E7;else{f[g++]=h;break}f[g]=h}for(;g<c;g++)f[g]=a[g];D(f);return f}function M(a,b,c){var n=a.length,f=Array(n);b=-b;var m,g;for(m=0;m<n;m++)g=a[m]+b,b=Math.floor(g/1E7),g%= | |||
1E7,f[m]=0>g?g+1E7:g;f=y(f);return"number"===typeof f?(c&&(f=-f),new e(f)):new d(f,c)}function Q(a,b){var c=a.length,d=b.length,f=K(c+d),e,g,h,k;for(h=0;h<c;++h){k=a[h];for(var l=0;l<d;++l)e=b[l],e=k*e+f[h+l],g=Math.floor(e/1E7),f[h+l]=e-1E7*g,f[h+l+1]+=g}D(f);return f}function H(a,b){var c=a.length,d=Array(c),f=0,e,g;for(g=0;g<c;g++)e=a[g]*b+f,f=Math.floor(e/1E7),d[g]=e-1E7*f;for(;0<f;)d[g++]=f%1E7,f=Math.floor(f/1E7);return d}function T(a,b){for(var c=[];0<b--;)c.push(0);return c.concat(a)}function N(a, | |||
b){var c=Math.max(a.length,b.length);if(30>=c)return Q(a,b);var c=Math.ceil(c/2),d=a.slice(c),f=a.slice(0,c),e=b.slice(c),g=b.slice(0,c),h=N(f,g),k=N(d,e),d=N(F(f,d),F(g,e)),c=F(F(h,T(G(G(d,h),k),c)),T(k,2*c));D(c);return c}function U(a,b,c){return 1E7>a?new d(H(b,a),c):new d(Q(b,z(a)),c)}function V(a){var b=a.length,c=K(b+b),d,f,e,g;for(e=0;e<b;e++){g=a[e];for(var h=0;h<b;h++)d=a[h],d=g*d+c[e+h],f=Math.floor(d/1E7),c[e+h]=d-1E7*f,c[e+h+1]+=f}D(c);return c}function W(a,b){var c=a.length,d=K(c),f, | |||
e;e=0;for(--c;0<=c;--c)e=1E7*e+a[c],f=B(e/b),e-=f*b,d[c]=f|0;return[d,e|0]}function I(a,b){var c,n=l(b),f=a.value;c=n.value;if(0===c)throw Error("Cannot divide by zero");if(a.isSmall)return n.isSmall?[new e(B(f/c)),new e(f%c)]:[k[0],a];if(n.isSmall){if(1===c)return[a,k[0]];if(-1==c)return[a.negate(),k[0]];c=Math.abs(c);if(1E7>c)return c=W(f,c),f=y(c[0]),c=c[1],a.sign&&(c=-c),"number"===typeof f?(a.sign!==n.sign&&(f=-f),[new e(f),new e(c)]):[new d(f,a.sign!==n.sign),new e(c)];c=z(c)}var m=A(f,c);if(-1=== | |||
m)return[k[0],a];if(0===m)return[k[a.sign===n.sign?1:-1],k[0]];if(200>=f.length+c.length){var g=c,h=f.length;c=g.length;var m=K(g.length),q=g[c-1],u=Math.ceil(1E7/(2*q)),f=H(f,u),g=H(g,u),p,r,x,t,v,w;f.length<=h&&f.push(0);g.push(0);q=g[c-1];for(p=h-c;0<=p;p--){h=9999999;f[p+c]!==q&&(h=Math.floor((1E7*f[p+c]+f[p+c-1])/q));x=r=0;v=g.length;for(t=0;t<v;t++)r+=h*g[t],w=Math.floor(r/1E7),x+=f[p+t]-(r-1E7*w),r=w,0>x?(f[p+t]=x+1E7,x=-1):(f[p+t]=x,x=0);for(;0!==x;){--h;for(t=r=0;t<v;t++)r+=f[p+t]-1E7+g[t], | |||
0>r?(f[p+t]=r+1E7,r=0):(f[p+t]=r,r=1);x+=r}m[p]=h}f=W(f,u)[0];c=[y(m),y(f)]}else{m=f.length;q=c.length;u=[];for(g=[];m;)if(g.unshift(f[--m]),0>A(g,c))u.push(0);else{h=g.length;p=1E7*g[h-1]+g[h-2];r=1E7*c[q-1]+c[q-2];h>q&&(p=1E7*(p+1));h=Math.ceil(p/r);do{p=H(c,h);if(0>=A(p,g))break;h--}while(h);u.push(h);g=G(g,p)}u.reverse();c=[y(u),y(g)]}f=c[0];n=a.sign!==n.sign;c=c[1];m=a.sign;"number"===typeof f?(n&&(f=-f),f=new e(f)):f=new d(f,n);"number"===typeof c?(m&&(c=-c),c=new e(c)):c=new d(c,m);return[f, | |||
c]}function A(a,b){if(a.length!==b.length)return a.length>b.length?1:-1;for(var c=a.length-1;0<=c;c--)if(a[c]!==b[c])return a[c]>b[c]?1:-1;return 0}function X(a){a=a.abs();if(a.isUnit())return!1;if(a.equals(2)||a.equals(3)||a.equals(5))return!0;if(a.isEven()||a.isDivisibleBy(3)||a.isDivisibleBy(5))return!1;if(a.lesser(25))return!0}function Y(a){return("number"===typeof a||"string"===typeof a)&&1E7>=+Math.abs(a)||a instanceof d&&1>=a.value.length}function R(a,b,c){b=l(b);var d=a.isNegative(),e=b.isNegative(), | |||
m=d?a.not():a,g=e?b.not():b;b=[];a=[];for(var h=!1,k=!1;!h||!k;)m.isZero()?(h=!0,b.push(d?1:0)):d?b.push(m.isEven()?1:0):b.push(m.isEven()?0:1),g.isZero()?(k=!0,a.push(e?1:0)):e?a.push(g.isEven()?1:0):a.push(g.isEven()?0:1),m=m.over(2),g=g.over(2);d=[];for(e=0;e<b.length;e++)d.push(c(b[e],a[e]));for(c=bigInt(d.pop()).negate().times(bigInt(2).pow(d.length));d.length;)c=c.add(bigInt(d.pop()).times(bigInt(2).pow(d.length)));return c}function O(a){a=a.value;a="number"===typeof a?a|1073741824:a[0]+1E7* | |||
a[1]|1073758208;return a&-a}function Z(a,b){a=l(a);b=l(b);return a.greater(b)?a:b}function aa(a,b){a=l(a);b=l(b);return a.lesser(b)?a:b}function ba(a,b){a=l(a).abs();b=l(b).abs();if(a.equals(b))return a;if(a.isZero())return b;if(b.isZero())return a;for(var c=k[1],d;a.isEven()&&b.isEven();)d=Math.min(O(a),O(b)),a=a.divide(d),b=b.divide(d),c=c.multiply(d);for(;a.isEven();)a=a.divide(O(a));do{for(;b.isEven();)b=b.divide(O(b));a.greater(b)&&(d=b,b=a,a=d);b=b.subtract(a)}while(!b.isZero());return c.isUnit()? | |||
a:a.multiply(c)}function ca(a){a=a.value;"number"===typeof a&&(a=[a]);return 1===a.length&&35>=a[0]?"0123456789abcdefghijklmnopqrstuvwxyz".charAt(a[0]):"<"+a+">"}function da(a,b){b=bigInt(b);if(b.isZero()){if(a.isZero())return"0";throw Error("Cannot convert nonzero numbers to base 0.");}if(b.equals(-1))return a.isZero()?"0":a.isNegative()?Array(1-a).join("10"):"1"+Array(+a).join("01");var c="";a.isNegative()&&b.isPositive()&&(c="-",a=a.abs());if(b.equals(1))return a.isZero()?"0":c+Array(+a+1).join(1); | |||
for(var d=[],e=a,k;e.isNegative()||0<=e.compareAbs(b);)k=e.divmod(b),e=k.quotient,k=k.remainder,k.isNegative()&&(k=b.minus(k).abs(),e=e.next()),d.push(ca(k));d.push(ca(e));return c+d.reverse().join("")}function ea(a){if(w(+a)){var b=+a;if(b===B(b))return new e(b);throw"Invalid integer: "+a;}(b="-"===a[0])&&(a=a.slice(1));var c=a.split(/e/i);if(2<c.length)throw Error("Invalid integer: "+c.join("e"));if(2===c.length){a=c[1];"+"===a[0]&&(a=a.slice(1));a=+a;if(a!==B(a)||!w(a))throw Error("Invalid integer: "+ | |||
a+" is not a valid exponent.");var c=c[0],n=c.indexOf(".");0<=n&&(a-=c.length-n-1,c=c.slice(0,n)+c.slice(n+1));if(0>a)throw Error("Cannot include negative exponent part for integers");a=c+=Array(a+1).join("0")}if(!/^([0-9][0-9]*)$/.test(a))throw Error("Invalid integer: "+a);for(var c=[],n=a.length,f=n-7;0<n;)c.push(+a.slice(f,n)),f-=7,0>f&&(f=0),n-=7;D(c);return new d(c,b)}function l(a){if("number"===typeof a){if(w(a)){if(a!==B(a))throw Error(a+" is not an integer.");a=new e(a)}else a=ea(a.toString()); | |||
return a}return"string"===typeof a?ea(a):a}var P=z(9007199254740992),ga=Math.log(9007199254740992);d.prototype=Object.create(k.prototype);e.prototype=Object.create(k.prototype);d.prototype.add=function(a){a=l(a);if(this.sign!==a.sign)return this.subtract(a.negate());var b=this.value,c=a.value;return a.isSmall?new d(L(b,Math.abs(c)),this.sign):new d(F(b,c),this.sign)};d.prototype.plus=d.prototype.add;e.prototype.add=function(a){a=l(a);var b=this.value;if(0>b!==a.sign)return this.subtract(a.negate()); | |||
var c=a.value;if(a.isSmall){if(w(b+c))return new e(b+c);c=z(Math.abs(c))}return new d(L(c,Math.abs(b)),0>b)};e.prototype.plus=e.prototype.add;d.prototype.subtract=function(a){var b=l(a);if(this.sign!==b.sign)return this.add(b.negate());a=this.value;var c=b.value;if(b.isSmall)return M(a,Math.abs(c),this.sign);b=this.sign;0<=A(a,c)?a=G(a,c):(a=G(c,a),b=!b);a=y(a);"number"===typeof a?(b&&(a=-a),a=new e(a)):a=new d(a,b);return a};d.prototype.minus=d.prototype.subtract;e.prototype.subtract=function(a){a= | |||
l(a);var b=this.value;if(0>b!==a.sign)return this.add(a.negate());var c=a.value;return a.isSmall?new e(b-c):M(c,Math.abs(b),0<=b)};e.prototype.minus=e.prototype.subtract;d.prototype.negate=function(){return new d(this.value,!this.sign)};e.prototype.negate=function(){var a=this.sign,b=new e(-this.value);b.sign=!a;return b};d.prototype.abs=function(){return new d(this.value,!1)};e.prototype.abs=function(){return new e(Math.abs(this.value))};d.prototype.multiply=function(a){var b=l(a);a=this.value;var c= | |||
b.value,e=this.sign!==b.sign;if(b.isSmall){if(0===c)return k[0];if(1===c)return this;if(-1===c)return this.negate();c=Math.abs(c);if(1E7>c)return new d(H(a,c),e);c=z(c)}var b=a.length,f=c.length;return 0<-.012*b-.012*f+1.5E-5*b*f?new d(N(a,c),e):new d(Q(a,c),e)};d.prototype.times=d.prototype.multiply;e.prototype._multiplyBySmall=function(a){return w(a.value*this.value)?new e(a.value*this.value):U(Math.abs(a.value),z(Math.abs(this.value)),this.sign!==a.sign)};d.prototype._multiplyBySmall=function(a){return 0=== | |||
a.value?k[0]:1===a.value?this:-1===a.value?this.negate():U(Math.abs(a.value),this.value,this.sign!==a.sign)};e.prototype.multiply=function(a){return l(a)._multiplyBySmall(this)};e.prototype.times=e.prototype.multiply;d.prototype.square=function(){return new d(V(this.value),!1)};e.prototype.square=function(){var a=this.value*this.value;return w(a)?new e(a):new d(V(z(Math.abs(this.value))),!1)};d.prototype.divmod=function(a){a=I(this,a);return{quotient:a[0],remainder:a[1]}};e.prototype.divmod=d.prototype.divmod; | |||
d.prototype.divide=function(a){return I(this,a)[0]};e.prototype.over=e.prototype.divide=d.prototype.over=d.prototype.divide;d.prototype.mod=function(a){return I(this,a)[1]};e.prototype.remainder=e.prototype.mod=d.prototype.remainder=d.prototype.mod;d.prototype.pow=function(a){var b=l(a),c=this.value;a=b.value;var d;if(0===a)return k[1];if(0===c)return k[0];if(1===c)return k[1];if(-1===c)return b.isEven()?k[1]:k[-1];if(b.sign)return k[0];if(!b.isSmall)throw Error("The exponent "+b.toString()+" is too large."); | |||
if(this.isSmall&&w(d=Math.pow(c,a)))return new e(B(d));d=this;for(b=k[1];;){a&1&&(b=b.times(d),--a);if(0===a)break;a/=2;d=d.square()}return b};e.prototype.pow=d.prototype.pow;d.prototype.modPow=function(a,b){a=l(a);b=l(b);if(b.isZero())throw Error("Cannot take modPow with modulus 0");for(var c=k[1],d=this.mod(b);a.isPositive();){if(d.isZero())return k[0];a.isOdd()&&(c=c.multiply(d).mod(b));a=a.divide(2);d=d.square().mod(b)}return c};e.prototype.modPow=d.prototype.modPow;d.prototype.compareAbs=function(a){a= | |||
l(a);return a.isSmall?1:A(this.value,a.value)};e.prototype.compareAbs=function(a){a=l(a);var b=Math.abs(this.value),c=a.value;return a.isSmall?(c=Math.abs(c),b===c?0:b>c?1:-1):-1};d.prototype.compare=function(a){if(Infinity===a)return-1;if(-Infinity===a)return 1;a=l(a);return this.sign!==a.sign?a.sign?1:-1:a.isSmall?this.sign?-1:1:A(this.value,a.value)*(this.sign?-1:1)};d.prototype.compareTo=d.prototype.compare;e.prototype.compare=function(a){if(Infinity===a)return-1;if(-Infinity===a)return 1;a=l(a); | |||
var b=this.value,c=a.value;return a.isSmall?b==c?0:b>c?1:-1:0>b!==a.sign?0>b?-1:1:0>b?1:-1};e.prototype.compareTo=e.prototype.compare;d.prototype.equals=function(a){return 0===this.compare(a)};e.prototype.eq=e.prototype.equals=d.prototype.eq=d.prototype.equals;d.prototype.notEquals=function(a){return 0!==this.compare(a)};e.prototype.neq=e.prototype.notEquals=d.prototype.neq=d.prototype.notEquals;d.prototype.greater=function(a){return 0<this.compare(a)};e.prototype.gt=e.prototype.greater=d.prototype.gt= | |||
d.prototype.greater;d.prototype.lesser=function(a){return 0>this.compare(a)};e.prototype.lt=e.prototype.lesser=d.prototype.lt=d.prototype.lesser;d.prototype.greaterOrEquals=function(a){return 0<=this.compare(a)};e.prototype.geq=e.prototype.greaterOrEquals=d.prototype.geq=d.prototype.greaterOrEquals;d.prototype.lesserOrEquals=function(a){return 0>=this.compare(a)};e.prototype.leq=e.prototype.lesserOrEquals=d.prototype.leq=d.prototype.lesserOrEquals;d.prototype.isEven=function(){return 0===(this.value[0]& | |||
1)};e.prototype.isEven=function(){return 0===(this.value&1)};d.prototype.isOdd=function(){return 1===(this.value[0]&1)};e.prototype.isOdd=function(){return 1===(this.value&1)};d.prototype.isPositive=function(){return!this.sign};e.prototype.isPositive=function(){return 0<this.value};d.prototype.isNegative=function(){return this.sign};e.prototype.isNegative=function(){return 0>this.value};d.prototype.isUnit=function(){return!1};e.prototype.isUnit=function(){return 1===Math.abs(this.value)};d.prototype.isZero= | |||
function(){return!1};e.prototype.isZero=function(){return 0===this.value};d.prototype.isDivisibleBy=function(a){a=l(a);var b=a.value;return 0===b?!1:1===b?!0:2===b?this.isEven():this.mod(a).equals(k[0])};e.prototype.isDivisibleBy=d.prototype.isDivisibleBy;d.prototype.isPrime=function(){var a=X(this);if(a!==E)return a;for(var a=this.abs(),b=a.prev(),c=[2,3,5,7,11,13,17,19],d=b,e,l,g,h;d.isEven();)d=d.divide(2);for(g=0;g<c.length;g++)if(h=bigInt(c[g]).modPow(d,a),!h.equals(k[1])&&!h.equals(b)){l=!0; | |||
for(e=d;l&&e.lesser(b);e=e.multiply(2))h=h.square().mod(a),h.equals(b)&&(l=!1);if(l)return!1}return!0};e.prototype.isPrime=d.prototype.isPrime;d.prototype.isProbablePrime=function(a){var b=X(this);if(b!==E)return b;b=this.abs();a=a===E?5:a;for(var c=0;c<a;c++)if(!bigInt.randBetween(2,b.minus(2)).modPow(b.prev(),b).isUnit())return!1;return!0};e.prototype.isProbablePrime=d.prototype.isProbablePrime;d.prototype.next=function(){var a=this.value;return this.sign?M(a,1,this.sign):new d(L(a,1),this.sign)}; | |||
e.prototype.next=function(){var a=this.value;return 9007199254740992>a+1?new e(a+1):new d(P,!1)};d.prototype.prev=function(){var a=this.value;return this.sign?new d(L(a,1),!0):M(a,1,this.sign)};e.prototype.prev=function(){var a=this.value;return-9007199254740992<a-1?new e(a-1):new d(P,!0)};for(var v=[1];1E7>=v[v.length-1];)v.push(2*v[v.length-1]);var J=v.length,fa=v[J-1];d.prototype.shiftLeft=function(a){if(!Y(a))throw Error(String(a)+" is too large for shifting.");a=+a;if(0>a)return this.shiftRight(-a); | |||
for(var b=this;a>=J;)b=b.multiply(fa),a-=J-1;return b.multiply(v[a])};e.prototype.shiftLeft=d.prototype.shiftLeft;d.prototype.shiftRight=function(a){var b;if(!Y(a))throw Error(String(a)+" is too large for shifting.");a=+a;if(0>a)return this.shiftLeft(-a);for(b=this;a>=J;){if(b.isZero())return b;b=I(b,fa);b=b[1].isNegative()?b[0].prev():b[0];a-=J-1}b=I(b,v[a]);return b[1].isNegative()?b[0].prev():b[0]};e.prototype.shiftRight=d.prototype.shiftRight;d.prototype.not=function(){return this.negate().prev()}; | |||
e.prototype.not=d.prototype.not;d.prototype.and=function(a){return R(this,a,function(a,c){return a&c})};e.prototype.and=d.prototype.and;d.prototype.or=function(a){return R(this,a,function(a,c){return a|c})};e.prototype.or=d.prototype.or;d.prototype.xor=function(a){return R(this,a,function(a,c){return a^c})};e.prototype.xor=d.prototype.xor;d.prototype.toString=function(a){a===E&&(a=10);if(10!==a)return da(this,a);a=this.value;for(var b=a.length,c=String(a[--b]),d;0<=--b;)d=String(a[b]),c+="0000000".slice(d.length)+ | |||
d;return(this.sign?"-":"")+c};e.prototype.toString=function(a){a===E&&(a=10);return 10!=a?da(this,a):String(this.value)};d.prototype.valueOf=function(){return+this.toString()};d.prototype.toJSNumber=d.prototype.valueOf;e.prototype.valueOf=function(){return this.value};e.prototype.toJSNumber=e.prototype.valueOf;for(var C=0;1E3>C;C++)k[C]=new e(C),0<C&&(k[-C]=new e(-C));k.one=k[1];k.zero=k[0];k.minusOne=k[-1];k.max=Z;k.min=aa;k.gcd=ba;k.lcm=function(a,b){a=l(a).abs();b=l(b).abs();return a.divide(ba(a, | |||
b)).multiply(b)};k.isInstance=function(a){return a instanceof d||a instanceof e};k.randBetween=function(a,b){a=l(a);b=l(b);var c=aa(a,b),k=Z(a,b).subtract(c);if(k.isSmall)return c.add(Math.round(Math.random()*k));for(var f=[],m=!0,g=k.value.length-1;0<=g;g--){var h=m?k.value[g]:1E7,q=B(Math.random()*h);f.unshift(q);q<h&&(m=!1)}f=y(f);return c.add("number"===typeof f?new e(f):new d(f,!1))};return k}();"undefined"!==typeof module&&module.hasOwnProperty("exports")&&(module.exports=bigInt); |
@@ -0,0 +1,24 @@ | |||
This is free and unencumbered software released into the public domain. | |||
Anyone is free to copy, modify, publish, use, compile, sell, or | |||
distribute this software, either in source code form or as a compiled | |||
binary, for any purpose, commercial or non-commercial, and by any | |||
means. | |||
In jurisdictions that recognize copyright laws, the author or authors | |||
of this software dedicate any and all copyright interest in the | |||
software to the public domain. We make this dedication for the benefit | |||
of the public at large and to the detriment of our heirs and | |||
successors. We intend this dedication to be an overt act of | |||
relinquishment in perpetuity of all present and future rights to this | |||
software under copyright law. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |||
OTHER DEALINGS IN THE SOFTWARE. | |||
For more information, please refer to <http://unlicense.org> |
@@ -0,0 +1,506 @@ | |||
# BigInteger.js [![Build Status][travis-img]][travis-url] [![Coverage Status][coveralls-img]][coveralls-url] [![Monthly Downloads][downloads-img]][downloads-url] | |||
[travis-url]: https://travis-ci.org/peterolson/BigInteger.js | |||
[travis-img]: https://travis-ci.org/peterolson/BigInteger.js.svg?branch=master | |||
[coveralls-url]: https://coveralls.io/github/peterolson/BigInteger.js?branch=master | |||
[coveralls-img]: https://coveralls.io/repos/peterolson/BigInteger.js/badge.svg?branch=master&service=github | |||
[downloads-url]: https://www.npmjs.com/package/big-integer | |||
[downloads-img]: https://img.shields.io/npm/dm/big-integer.svg | |||
**BigInteger.js** is an arbitrary-length integer library for Javascript, allowing arithmetic operations on integers of unlimited size, notwithstanding memory and time limitations. | |||
## Installation | |||
If you are using a browser, you can download [BigInteger.js from GitHub](http://peterolson.github.com/BigInteger.js/BigInteger.min.js) or just hotlink to it: | |||
<script src="http://peterolson.github.com/BigInteger.js/BigInteger.min.js"></script> | |||
If you are using node, you can install BigInteger with [npm](https://npmjs.org/). | |||
npm install big-integer | |||
Then you can include it in your code: | |||
var bigInt = require("big-integer"); | |||
## Usage | |||
### `bigInt(number, [base])` | |||
You can create a bigInt by calling the `bigInt` function. You can pass in | |||
- a string, which it will parse as an bigInt and throw an `"Invalid integer"` error if the parsing fails. | |||
- a Javascript number, which it will parse as an bigInt and throw an `"Invalid integer"` error if the parsing fails. | |||
- another bigInt. | |||
- nothing, and it will return `bigInt.zero`. | |||
If you provide a second parameter, then it will parse `number` as a number in base `base`. Note that `base` can be any bigInt (even negative or zero). The letters "a-z" and "A-Z" will be interpreted as the numbers 10 to 35. Higher digits can be specified in angle brackets (`<` and `>`). | |||
Examples: | |||
var zero = bigInt(); | |||
var ninetyThree = bigInt(93); | |||
var largeNumber = bigInt("75643564363473453456342378564387956906736546456235345"); | |||
var googol = bigInt("1e100"); | |||
var bigNumber = bigInt(largeNumber); | |||
var maximumByte = bigInt("FF", 16); | |||
var fiftyFiveGoogol = bigInt("<55>0", googol); | |||
Note that Javascript numbers larger than `9007199254740992` and smaller than `-9007199254740992` are not precisely represented numbers and will not produce exact results. If you are dealing with numbers outside that range, it is better to pass in strings. | |||
### Method Chaining | |||
Note that bigInt operations return bigInts, which allows you to chain methods, for example: | |||
var salary = bigInt(dollarsPerHour).times(hoursWorked).plus(randomBonuses) | |||
### Constants | |||
There are three named constants already stored that you do not have to construct with the `bigInt` function yourself: | |||
- `bigInt.one`, equivalent to `bigInt(1)` | |||
- `bigInt.zero`, equivalent to `bigInt(0)` | |||
- `bigInt.minusOne`, equivalent to `bigInt(-1)` | |||
The numbers from -999 to 999 are also already prestored and can be accessed using `bigInt[index]`, for example: | |||
- `bigInt[-999]`, equivalent to `bigInt(-999)` | |||
- `bigInt[256]`, equivalent to `bigInt(256)` | |||
### Methods | |||
#### `abs()` | |||
Returns the absolute value of a bigInt. | |||
- `bigInt(-45).abs()` => `45` | |||
- `bigInt(45).abs()` => `45` | |||
#### `add(number)` | |||
Performs addition. | |||
- `bigInt(5).add(7)` => `12` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Addition) | |||
#### `and(number)` | |||
Performs the bitwise AND operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement). | |||
- `bigInt(6).and(3)` => `2` | |||
- `bigInt(6).and(-3)` => `4` | |||
#### `compare(number)` | |||
Performs a comparison between two numbers. If the numbers are equal, it returns `0`. If the first number is greater, it returns `1`. If the first number is lesser, it returns `-1`. | |||
- `bigInt(5).compare(5)` => `0` | |||
- `bigInt(5).compare(4)` => `1` | |||
- `bigInt(4).compare(5)` => `-1` | |||
#### `compareAbs(number)` | |||
Performs a comparison between the absolute value of two numbers. | |||
- `bigInt(5).compareAbs(-5)` => `0` | |||
- `bigInt(5).compareAbs(4)` => `1` | |||
- `bigInt(4).compareAbs(-5)` => `-1` | |||
#### `compareTo(number)` | |||
Alias for the `compare` method. | |||
#### `divide(number)` | |||
Performs integer division, disregarding the remainder. | |||
- `bigInt(59).divide(5)` => `11` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division) | |||
#### `divmod(number)` | |||
Performs division and returns an object with two properties: `quotient` and `remainder`. The sign of the remainder will match the sign of the dividend. | |||
- `bigInt(59).divmod(5)` => `{quotient: bigInt(11), remainder: bigInt(4) }` | |||
- `bigInt(-5).divmod(2)` => `{quotient: bigInt(-2), remainder: bigInt(-1) }` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division) | |||
#### `eq(number)` | |||
Alias for the `equals` method. | |||
#### `equals(number)` | |||
Checks if two numbers are equal. | |||
- `bigInt(5).equals(5)` => `true` | |||
- `bigInt(4).equals(7)` => `false` | |||
#### `geq(number)` | |||
Alias for the `greaterOrEquals` method. | |||
#### `greater(number)` | |||
Checks if the first number is greater than the second. | |||
- `bigInt(5).greater(6)` => `false` | |||
- `bigInt(5).greater(5)` => `false` | |||
- `bigInt(5).greater(4)` => `true` | |||
#### `greaterOrEquals(number)` | |||
Checks if the first number is greater than or equal to the second. | |||
- `bigInt(5).greaterOrEquals(6)` => `false` | |||
- `bigInt(5).greaterOrEquals(5)` => `true` | |||
- `bigInt(5).greaterOrEquals(4)` => `true` | |||
#### `gt(number)` | |||
Alias for the `greater` method. | |||
#### `isDivisibleBy(number)` | |||
Returns `true` if the first number is divisible by the second number, `false` otherwise. | |||
- `bigInt(999).isDivisibleBy(333)` => `true` | |||
- `bigInt(99).isDivisibleBy(5)` => `false` | |||
#### `isEven()` | |||
Returns `true` if the number is even, `false` otherwise. | |||
- `bigInt(6).isEven()` => `true` | |||
- `bigInt(3).isEven()` => `false` | |||
#### `isNegative()` | |||
Returns `true` if the number is negative, `false` otherwise. | |||
Returns `false` for `0` and `-0`. | |||
- `bigInt(-23).isNegative()` => `true` | |||
- `bigInt(50).isNegative()` => `false` | |||
#### `isOdd()` | |||
Returns `true` if the number is odd, `false` otherwise. | |||
- `bigInt(13).isOdd()` => `true` | |||
- `bigInt(40).isOdd()` => `false` | |||
#### `isPositive()` | |||
Return `true` if the number is positive, `false` otherwise. | |||
Returns `false` for `0` and `-0`. | |||
- `bigInt(54).isPositive()` => `true` | |||
- `bigInt(-1).isPositive()` => `false` | |||
#### `isPrime()` | |||
Returns `true` if the number is prime, `false` otherwise. | |||
- `bigInt(5).isPrime()` => `true` | |||
- `bigInt(6).isPrime()` => `false` | |||
#### `isProbablePrime([iterations])` | |||
Returns `true` if the number is very likely to be positive, `false` otherwise. | |||
Argument is optional and determines the amount of iterations of the test (default: `5`). The more iterations, the lower chance of getting a false positive. | |||
This uses the [Fermat primality test](https://en.wikipedia.org/wiki/Fermat_primality_test). | |||
- `bigInt(5).isProbablePrime()` => `true` | |||
- `bigInt(49).isProbablePrime()` => `false` | |||
- `bigInt(1729).isProbablePrime(50)` => `false` | |||
Note that this function is not deterministic, since it relies on random sampling of factors, so the result for some numbers is not always the same. [Carmichael numbers](https://en.wikipedia.org/wiki/Carmichael_number) are particularly prone to give unreliable results. | |||
For example, `bigInt(1729).isProbablePrime()` returns `false` about 76% of the time and `true` about 24% of the time. The correct result is `false`. | |||
#### `isUnit()` | |||
Returns `true` if the number is `1` or `-1`, `false` otherwise. | |||
- `bigInt.one.isUnit()` => `true` | |||
- `bigInt.minusOne.isUnit()` => `true` | |||
- `bigInt(5).isUnit()` => `false` | |||
#### `isZero()` | |||
Return `true` if the number is `0` or `-0`, `false` otherwise. | |||
- `bigInt.zero.isZero()` => `true` | |||
- `bigInt("-0").isZero()` => `true` | |||
- `bigInt(50).isZero()` => `false` | |||
#### `leq(number)` | |||
Alias for the `lesserOrEquals` method. | |||
#### `lesser(number)` | |||
Checks if the first number is lesser than the second. | |||
- `bigInt(5).lesser(6)` => `true` | |||
- `bigInt(5).lesser(5)` => `false` | |||
- `bigInt(5).lesser(4)` => `false` | |||
#### `lesserOrEquals(number)` | |||
Checks if the first number is less than or equal to the second. | |||
- `bigInt(5).lesserOrEquals(6)` => `true` | |||
- `bigInt(5).lesserOrEquals(5)` => `true` | |||
- `bigInt(5).lesserOrEquals(4)` => `false` | |||
#### `lt(number)` | |||
Alias for the `lesser` method. | |||
#### `minus(number)` | |||
Alias for the `subtract` method. | |||
- `bigInt(3).minus(5)` => `-2` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Subtraction) | |||
#### `mod(number)` | |||
Performs division and returns the remainder, disregarding the quotient. The sign of the remainder will match the sign of the dividend. | |||
- `bigInt(59).mod(5)` => `4` | |||
- `bigInt(-5).mod(2)` => `-1` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division) | |||
#### `modPow(exp, mod)` | |||
Takes the number to the power `exp` modulo `mod`. | |||
- `bigInt(10).modPow(3, 30)` => `10` | |||
#### `multiply(number)` | |||
Performs multiplication. | |||
- `bigInt(111).multiply(111)` => `12321` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Multiplication) | |||
#### `neq(number)` | |||
Alias for the `notEquals` method. | |||
#### `next()` | |||
Adds one to the number. | |||
- `bigInt(6).next()` => `7` | |||
#### `not()` | |||
Performs the bitwise NOT operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement). | |||
- `bigInt(10).not()` => `-11` | |||
- `bigInt(0).not()` => `-1` | |||
#### `notEquals(number)` | |||
Checks if two numbers are not equal. | |||
- `bigInt(5).notEquals(5)` => `false` | |||
- `bigInt(4).notEquals(7)` => `true` | |||
#### `or(number)` | |||
Performs the bitwise OR operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement). | |||
- `bigInt(13).or(10)` => `15` | |||
- `bigInt(13).or(-8)` => `-3` | |||
#### `over(number)` | |||
Alias for the `divide` method. | |||
- `bigInt(59).over(5)` => `11` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division) | |||
#### `plus(number)` | |||
Alias for the `add` method. | |||
- `bigInt(5).plus(7)` => `12` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Addition) | |||
#### `pow(number)` | |||
Performs exponentiation. If the exponent is less than `0`, `pow` returns `0`. `bigInt.zero.pow(0)` returns `1`. | |||
- `bigInt(16).pow(16)` => `18446744073709551616` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Exponentiation) | |||
#### `prev(number)` | |||
Subtracts one from the number. | |||
- `bigInt(6).prev()` => `5` | |||
#### `remainder(number)` | |||
Alias for the `mod` method. | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Division) | |||
#### `shiftLeft(n)` | |||
Shifts the number left by `n` places in its binary representation. If a negative number is provided, it will shift right. Throws an error if `n` is outside of the range `[-9007199254740992, 9007199254740992]`. | |||
- `bigInt(8).shiftLeft(2)` => `32` | |||
- `bigInt(8).shiftLeft(-2)` => `2` | |||
#### `shiftRight(n)` | |||
Shifts the number right by `n` places in its binary representation. If a negative number is provided, it will shift left. Throws an error if `n` is outside of the range `[-9007199254740992, 9007199254740992]`. | |||
- `bigInt(8).shiftRight(2)` => `2` | |||
- `bigInt(8).shiftRight(-2)` => `32` | |||
#### `square()` | |||
Squares the number | |||
- `bigInt(3).square()` => `9` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Squaring) | |||
#### `subtract(number)` | |||
Performs subtraction. | |||
- `bigInt(3).subtract(5)` => `-2` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Subtraction) | |||
#### `times(number)` | |||
Alias for the `multiply` method. | |||
- `bigInt(111).times(111)` => `12321` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#Multiplication) | |||
#### `toJSNumber()` | |||
Converts a bigInt into a native Javascript number. Loses precision for numbers outside the range `[-9007199254740992, 9007199254740992]`. | |||
- `bigInt("18446744073709551616").toJSNumber()` => `18446744073709552000` | |||
#### `xor(number)` | |||
Performs the bitwise XOR operation. The operands are treated as if they were represented using [two's complement representation](http://en.wikipedia.org/wiki/Two%27s_complement). | |||
- `bigInt(12).xor(5)` => `9` | |||
- `bigInt(12).xor(-5)` => `-9` | |||
### Static Methods | |||
#### `gcd(a, b)` | |||
Finds the greatest common denominator of `a` and `b`. | |||
- `bigInt.gcd(42,56)` => `14` | |||
#### `isInstance(x)` | |||
Returns `true` if `x` is a BigInteger, `false` otherwise. | |||
- `bigInt.isInstance(bigInt(14))` => `true` | |||
- `bigInt.isInstance(14)` => `false` | |||
#### `lcm(a,b)` | |||
Finds the least common multiple of `a` and `b`. | |||
- `bigInt.lcm(21, 6)` => `42` | |||
#### `max(a,b)` | |||
Returns the largest of `a` and `b`. | |||
- `bigInt.max(77, 432)` => `432` | |||
#### `min(a,b)` | |||
Returns the smallest of `a` and `b`. | |||
- `bigInt.min(77, 432)` => `77` | |||
#### `randBetween(min, max)` | |||
Returns a random number between `min` and `max`. | |||
- `bigInt.randBetween("-1e100", "1e100")` => (for example) `8494907165436643479673097939554427056789510374838494147955756275846226209006506706784609314471378745` | |||
### Override Methods | |||
#### `toString(radix = 10)` | |||
Converts a bigInt to a string. There is an optional radix parameter (which defaults to 10) that converts the number to the given radix. Digits in the range `10-35` will use the letters `a-z`. | |||
- `bigInt("1e9").toString()` => `"1000000000"` | |||
- `bigInt("1e9").toString(16)` => `"3b9aca00"` | |||
**Note that arithmetical operators will trigger the `valueOf` function rather than the `toString` function.** When converting a bigInteger to a string, you should use the `toString` method or the `String` function instead of adding the empty string. | |||
- `bigInt("999999999999999999").toString()` => `"999999999999999999"` | |||
- `String(bigInt("999999999999999999"))` => `"999999999999999999"` | |||
- `bigInt("999999999999999999") + ""` => `1000000000000000000` | |||
Bases larger than 36 are supported. If a digit is greater than or equal to 36, it will be enclosed in angle brackets. | |||
- `bigInt(567890).toString(100)` => `"<56><78><90>"` | |||
Negative bases are also supported. | |||
- `bigInt(12345).toString(-10)` => `"28465"` | |||
Base 1 and base -1 are also supported. | |||
- `bigInt(-15).toString(1)` => `"-111111111111111"` | |||
- `bigInt(-15).toString(-1)` => `"101010101010101010101010101010"` | |||
Base 0 is only allowed for the number zero. | |||
- `bigInt(0).toString(0)` => `0` | |||
- `bigInt(1).toString(0)` => `Error: Cannot convert nonzero numbers to base 0.` | |||
[View benchmarks for this method](http://peterolson.github.io/BigInteger.js/benchmark/#toString) | |||
#### `valueOf()` | |||
Converts a bigInt to a native Javascript number. This override allows you to use native arithmetic operators without explicit conversion: | |||
- `bigInt("100") + bigInt("200") === 300; //true` | |||
## Contributors | |||
To contribute, just fork the project, make some changes, and submit a pull request. Please verify that the unit tests pass before submitting. | |||
The unit tests are contained in the `spec/spec.js` file. You can run them locally by opening the `spec/SpecRunner.html` or file or running `npm test`. You can also [run the tests online from GitHub](http://peterolson.github.io/BigInteger.js/spec/SpecRunner.html). | |||
There are performance benchmarks that can be viewed from the `benchmarks/index.html` page. You can [run them online from GitHub](http://peterolson.github.io/BigInteger.js/benchmark/). | |||
## License | |||
This project is public domain. For more details, read about the [Unlicense](http://unlicense.org/). |
@@ -0,0 +1,109 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "big-integer@^1.6.7", | |||
"scope": null, | |||
"escapedName": "big-integer", | |||
"name": "big-integer", | |||
"rawSpec": "^1.6.7", | |||
"spec": ">=1.6.7 <2.0.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\bplist-parser" | |||
] | |||
], | |||
"_from": "big-integer@>=1.6.7 <2.0.0", | |||
"_id": "big-integer@1.6.15", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/big-integer", | |||
"_nodeVersion": "0.12.3", | |||
"_npmOperationalInternal": { | |||
"host": "packages-12-west.internal.npmjs.com", | |||
"tmp": "tmp/big-integer-1.6.15.tgz_1460079231162_0.7087579960934818" | |||
}, | |||
"_npmUser": { | |||
"name": "peterolson", | |||
"email": "peter.e.c.olson+npm@gmail.com" | |||
}, | |||
"_npmVersion": "2.9.1", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "big-integer@^1.6.7", | |||
"scope": null, | |||
"escapedName": "big-integer", | |||
"name": "big-integer", | |||
"rawSpec": "^1.6.7", | |||
"spec": ">=1.6.7 <2.0.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/bplist-parser" | |||
], | |||
"_resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.15.tgz", | |||
"_shasum": "33d27d3b7388dfcc4b86d3130c10740cec01fb9e", | |||
"_shrinkwrap": null, | |||
"_spec": "big-integer@^1.6.7", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\bplist-parser", | |||
"author": { | |||
"name": "Peter Olson", | |||
"email": "peter.e.c.olson+npm@gmail.com" | |||
}, | |||
"bin": {}, | |||
"bugs": { | |||
"url": "https://github.com/peterolson/BigInteger.js/issues" | |||
}, | |||
"contributors": [], | |||
"dependencies": {}, | |||
"description": "An arbitrary length integer library for Javascript", | |||
"devDependencies": { | |||
"coveralls": "^2.11.4", | |||
"jasmine": "2.1.x", | |||
"jasmine-core": "^2.3.4", | |||
"karma": "^0.13.3", | |||
"karma-coverage": "^0.4.2", | |||
"karma-jasmine": "^0.3.6", | |||
"karma-phantomjs-launcher": "~0.1" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "33d27d3b7388dfcc4b86d3130c10740cec01fb9e", | |||
"tarball": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.15.tgz" | |||
}, | |||
"engines": { | |||
"node": ">=0.6" | |||
}, | |||
"gitHead": "cda5bcce74c3a4eb34951201d50c1b8776a56eca", | |||
"homepage": "https://github.com/peterolson/BigInteger.js#readme", | |||
"keywords": [ | |||
"math", | |||
"big", | |||
"bignum", | |||
"bigint", | |||
"biginteger", | |||
"integer", | |||
"arbitrary", | |||
"precision", | |||
"arithmetic" | |||
], | |||
"license": "Unlicense", | |||
"main": "./BigInteger", | |||
"maintainers": [ | |||
{ | |||
"name": "peterolson", | |||
"email": "peter.e.c.olson+npm@gmail.com" | |||
} | |||
], | |||
"name": "big-integer", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git+ssh://git@github.com/peterolson/BigInteger.js.git" | |||
}, | |||
"scripts": { | |||
"test": "karma start my.conf.js" | |||
}, | |||
"version": "1.6.15" | |||
} |
@@ -0,0 +1,8 @@ | |||
/build/* | |||
node_modules | |||
*.node | |||
*.sh | |||
*.swp | |||
.lock* | |||
npm-debug.log | |||
.idea |
@@ -0,0 +1,47 @@ | |||
bplist-parser | |||
============= | |||
Binary Mac OS X Plist (property list) parser. | |||
## Installation | |||
```bash | |||
$ npm install bplist-parser | |||
``` | |||
## Quick Examples | |||
```javascript | |||
var bplist = require('bplist-parser'); | |||
bplist.parseFile('myPlist.bplist', function(err, obj) { | |||
if (err) throw err; | |||
console.log(JSON.stringify(obj)); | |||
}); | |||
``` | |||
## License | |||
(The MIT License) | |||
Copyright (c) 2012 Near Infinity Corporation | |||
Permission is hereby granted, free of charge, to any person obtaining | |||
a copy of this software and associated documentation files (the | |||
"Software"), to deal in the Software without restriction, including | |||
without limitation the rights to use, copy, modify, merge, publish, | |||
distribute, sublicense, and/or sell copies of the Software, and to | |||
permit persons to whom the Software is furnished to do so, subject to | |||
the following conditions: | |||
The above copyright notice and this permission notice shall be | |||
included in all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,357 @@ | |||
'use strict'; | |||
// adapted from http://code.google.com/p/plist/source/browse/trunk/src/com/dd/plist/BinaryPropertyListParser.java | |||
var fs = require('fs'); | |||
var bigInt = require("big-integer"); | |||
var debug = false; | |||
exports.maxObjectSize = 100 * 1000 * 1000; // 100Meg | |||
exports.maxObjectCount = 32768; | |||
// EPOCH = new SimpleDateFormat("yyyy MM dd zzz").parse("2001 01 01 GMT").getTime(); | |||
// ...but that's annoying in a static initializer because it can throw exceptions, ick. | |||
// So we just hardcode the correct value. | |||
var EPOCH = 978307200000; | |||
// UID object definition | |||
var UID = exports.UID = function(id) { | |||
this.UID = id; | |||
} | |||
var parseFile = exports.parseFile = function (fileNameOrBuffer, callback) { | |||
function tryParseBuffer(buffer) { | |||
var err = null; | |||
var result; | |||
try { | |||
result = parseBuffer(buffer); | |||
} catch (ex) { | |||
err = ex; | |||
} | |||
callback(err, result); | |||
} | |||
if (Buffer.isBuffer(fileNameOrBuffer)) { | |||
return tryParseBuffer(fileNameOrBuffer); | |||
} else { | |||
fs.readFile(fileNameOrBuffer, function (err, data) { | |||
if (err) { return callback(err); } | |||
tryParseBuffer(data); | |||
}); | |||
} | |||
}; | |||
var parseBuffer = exports.parseBuffer = function (buffer) { | |||
var result = {}; | |||
// check header | |||
var header = buffer.slice(0, 'bplist'.length).toString('utf8'); | |||
if (header !== 'bplist') { | |||
throw new Error("Invalid binary plist. Expected 'bplist' at offset 0."); | |||
} | |||
// Handle trailer, last 32 bytes of the file | |||
var trailer = buffer.slice(buffer.length - 32, buffer.length); | |||
// 6 null bytes (index 0 to 5) | |||
var offsetSize = trailer.readUInt8(6); | |||
if (debug) { | |||
console.log("offsetSize: " + offsetSize); | |||
} | |||
var objectRefSize = trailer.readUInt8(7); | |||
if (debug) { | |||
console.log("objectRefSize: " + objectRefSize); | |||
} | |||
var numObjects = readUInt64BE(trailer, 8); | |||
if (debug) { | |||
console.log("numObjects: " + numObjects); | |||
} | |||
var topObject = readUInt64BE(trailer, 16); | |||
if (debug) { | |||
console.log("topObject: " + topObject); | |||
} | |||
var offsetTableOffset = readUInt64BE(trailer, 24); | |||
if (debug) { | |||
console.log("offsetTableOffset: " + offsetTableOffset); | |||
} | |||
if (numObjects > exports.maxObjectCount) { | |||
throw new Error("maxObjectCount exceeded"); | |||
} | |||
// Handle offset table | |||
var offsetTable = []; | |||
for (var i = 0; i < numObjects; i++) { | |||
var offsetBytes = buffer.slice(offsetTableOffset + i * offsetSize, offsetTableOffset + (i + 1) * offsetSize); | |||
offsetTable[i] = readUInt(offsetBytes, 0); | |||
if (debug) { | |||
console.log("Offset for Object #" + i + " is " + offsetTable[i] + " [" + offsetTable[i].toString(16) + "]"); | |||
} | |||
} | |||
// Parses an object inside the currently parsed binary property list. | |||
// For the format specification check | |||
// <a href="http://www.opensource.apple.com/source/CF/CF-635/CFBinaryPList.c"> | |||
// Apple's binary property list parser implementation</a>. | |||
function parseObject(tableOffset) { | |||
var offset = offsetTable[tableOffset]; | |||
var type = buffer[offset]; | |||
var objType = (type & 0xF0) >> 4; //First 4 bits | |||
var objInfo = (type & 0x0F); //Second 4 bits | |||
switch (objType) { | |||
case 0x0: | |||
return parseSimple(); | |||
case 0x1: | |||
return parseInteger(); | |||
case 0x8: | |||
return parseUID(); | |||
case 0x2: | |||
return parseReal(); | |||
case 0x3: | |||
return parseDate(); | |||
case 0x4: | |||
return parseData(); | |||
case 0x5: // ASCII | |||
return parsePlistString(); | |||
case 0x6: // UTF-16 | |||
return parsePlistString(true); | |||
case 0xA: | |||
return parseArray(); | |||
case 0xD: | |||
return parseDictionary(); | |||
default: | |||
throw new Error("Unhandled type 0x" + objType.toString(16)); | |||
} | |||
function parseSimple() { | |||
//Simple | |||
switch (objInfo) { | |||
case 0x0: // null | |||
return null; | |||
case 0x8: // false | |||
return false; | |||
case 0x9: // true | |||
return true; | |||
case 0xF: // filler byte | |||
return null; | |||
default: | |||
throw new Error("Unhandled simple type 0x" + objType.toString(16)); | |||
} | |||
} | |||
function bufferToHexString(buffer) { | |||
var str = ''; | |||
var i; | |||
for (i = 0; i < buffer.length; i++) { | |||
if (buffer[i] != 0x00) { | |||
break; | |||
} | |||
} | |||
for (; i < buffer.length; i++) { | |||
var part = '00' + buffer[i].toString(16); | |||
str += part.substr(part.length - 2); | |||
} | |||
return str; | |||
} | |||
function parseInteger() { | |||
var length = Math.pow(2, objInfo); | |||
if (length > 4) { | |||
var data = buffer.slice(offset + 1, offset + 1 + length); | |||
var str = bufferToHexString(data); | |||
return bigInt(str, 16); | |||
} if (length < exports.maxObjectSize) { | |||
return readUInt(buffer.slice(offset + 1, offset + 1 + length)); | |||
} else { | |||
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available."); | |||
} | |||
} | |||
function parseUID() { | |||
var length = objInfo + 1; | |||
if (length < exports.maxObjectSize) { | |||
return new UID(readUInt(buffer.slice(offset + 1, offset + 1 + length))); | |||
} else { | |||
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available."); | |||
} | |||
} | |||
function parseReal() { | |||
var length = Math.pow(2, objInfo); | |||
if (length < exports.maxObjectSize) { | |||
var realBuffer = buffer.slice(offset + 1, offset + 1 + length); | |||
if (length === 4) { | |||
return realBuffer.readFloatBE(0); | |||
} | |||
else if (length === 8) { | |||
return realBuffer.readDoubleBE(0); | |||
} | |||
} else { | |||
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available."); | |||
} | |||
} | |||
function parseDate() { | |||
if (objInfo != 0x3) { | |||
console.error("Unknown date type :" + objInfo + ". Parsing anyway..."); | |||
} | |||
var dateBuffer = buffer.slice(offset + 1, offset + 9); | |||
return new Date(EPOCH + (1000 * dateBuffer.readDoubleBE(0))); | |||
} | |||
function parseData() { | |||
var dataoffset = 1; | |||
var length = objInfo; | |||
if (objInfo == 0xF) { | |||
var int_type = buffer[offset + 1]; | |||
var intType = (int_type & 0xF0) / 0x10; | |||
if (intType != 0x1) { | |||
console.error("0x4: UNEXPECTED LENGTH-INT TYPE! " + intType); | |||
} | |||
var intInfo = int_type & 0x0F; | |||
var intLength = Math.pow(2, intInfo); | |||
dataoffset = 2 + intLength; | |||
if (intLength < 3) { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} else { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} | |||
} | |||
if (length < exports.maxObjectSize) { | |||
return buffer.slice(offset + dataoffset, offset + dataoffset + length); | |||
} else { | |||
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available."); | |||
} | |||
} | |||
function parsePlistString (isUtf16) { | |||
isUtf16 = isUtf16 || 0; | |||
var enc = "utf8"; | |||
var length = objInfo; | |||
var stroffset = 1; | |||
if (objInfo == 0xF) { | |||
var int_type = buffer[offset + 1]; | |||
var intType = (int_type & 0xF0) / 0x10; | |||
if (intType != 0x1) { | |||
console.err("UNEXPECTED LENGTH-INT TYPE! " + intType); | |||
} | |||
var intInfo = int_type & 0x0F; | |||
var intLength = Math.pow(2, intInfo); | |||
var stroffset = 2 + intLength; | |||
if (intLength < 3) { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} else { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} | |||
} | |||
// length is String length -> to get byte length multiply by 2, as 1 character takes 2 bytes in UTF-16 | |||
length *= (isUtf16 + 1); | |||
if (length < exports.maxObjectSize) { | |||
var plistString = new Buffer(buffer.slice(offset + stroffset, offset + stroffset + length)); | |||
if (isUtf16) { | |||
plistString = swapBytes(plistString); | |||
enc = "ucs2"; | |||
} | |||
return plistString.toString(enc); | |||
} else { | |||
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available."); | |||
} | |||
} | |||
function parseArray() { | |||
var length = objInfo; | |||
var arrayoffset = 1; | |||
if (objInfo == 0xF) { | |||
var int_type = buffer[offset + 1]; | |||
var intType = (int_type & 0xF0) / 0x10; | |||
if (intType != 0x1) { | |||
console.error("0xa: UNEXPECTED LENGTH-INT TYPE! " + intType); | |||
} | |||
var intInfo = int_type & 0x0F; | |||
var intLength = Math.pow(2, intInfo); | |||
arrayoffset = 2 + intLength; | |||
if (intLength < 3) { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} else { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} | |||
} | |||
if (length * objectRefSize > exports.maxObjectSize) { | |||
throw new Error("To little heap space available!"); | |||
} | |||
var array = []; | |||
for (var i = 0; i < length; i++) { | |||
var objRef = readUInt(buffer.slice(offset + arrayoffset + i * objectRefSize, offset + arrayoffset + (i + 1) * objectRefSize)); | |||
array[i] = parseObject(objRef); | |||
} | |||
return array; | |||
} | |||
function parseDictionary() { | |||
var length = objInfo; | |||
var dictoffset = 1; | |||
if (objInfo == 0xF) { | |||
var int_type = buffer[offset + 1]; | |||
var intType = (int_type & 0xF0) / 0x10; | |||
if (intType != 0x1) { | |||
console.error("0xD: UNEXPECTED LENGTH-INT TYPE! " + intType); | |||
} | |||
var intInfo = int_type & 0x0F; | |||
var intLength = Math.pow(2, intInfo); | |||
dictoffset = 2 + intLength; | |||
if (intLength < 3) { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} else { | |||
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength)); | |||
} | |||
} | |||
if (length * 2 * objectRefSize > exports.maxObjectSize) { | |||
throw new Error("To little heap space available!"); | |||
} | |||
if (debug) { | |||
console.log("Parsing dictionary #" + tableOffset); | |||
} | |||
var dict = {}; | |||
for (var i = 0; i < length; i++) { | |||
var keyRef = readUInt(buffer.slice(offset + dictoffset + i * objectRefSize, offset + dictoffset + (i + 1) * objectRefSize)); | |||
var valRef = readUInt(buffer.slice(offset + dictoffset + (length * objectRefSize) + i * objectRefSize, offset + dictoffset + (length * objectRefSize) + (i + 1) * objectRefSize)); | |||
var key = parseObject(keyRef); | |||
var val = parseObject(valRef); | |||
if (debug) { | |||
console.log(" DICT #" + tableOffset + ": Mapped " + key + " to " + val); | |||
} | |||
dict[key] = val; | |||
} | |||
return dict; | |||
} | |||
} | |||
return [ parseObject(topObject) ]; | |||
}; | |||
function readUInt(buffer, start) { | |||
start = start || 0; | |||
var l = 0; | |||
for (var i = start; i < buffer.length; i++) { | |||
l <<= 8; | |||
l |= buffer[i] & 0xFF; | |||
} | |||
return l; | |||
} | |||
// we're just going to toss the high order bits because javascript doesn't have 64-bit ints | |||
function readUInt64BE(buffer, start) { | |||
var data = buffer.slice(start, start + 8); | |||
return data.readUInt32BE(4, 8); | |||
} | |||
function swapBytes(buffer) { | |||
var len = buffer.length; | |||
for (var i = 0; i < len; i += 2) { | |||
var a = buffer[i]; | |||
buffer[i] = buffer[i+1]; | |||
buffer[i+1] = a; | |||
} | |||
return buffer; | |||
} |
@@ -0,0 +1,90 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "bplist-parser@^0.1.0", | |||
"scope": null, | |||
"escapedName": "bplist-parser", | |||
"name": "bplist-parser", | |||
"rawSpec": "^0.1.0", | |||
"spec": ">=0.1.0 <0.2.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\cordova-common" | |||
] | |||
], | |||
"_from": "bplist-parser@>=0.1.0 <0.2.0", | |||
"_id": "bplist-parser@0.1.1", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/bplist-parser", | |||
"_nodeVersion": "5.1.0", | |||
"_npmUser": { | |||
"name": "joeferner", | |||
"email": "joe@fernsroth.com" | |||
}, | |||
"_npmVersion": "3.4.0", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "bplist-parser@^0.1.0", | |||
"scope": null, | |||
"escapedName": "bplist-parser", | |||
"name": "bplist-parser", | |||
"rawSpec": "^0.1.0", | |||
"spec": ">=0.1.0 <0.2.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/cordova-common" | |||
], | |||
"_resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", | |||
"_shasum": "d60d5dcc20cba6dc7e1f299b35d3e1f95dafbae6", | |||
"_shrinkwrap": null, | |||
"_spec": "bplist-parser@^0.1.0", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\cordova-common", | |||
"author": { | |||
"name": "Joe Ferner", | |||
"email": "joe.ferner@nearinfinity.com" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/nearinfinity/node-bplist-parser/issues" | |||
}, | |||
"dependencies": { | |||
"big-integer": "^1.6.7" | |||
}, | |||
"description": "Binary plist parser.", | |||
"devDependencies": { | |||
"nodeunit": "~0.9.1" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "d60d5dcc20cba6dc7e1f299b35d3e1f95dafbae6", | |||
"tarball": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz" | |||
}, | |||
"gitHead": "c4f22650de2cc95edd21a6e609ff0654a2b951bd", | |||
"homepage": "https://github.com/nearinfinity/node-bplist-parser#readme", | |||
"keywords": [ | |||
"bplist", | |||
"plist", | |||
"parser" | |||
], | |||
"license": "MIT", | |||
"main": "bplistParser.js", | |||
"maintainers": [ | |||
{ | |||
"name": "joeferner", | |||
"email": "joe@fernsroth.com" | |||
} | |||
], | |||
"name": "bplist-parser", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git+https://github.com/nearinfinity/node-bplist-parser.git" | |||
}, | |||
"scripts": { | |||
"test": "./node_modules/nodeunit/bin/nodeunit test" | |||
}, | |||
"version": "0.1.1" | |||
} |
@@ -0,0 +1,10 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
<plist version="1.0"> | |||
<dict> | |||
<key>zero</key> | |||
<integer>0</integer> | |||
<key>int64item</key> | |||
<integer>12345678901234567890</integer> | |||
</dict> | |||
</plist> |
@@ -0,0 +1,159 @@ | |||
'use strict'; | |||
// tests are adapted from https://github.com/TooTallNate/node-plist | |||
var path = require('path'); | |||
var nodeunit = require('nodeunit'); | |||
var bplist = require('../'); | |||
module.exports = { | |||
'iTunes Small': function (test) { | |||
var file = path.join(__dirname, "iTunes-small.bplist"); | |||
var startTime1 = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime1) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['Application Version'], "9.0.3"); | |||
test.equal(dict['Library Persistent ID'], "6F81D37F95101437"); | |||
test.done(); | |||
}); | |||
}, | |||
'sample1': function (test) { | |||
var file = path.join(__dirname, "sample1.bplist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['CFBundleIdentifier'], 'com.apple.dictionary.MySample'); | |||
test.done(); | |||
}); | |||
}, | |||
'sample2': function (test) { | |||
var file = path.join(__dirname, "sample2.bplist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['PopupMenu'][2]['Key'], "\n #import <Cocoa/Cocoa.h>\n\n#import <MacRuby/MacRuby.h>\n\nint main(int argc, char *argv[])\n{\n return macruby_main(\"rb_main.rb\", argc, argv);\n}\n"); | |||
test.done(); | |||
}); | |||
}, | |||
'airplay': function (test) { | |||
var file = path.join(__dirname, "airplay.bplist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['duration'], 5555.0495000000001); | |||
test.equal(dict['position'], 4.6269989039999997); | |||
test.done(); | |||
}); | |||
}, | |||
'utf16': function (test) { | |||
var file = path.join(__dirname, "utf16.bplist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['CFBundleName'], 'sellStuff'); | |||
test.equal(dict['CFBundleShortVersionString'], '2.6.1'); | |||
test.equal(dict['NSHumanReadableCopyright'], '©2008-2012, sellStuff, Inc.'); | |||
test.done(); | |||
}); | |||
}, | |||
'utf16chinese': function (test) { | |||
var file = path.join(__dirname, "utf16_chinese.plist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['CFBundleName'], '天翼阅读'); | |||
test.equal(dict['CFBundleDisplayName'], '天翼阅读'); | |||
test.done(); | |||
}); | |||
}, | |||
'uid': function (test) { | |||
var file = path.join(__dirname, "uid.bplist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.deepEqual(dict['$objects'][1]['NS.keys'], [{UID:2}, {UID:3}, {UID:4}]); | |||
test.deepEqual(dict['$objects'][1]['NS.objects'], [{UID: 5}, {UID:6}, {UID:7}]); | |||
test.deepEqual(dict['$top']['root'], {UID:1}); | |||
test.done(); | |||
}); | |||
}, | |||
'int64': function (test) { | |||
var file = path.join(__dirname, "int64.bplist"); | |||
var startTime = new Date(); | |||
bplist.parseFile(file, function (err, dicts) { | |||
if (err) { | |||
throw err; | |||
} | |||
var endTime = new Date(); | |||
console.log('Parsed "' + file + '" in ' + (endTime - startTime) + 'ms'); | |||
var dict = dicts[0]; | |||
test.equal(dict['zero'], '0'); | |||
test.equal(dict['int64item'], '12345678901234567890'); | |||
test.done(); | |||
}); | |||
} | |||
}; |
@@ -0,0 +1,122 @@ | |||
# brace-expansion | |||
[Brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html), | |||
as known from sh/bash, in JavaScript. | |||
[![build status](https://secure.travis-ci.org/juliangruber/brace-expansion.svg)](http://travis-ci.org/juliangruber/brace-expansion) | |||
[![downloads](https://img.shields.io/npm/dm/brace-expansion.svg)](https://www.npmjs.org/package/brace-expansion) | |||
[![testling badge](https://ci.testling.com/juliangruber/brace-expansion.png)](https://ci.testling.com/juliangruber/brace-expansion) | |||
## Example | |||
```js | |||
var expand = require('brace-expansion'); | |||
expand('file-{a,b,c}.jpg') | |||
// => ['file-a.jpg', 'file-b.jpg', 'file-c.jpg'] | |||
expand('-v{,,}') | |||
// => ['-v', '-v', '-v'] | |||
expand('file{0..2}.jpg') | |||
// => ['file0.jpg', 'file1.jpg', 'file2.jpg'] | |||
expand('file-{a..c}.jpg') | |||
// => ['file-a.jpg', 'file-b.jpg', 'file-c.jpg'] | |||
expand('file{2..0}.jpg') | |||
// => ['file2.jpg', 'file1.jpg', 'file0.jpg'] | |||
expand('file{0..4..2}.jpg') | |||
// => ['file0.jpg', 'file2.jpg', 'file4.jpg'] | |||
expand('file-{a..e..2}.jpg') | |||
// => ['file-a.jpg', 'file-c.jpg', 'file-e.jpg'] | |||
expand('file{00..10..5}.jpg') | |||
// => ['file00.jpg', 'file05.jpg', 'file10.jpg'] | |||
expand('{{A..C},{a..c}}') | |||
// => ['A', 'B', 'C', 'a', 'b', 'c'] | |||
expand('ppp{,config,oe{,conf}}') | |||
// => ['ppp', 'pppconfig', 'pppoe', 'pppoeconf'] | |||
``` | |||
## API | |||
```js | |||
var expand = require('brace-expansion'); | |||
``` | |||
### var expanded = expand(str) | |||
Return an array of all possible and valid expansions of `str`. If none are | |||
found, `[str]` is returned. | |||
Valid expansions are: | |||
```js | |||
/^(.*,)+(.+)?$/ | |||
// {a,b,...} | |||
``` | |||
A comma seperated list of options, like `{a,b}` or `{a,{b,c}}` or `{,a,}`. | |||
```js | |||
/^-?\d+\.\.-?\d+(\.\.-?\d+)?$/ | |||
// {x..y[..incr]} | |||
``` | |||
A numeric sequence from `x` to `y` inclusive, with optional increment. | |||
If `x` or `y` start with a leading `0`, all the numbers will be padded | |||
to have equal length. Negative numbers and backwards iteration work too. | |||
```js | |||
/^-?\d+\.\.-?\d+(\.\.-?\d+)?$/ | |||
// {x..y[..incr]} | |||
``` | |||
An alphabetic sequence from `x` to `y` inclusive, with optional increment. | |||
`x` and `y` must be exactly one character, and if given, `incr` must be a | |||
number. | |||
For compatibility reasons, the string `${` is not eligible for brace expansion. | |||
## Installation | |||
With [npm](https://npmjs.org) do: | |||
```bash | |||
npm install brace-expansion | |||
``` | |||
## Contributors | |||
- [Julian Gruber](https://github.com/juliangruber) | |||
- [Isaac Z. Schlueter](https://github.com/isaacs) | |||
## License | |||
(MIT) | |||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
this software and associated documentation files (the "Software"), to deal in | |||
the Software without restriction, including without limitation the rights to | |||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |||
of the Software, and to permit persons to whom the Software is furnished to do | |||
so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,191 @@ | |||
var concatMap = require('concat-map'); | |||
var balanced = require('balanced-match'); | |||
module.exports = expandTop; | |||
var escSlash = '\0SLASH'+Math.random()+'\0'; | |||
var escOpen = '\0OPEN'+Math.random()+'\0'; | |||
var escClose = '\0CLOSE'+Math.random()+'\0'; | |||
var escComma = '\0COMMA'+Math.random()+'\0'; | |||
var escPeriod = '\0PERIOD'+Math.random()+'\0'; | |||
function numeric(str) { | |||
return parseInt(str, 10) == str | |||
? parseInt(str, 10) | |||
: str.charCodeAt(0); | |||
} | |||
function escapeBraces(str) { | |||
return str.split('\\\\').join(escSlash) | |||
.split('\\{').join(escOpen) | |||
.split('\\}').join(escClose) | |||
.split('\\,').join(escComma) | |||
.split('\\.').join(escPeriod); | |||
} | |||
function unescapeBraces(str) { | |||
return str.split(escSlash).join('\\') | |||
.split(escOpen).join('{') | |||
.split(escClose).join('}') | |||
.split(escComma).join(',') | |||
.split(escPeriod).join('.'); | |||
} | |||
// Basically just str.split(","), but handling cases | |||
// where we have nested braced sections, which should be | |||
// treated as individual members, like {a,{b,c},d} | |||
function parseCommaParts(str) { | |||
if (!str) | |||
return ['']; | |||
var parts = []; | |||
var m = balanced('{', '}', str); | |||
if (!m) | |||
return str.split(','); | |||
var pre = m.pre; | |||
var body = m.body; | |||
var post = m.post; | |||
var p = pre.split(','); | |||
p[p.length-1] += '{' + body + '}'; | |||
var postParts = parseCommaParts(post); | |||
if (post.length) { | |||
p[p.length-1] += postParts.shift(); | |||
p.push.apply(p, postParts); | |||
} | |||
parts.push.apply(parts, p); | |||
return parts; | |||
} | |||
function expandTop(str) { | |||
if (!str) | |||
return []; | |||
return expand(escapeBraces(str), true).map(unescapeBraces); | |||
} | |||
function identity(e) { | |||
return e; | |||
} | |||
function embrace(str) { | |||
return '{' + str + '}'; | |||
} | |||
function isPadded(el) { | |||
return /^-?0\d/.test(el); | |||
} | |||
function lte(i, y) { | |||
return i <= y; | |||
} | |||
function gte(i, y) { | |||
return i >= y; | |||
} | |||
function expand(str, isTop) { | |||
var expansions = []; | |||
var m = balanced('{', '}', str); | |||
if (!m || /\$$/.test(m.pre)) return [str]; | |||
var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); | |||
var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); | |||
var isSequence = isNumericSequence || isAlphaSequence; | |||
var isOptions = /^(.*,)+(.+)?$/.test(m.body); | |||
if (!isSequence && !isOptions) { | |||
// {a},b} | |||
if (m.post.match(/,.*\}/)) { | |||
str = m.pre + '{' + m.body + escClose + m.post; | |||
return expand(str); | |||
} | |||
return [str]; | |||
} | |||
var n; | |||
if (isSequence) { | |||
n = m.body.split(/\.\./); | |||
} else { | |||
n = parseCommaParts(m.body); | |||
if (n.length === 1) { | |||
// x{{a,b}}y ==> x{a}y x{b}y | |||
n = expand(n[0], false).map(embrace); | |||
if (n.length === 1) { | |||
var post = m.post.length | |||
? expand(m.post, false) | |||
: ['']; | |||
return post.map(function(p) { | |||
return m.pre + n[0] + p; | |||
}); | |||
} | |||
} | |||
} | |||
// at this point, n is the parts, and we know it's not a comma set | |||
// with a single entry. | |||
// no need to expand pre, since it is guaranteed to be free of brace-sets | |||
var pre = m.pre; | |||
var post = m.post.length | |||
? expand(m.post, false) | |||
: ['']; | |||
var N; | |||
if (isSequence) { | |||
var x = numeric(n[0]); | |||
var y = numeric(n[1]); | |||
var width = Math.max(n[0].length, n[1].length) | |||
var incr = n.length == 3 | |||
? Math.abs(numeric(n[2])) | |||
: 1; | |||
var test = lte; | |||
var reverse = y < x; | |||
if (reverse) { | |||
incr *= -1; | |||
test = gte; | |||
} | |||
var pad = n.some(isPadded); | |||
N = []; | |||
for (var i = x; test(i, y); i += incr) { | |||
var c; | |||
if (isAlphaSequence) { | |||
c = String.fromCharCode(i); | |||
if (c === '\\') | |||
c = ''; | |||
} else { | |||
c = String(i); | |||
if (pad) { | |||
var need = width - c.length; | |||
if (need > 0) { | |||
var z = new Array(need + 1).join('0'); | |||
if (i < 0) | |||
c = '-' + z + c.slice(1); | |||
else | |||
c = z + c; | |||
} | |||
} | |||
} | |||
N.push(c); | |||
} | |||
} else { | |||
N = concatMap(n, function(el) { return expand(el, false) }); | |||
} | |||
for (var j = 0; j < N.length; j++) { | |||
for (var k = 0; k < post.length; k++) { | |||
var expansion = pre + N[j] + post[k]; | |||
if (!isTop || isSequence || expansion) | |||
expansions.push(expansion); | |||
} | |||
} | |||
return expansions; | |||
} | |||
@@ -0,0 +1,113 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "brace-expansion@^1.0.0", | |||
"scope": null, | |||
"escapedName": "brace-expansion", | |||
"name": "brace-expansion", | |||
"rawSpec": "^1.0.0", | |||
"spec": ">=1.0.0 <2.0.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\minimatch" | |||
] | |||
], | |||
"_from": "brace-expansion@>=1.0.0 <2.0.0", | |||
"_id": "brace-expansion@1.1.5", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/brace-expansion", | |||
"_nodeVersion": "4.4.5", | |||
"_npmOperationalInternal": { | |||
"host": "packages-16-east.internal.npmjs.com", | |||
"tmp": "tmp/brace-expansion-1.1.5.tgz_1465989660138_0.34528115345165133" | |||
}, | |||
"_npmUser": { | |||
"name": "juliangruber", | |||
"email": "julian@juliangruber.com" | |||
}, | |||
"_npmVersion": "2.15.5", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "brace-expansion@^1.0.0", | |||
"scope": null, | |||
"escapedName": "brace-expansion", | |||
"name": "brace-expansion", | |||
"rawSpec": "^1.0.0", | |||
"spec": ">=1.0.0 <2.0.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/minimatch" | |||
], | |||
"_resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.5.tgz", | |||
"_shasum": "f5b4ad574e2cb7ccc1eb83e6fe79b8ecadf7a526", | |||
"_shrinkwrap": null, | |||
"_spec": "brace-expansion@^1.0.0", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\minimatch", | |||
"author": { | |||
"name": "Julian Gruber", | |||
"email": "mail@juliangruber.com", | |||
"url": "http://juliangruber.com" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/juliangruber/brace-expansion/issues" | |||
}, | |||
"dependencies": { | |||
"balanced-match": "^0.4.1", | |||
"concat-map": "0.0.1" | |||
}, | |||
"description": "Brace expansion as known from sh/bash", | |||
"devDependencies": { | |||
"tape": "4.5.1" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "f5b4ad574e2cb7ccc1eb83e6fe79b8ecadf7a526", | |||
"tarball": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.5.tgz" | |||
}, | |||
"gitHead": "ff31acab078f1bb696ac4c55ca56ea24e6495fb6", | |||
"homepage": "https://github.com/juliangruber/brace-expansion", | |||
"keywords": [], | |||
"license": "MIT", | |||
"main": "index.js", | |||
"maintainers": [ | |||
{ | |||
"name": "juliangruber", | |||
"email": "julian@juliangruber.com" | |||
}, | |||
{ | |||
"name": "isaacs", | |||
"email": "isaacs@npmjs.com" | |||
} | |||
], | |||
"name": "brace-expansion", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/juliangruber/brace-expansion.git" | |||
}, | |||
"scripts": { | |||
"gentest": "bash test/generate.sh", | |||
"test": "tape test/*.js" | |||
}, | |||
"testling": { | |||
"files": "test/*.js", | |||
"browsers": [ | |||
"ie/8..latest", | |||
"firefox/20..latest", | |||
"firefox/nightly", | |||
"chrome/25..latest", | |||
"chrome/canary", | |||
"opera/12..latest", | |||
"opera/next", | |||
"safari/5.1..latest", | |||
"ipad/6.0..latest", | |||
"iphone/6.0..latest", | |||
"android-browser/4.2..latest" | |||
] | |||
}, | |||
"version": "1.1.5" | |||
} |
@@ -0,0 +1,4 @@ | |||
language: node_js | |||
node_js: | |||
- 0.4 | |||
- 0.6 |
@@ -0,0 +1,18 @@ | |||
This software is released under the MIT license: | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
this software and associated documentation files (the "Software"), to deal in | |||
the Software without restriction, including without limitation the rights to | |||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |||
the Software, and to permit persons to whom the Software is furnished to do so, | |||
subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,62 @@ | |||
concat-map | |||
========== | |||
Concatenative mapdashery. | |||
[![browser support](http://ci.testling.com/substack/node-concat-map.png)](http://ci.testling.com/substack/node-concat-map) | |||
[![build status](https://secure.travis-ci.org/substack/node-concat-map.png)](http://travis-ci.org/substack/node-concat-map) | |||
example | |||
======= | |||
``` js | |||
var concatMap = require('concat-map'); | |||
var xs = [ 1, 2, 3, 4, 5, 6 ]; | |||
var ys = concatMap(xs, function (x) { | |||
return x % 2 ? [ x - 0.1, x, x + 0.1 ] : []; | |||
}); | |||
console.dir(ys); | |||
``` | |||
*** | |||
``` | |||
[ 0.9, 1, 1.1, 2.9, 3, 3.1, 4.9, 5, 5.1 ] | |||
``` | |||
methods | |||
======= | |||
``` js | |||
var concatMap = require('concat-map') | |||
``` | |||
concatMap(xs, fn) | |||
----------------- | |||
Return an array of concatenated elements by calling `fn(x, i)` for each element | |||
`x` and each index `i` in the array `xs`. | |||
When `fn(x, i)` returns an array, its result will be concatenated with the | |||
result array. If `fn(x, i)` returns anything else, that value will be pushed | |||
onto the end of the result array. | |||
install | |||
======= | |||
With [npm](http://npmjs.org) do: | |||
``` | |||
npm install concat-map | |||
``` | |||
license | |||
======= | |||
MIT | |||
notes | |||
===== | |||
This module was written while sitting high above the ground in a tree. |
@@ -0,0 +1,13 @@ | |||
module.exports = function (xs, fn) { | |||
var res = []; | |||
for (var i = 0; i < xs.length; i++) { | |||
var x = fn(xs[i], i); | |||
if (isArray(x)) res.push.apply(res, x); | |||
else res.push(x); | |||
} | |||
return res; | |||
}; | |||
var isArray = Array.isArray || function (xs) { | |||
return Object.prototype.toString.call(xs) === '[object Array]'; | |||
}; |
@@ -0,0 +1,118 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "concat-map@0.0.1", | |||
"scope": null, | |||
"escapedName": "concat-map", | |||
"name": "concat-map", | |||
"rawSpec": "0.0.1", | |||
"spec": "0.0.1", | |||
"type": "version" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\brace-expansion" | |||
] | |||
], | |||
"_from": "concat-map@0.0.1", | |||
"_id": "concat-map@0.0.1", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/concat-map", | |||
"_npmUser": { | |||
"name": "substack", | |||
"email": "mail@substack.net" | |||
}, | |||
"_npmVersion": "1.3.21", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "concat-map@0.0.1", | |||
"scope": null, | |||
"escapedName": "concat-map", | |||
"name": "concat-map", | |||
"rawSpec": "0.0.1", | |||
"spec": "0.0.1", | |||
"type": "version" | |||
}, | |||
"_requiredBy": [ | |||
"/brace-expansion" | |||
], | |||
"_resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | |||
"_shasum": "d8a96bd77fd68df7793a73036a3ba0d5405d477b", | |||
"_shrinkwrap": null, | |||
"_spec": "concat-map@0.0.1", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\brace-expansion", | |||
"author": { | |||
"name": "James Halliday", | |||
"email": "mail@substack.net", | |||
"url": "http://substack.net" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/substack/node-concat-map/issues" | |||
}, | |||
"dependencies": {}, | |||
"description": "concatenative mapdashery", | |||
"devDependencies": { | |||
"tape": "~2.4.0" | |||
}, | |||
"directories": { | |||
"example": "example", | |||
"test": "test" | |||
}, | |||
"dist": { | |||
"shasum": "d8a96bd77fd68df7793a73036a3ba0d5405d477b", | |||
"tarball": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" | |||
}, | |||
"homepage": "https://github.com/substack/node-concat-map", | |||
"keywords": [ | |||
"concat", | |||
"concatMap", | |||
"map", | |||
"functional", | |||
"higher-order" | |||
], | |||
"license": "MIT", | |||
"main": "index.js", | |||
"maintainers": [ | |||
{ | |||
"name": "substack", | |||
"email": "mail@substack.net" | |||
} | |||
], | |||
"name": "concat-map", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/substack/node-concat-map.git" | |||
}, | |||
"scripts": { | |||
"test": "tape test/*.js" | |||
}, | |||
"testling": { | |||
"files": "test/*.js", | |||
"browsers": { | |||
"ie": [ | |||
6, | |||
7, | |||
8, | |||
9 | |||
], | |||
"ff": [ | |||
3.5, | |||
10, | |||
15 | |||
], | |||
"chrome": [ | |||
10, | |||
22 | |||
], | |||
"safari": [ | |||
5.1 | |||
], | |||
"opera": [ | |||
12 | |||
] | |||
} | |||
}, | |||
"version": "0.0.1" | |||
} |
@@ -0,0 +1,39 @@ | |||
var concatMap = require('../'); | |||
var test = require('tape'); | |||
test('empty or not', function (t) { | |||
var xs = [ 1, 2, 3, 4, 5, 6 ]; | |||
var ixes = []; | |||
var ys = concatMap(xs, function (x, ix) { | |||
ixes.push(ix); | |||
return x % 2 ? [ x - 0.1, x, x + 0.1 ] : []; | |||
}); | |||
t.same(ys, [ 0.9, 1, 1.1, 2.9, 3, 3.1, 4.9, 5, 5.1 ]); | |||
t.same(ixes, [ 0, 1, 2, 3, 4, 5 ]); | |||
t.end(); | |||
}); | |||
test('always something', function (t) { | |||
var xs = [ 'a', 'b', 'c', 'd' ]; | |||
var ys = concatMap(xs, function (x) { | |||
return x === 'b' ? [ 'B', 'B', 'B' ] : [ x ]; | |||
}); | |||
t.same(ys, [ 'a', 'B', 'B', 'B', 'c', 'd' ]); | |||
t.end(); | |||
}); | |||
test('scalars', function (t) { | |||
var xs = [ 'a', 'b', 'c', 'd' ]; | |||
var ys = concatMap(xs, function (x) { | |||
return x === 'b' ? [ 'B', 'B', 'B' ] : x; | |||
}); | |||
t.same(ys, [ 'a', 'B', 'B', 'B', 'c', 'd' ]); | |||
t.end(); | |||
}); | |||
test('undefs', function (t) { | |||
var xs = [ 'a', 'b', 'c', 'd' ]; | |||
var ys = concatMap(xs, function () {}); | |||
t.same(ys, [ undefined, undefined, undefined, undefined ]); | |||
t.end(); | |||
}); |
@@ -0,0 +1,24 @@ | |||
{ | |||
"disallowMixedSpacesAndTabs": true, | |||
"disallowTrailingWhitespace": true, | |||
"validateLineBreaks": "LF", | |||
"validateIndentation": 4, | |||
"requireLineFeedAtFileEnd": true, | |||
"disallowSpaceAfterPrefixUnaryOperators": true, | |||
"disallowSpaceBeforePostfixUnaryOperators": true, | |||
"requireSpaceAfterLineComment": true, | |||
"requireCapitalizedConstructors": true, | |||
"disallowSpacesInNamedFunctionExpression": { | |||
"beforeOpeningRoundBrace": true | |||
}, | |||
"requireSpaceAfterKeywords": [ | |||
"if", | |||
"else", | |||
"for", | |||
"while", | |||
"do" | |||
] | |||
} |
@@ -0,0 +1 @@ | |||
spec/fixtures/* |
@@ -0,0 +1,2 @@ | |||
spec | |||
coverage |
@@ -0,0 +1,2 @@ | |||
fixtures | |||
coverage |
@@ -0,0 +1,153 @@ | |||
<!-- | |||
# | |||
# 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-common | |||
Expoeses shared functionality used by [cordova-lib](https://github.com/apache/cordova-lib/) and Cordova platforms. | |||
## Exposed APIs | |||
### `events` | |||
Represents special instance of NodeJS EventEmitter which is intended to be used to post events to cordova-lib and cordova-cli | |||
Usage: | |||
``` | |||
var events = require('cordova-common').events; | |||
events.emit('warn', 'Some warning message') | |||
``` | |||
There are the following events supported by cordova-cli: `verbose`, `log`, `info`, `warn`, `error`. | |||
### `CordovaError` | |||
An error class used by Cordova to throw cordova-specific errors. The CordovaError class is inherited from Error, so CordovaError instances is also valid Error instances (`instanceof` check succeeds). | |||
Usage: | |||
``` | |||
var CordovaError = require('cordova-common').CordovaError; | |||
throw new CordovaError('Some error message', SOME_ERR_CODE); | |||
``` | |||
See [CordovaError](src/CordovaError/CordovaError.js) for supported error codes. | |||
### `ConfigParser` | |||
Exposes functionality to deal with cordova project `config.xml` files. For ConfigParser API reference check [ConfigParser Readme](src/ConfigParser/README.md). | |||
Usage: | |||
``` | |||
var ConfigParser = require('cordova-common').ConfigParser; | |||
var appConfig = new ConfigParser('path/to/cordova-app/config.xml'); | |||
console.log(appconfig.name() + ':' + appConfig.version()); | |||
``` | |||
### `PluginInfoProvider` and `PluginInfo` | |||
`PluginInfo` is a wrapper for cordova plugins' `plugin.xml` files. This class may be instantiated directly or via `PluginInfoProvider`. The difference is that `PluginInfoProvider` caches `PluginInfo` instances based on plugin source directory. | |||
Usage: | |||
``` | |||
var PluginInfo: require('cordova-common').PluginInfo; | |||
var PluginInfoProvider: require('cordova-common').PluginInfoProvider; | |||
// The following instances are equal | |||
var plugin1 = new PluginInfo('path/to/plugin_directory'); | |||
var plugin2 = new PluginInfoProvider().get('path/to/plugin_directory'); | |||
console.log('The plugin ' + plugin1.id + ' has version ' + plugin1.version) | |||
``` | |||
### `ActionStack` | |||
Utility module for dealing with sequential tasks. Provides a set of tasks that are needed to be done and reverts all tasks that are already completed if one of those tasks fail to complete. Used internally by cordova-lib and platform's plugin installation routines. | |||
Usage: | |||
``` | |||
var ActionStack = require('cordova-common').ActionStack; | |||
var stack = new ActionStack() | |||
var action1 = stack.createAction(task1, [<task parameters>], task1_reverter, [<reverter_parameters>]); | |||
var action2 = stack.createAction(task2, [<task parameters>], task2_reverter, [<reverter_parameters>]); | |||
stack.push(action1); | |||
stack.push(action2); | |||
stack.process() | |||
.then(function() { | |||
// all actions succeded | |||
}) | |||
.catch(function(error){ | |||
// One of actions failed with error | |||
}) | |||
``` | |||
### `superspawn` | |||
Module for spawning child processes with some advanced logic. | |||
Usage: | |||
``` | |||
var superspawn = require('cordova-common').superspawn; | |||
superspawn.spawn('adb', ['devices']) | |||
.progress(function(data){ | |||
if (data.stderr) | |||
console.error('"adb devices" raised an error: ' + data.stderr); | |||
}) | |||
.then(function(devices){ | |||
// Do something... | |||
}) | |||
``` | |||
### `xmlHelpers` | |||
A set of utility methods for dealing with xml files. | |||
Usage: | |||
``` | |||
var xml = require('cordova-common').xmlHelpers; | |||
var xmlDoc1 = xml.parseElementtreeSync('some/xml/file'); | |||
var xmlDoc2 = xml.parseElementtreeSync('another/xml/file'); | |||
xml.mergeXml(doc1, doc2); // doc2 now contains all the nodes from doc1 | |||
``` | |||
### Other APIs | |||
The APIs listed below are also exposed but are intended to be only used internally by cordova plugin installation routines. | |||
``` | |||
PlatformJson | |||
ConfigChanges | |||
ConfigKeeper | |||
ConfigFile | |||
mungeUtil | |||
``` | |||
## Setup | |||
* Clone this repository onto your local machine | |||
`git clone https://git-wip-us.apache.org/repos/asf/cordova-lib.git` | |||
* In terminal, navigate to the inner cordova-common directory | |||
`cd cordova-lib/cordova-common` | |||
* Install dependencies and npm-link | |||
`npm install && npm link` | |||
* Navigate to cordova-lib directory and link cordova-common | |||
`cd ../cordova-lib && npm link cordova-common && npm install` |
@@ -0,0 +1,64 @@ | |||
<!-- | |||
# | |||
# 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-common Release Notes | |||
### 1.3.0 (May 12, 2016) | |||
* [CB-11259](https://issues.apache.org/jira/browse/CB-11259): Improving prepare and build logging | |||
* [CB-11194](https://issues.apache.org/jira/browse/CB-11194) Improve cordova load time | |||
* [CB-1117](https://issues.apache.org/jira/browse/CB-1117) Add `FileUpdater` module to `cordova-common`. | |||
* [CB-11131](https://issues.apache.org/jira/browse/CB-11131) Fix `TypeError: message.toUpperCase` is not a function in `CordovaLogger` | |||
### 1.2.0 (Apr 18, 2016) | |||
* [CB-11022](https://issues.apache.org/jira/browse/CB-11022) Save modulesMetadata to both www and platform_www when necessary | |||
* [CB-10833](https://issues.apache.org/jira/browse/CB-10833) Deduplicate common logic for plugin installation/uninstallation | |||
* [CB-10822](https://issues.apache.org/jira/browse/CB-10822) Manage plugins/modules metadata using PlatformJson | |||
* [CB-10940](https://issues.apache.org/jira/browse/CB-10940) Can't add Android platform from path | |||
* [CB-10965](https://issues.apache.org/jira/browse/CB-10965) xml helper allows multiple instances to be merge in config.xml | |||
### 1.1.1 (Mar 18, 2016) | |||
* [CB-10694](https://issues.apache.org/jira/browse/CB-10694) Update test to reflect merging of [CB-9264](https://issues.apache.org/jira/browse/CB-9264) fix | |||
* [CB-10694](https://issues.apache.org/jira/browse/CB-10694) Platform-specific configuration preferences don't override global settings | |||
* [CB-9264](https://issues.apache.org/jira/browse/CB-9264) Duplicate entries in `config.xml` | |||
* [CB-10791](https://issues.apache.org/jira/browse/CB-10791) Add `adjustLoggerLevel` to `cordova-common.CordovaLogger` | |||
* [CB-10662](https://issues.apache.org/jira/browse/CB-10662) Add tests for `ConfigParser.getStaticResources` | |||
* [CB-10622](https://issues.apache.org/jira/browse/CB-10622) fix target attribute being ignored for images in `config.xml`. | |||
* [CB-10583](https://issues.apache.org/jira/browse/CB-10583) Protect plugin preferences from adding extra Array properties. | |||
### 1.1.0 (Feb 16, 2016) | |||
* [CB-10482](https://issues.apache.org/jira/browse/CB-10482) Remove references to windows8 from cordova-lib/cli | |||
* [CB-10430](https://issues.apache.org/jira/browse/CB-10430) Adds forwardEvents method to easily connect two EventEmitters | |||
* [CB-10176](https://issues.apache.org/jira/browse/CB-10176) Adds CordovaLogger class, based on logger module from cordova-cli | |||
* [CB-10052](https://issues.apache.org/jira/browse/CB-10052) Expose child process' io streams via promise progress notification | |||
* [CB-10497](https://issues.apache.org/jira/browse/CB-10497) Prefer .bat over .cmd on windows platform | |||
* [CB-9984](https://issues.apache.org/jira/browse/CB-9984) Bumps plist version and fixes failing cordova-common test | |||
### 1.0.0 (Oct 29, 2015) | |||
* [CB-9890](https://issues.apache.org/jira/browse/CB-9890) Documents cordova-common | |||
* [CB-9598](https://issues.apache.org/jira/browse/CB-9598) Correct cordova-lib -> cordova-common in README | |||
* Pick ConfigParser changes from apache@0c3614e | |||
* [CB-9743](https://issues.apache.org/jira/browse/CB-9743) Removes system frameworks handling from ConfigChanges | |||
* [CB-9598](https://issues.apache.org/jira/browse/CB-9598) Cleans out code which has been moved to `cordova-common` | |||
* Pick ConfigParser changes from apache@ddb027b | |||
* Picking CordovaError changes from apache@a3b1fca | |||
* [CB-9598](https://issues.apache.org/jira/browse/CB-9598) Adds tests and fixtures based on existing cordova-lib ones | |||
* [CB-9598](https://issues.apache.org/jira/browse/CB-9598) Initial implementation for cordova-common | |||
@@ -0,0 +1,46 @@ | |||
/** | |||
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 addProperty = require('./src/util/addProperty'); | |||
module.exports = { }; | |||
addProperty(module, 'events', './src/events'); | |||
addProperty(module, 'superspawn', './src/superspawn'); | |||
addProperty(module, 'ActionStack', './src/ActionStack'); | |||
addProperty(module, 'CordovaError', './src/CordovaError/CordovaError'); | |||
addProperty(module, 'CordovaLogger', './src/CordovaLogger'); | |||
addProperty(module, 'CordovaExternalToolErrorContext', './src/CordovaError/CordovaExternalToolErrorContext'); | |||
addProperty(module, 'PlatformJson', './src/PlatformJson'); | |||
addProperty(module, 'ConfigParser', './src/ConfigParser/ConfigParser'); | |||
addProperty(module, 'FileUpdater', './src/FileUpdater'); | |||
addProperty(module, 'PluginInfo', './src/PluginInfo/PluginInfo'); | |||
addProperty(module, 'PluginInfoProvider', './src/PluginInfo/PluginInfoProvider'); | |||
addProperty(module, 'PluginManager', './src/PluginManager'); | |||
addProperty(module, 'ConfigChanges', './src/ConfigChanges/ConfigChanges'); | |||
addProperty(module, 'ConfigKeeper', './src/ConfigChanges/ConfigKeeper'); | |||
addProperty(module, 'ConfigFile', './src/ConfigChanges/ConfigFile'); | |||
addProperty(module, 'mungeUtil', './src/ConfigChanges/munge-util'); | |||
addProperty(module, 'xmlHelpers', './src/util/xml-helpers'); | |||
@@ -0,0 +1,131 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "cordova-common@^1.3.0", | |||
"scope": null, | |||
"escapedName": "cordova-common", | |||
"name": "cordova-common", | |||
"rawSpec": "^1.3.0", | |||
"spec": ">=1.3.0 <2.0.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android" | |||
] | |||
], | |||
"_from": "cordova-common@>=1.3.0 <2.0.0", | |||
"_id": "cordova-common@1.3.0", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/cordova-common", | |||
"_nodeVersion": "5.4.1", | |||
"_npmOperationalInternal": { | |||
"host": "packages-16-east.internal.npmjs.com", | |||
"tmp": "tmp/cordova-common-1.3.0.tgz_1464130094288_0.48495062021538615" | |||
}, | |||
"_npmUser": { | |||
"name": "stevegill", | |||
"email": "stevengill97@gmail.com" | |||
}, | |||
"_npmVersion": "3.9.0", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "cordova-common@^1.3.0", | |||
"scope": null, | |||
"escapedName": "cordova-common", | |||
"name": "cordova-common", | |||
"rawSpec": "^1.3.0", | |||
"spec": ">=1.3.0 <2.0.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/" | |||
], | |||
"_resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-1.3.0.tgz", | |||
"_shasum": "f75161f6aa7cef5486fd5d69a3b0a1f628334491", | |||
"_shrinkwrap": null, | |||
"_spec": "cordova-common@^1.3.0", | |||
"_where": "d:\\cordova\\cordova-android", | |||
"author": { | |||
"name": "Apache Software Foundation" | |||
}, | |||
"bugs": { | |||
"url": "https://issues.apache.org/jira/browse/CB", | |||
"email": "dev@cordova.apache.org" | |||
}, | |||
"contributors": [], | |||
"dependencies": { | |||
"ansi": "^0.3.1", | |||
"bplist-parser": "^0.1.0", | |||
"cordova-registry-mapper": "^1.1.8", | |||
"elementtree": "^0.1.6", | |||
"glob": "^5.0.13", | |||
"minimatch": "^3.0.0", | |||
"osenv": "^0.1.3", | |||
"plist": "^1.2.0", | |||
"q": "^1.4.1", | |||
"semver": "^5.0.1", | |||
"shelljs": "^0.5.3", | |||
"underscore": "^1.8.3", | |||
"unorm": "^1.3.3" | |||
}, | |||
"description": "Apache Cordova tools and platforms shared routines", | |||
"devDependencies": { | |||
"istanbul": "^0.3.17", | |||
"jasmine-node": "^1.14.5", | |||
"jshint": "^2.8.0", | |||
"promise-matchers": "^0.9.6", | |||
"rewire": "^2.5.1" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "f75161f6aa7cef5486fd5d69a3b0a1f628334491", | |||
"tarball": "https://registry.npmjs.org/cordova-common/-/cordova-common-1.3.0.tgz" | |||
}, | |||
"engineStrict": true, | |||
"engines": { | |||
"node": ">=0.9.9" | |||
}, | |||
"license": "Apache-2.0", | |||
"main": "cordova-common.js", | |||
"maintainers": [ | |||
{ | |||
"name": "bowserj", | |||
"email": "bowserj@apache.org" | |||
}, | |||
{ | |||
"name": "kotikov.vladimir", | |||
"email": "kotikov.vladimir@gmail.com" | |||
}, | |||
{ | |||
"name": "purplecabbage", | |||
"email": "purplecabbage@gmail.com" | |||
}, | |||
{ | |||
"name": "shazron", | |||
"email": "shazron@gmail.com" | |||
}, | |||
{ | |||
"name": "stevegill", | |||
"email": "stevengill97@gmail.com" | |||
}, | |||
{ | |||
"name": "timbarham", | |||
"email": "npmjs@barhams.info" | |||
} | |||
], | |||
"name": "cordova-common", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://git-wip-us.apache.org/repos/asf/cordova-common.git" | |||
}, | |||
"scripts": { | |||
"cover": "node node_modules/istanbul/lib/cli.js cover --root src --print detail node_modules/jasmine-node/bin/jasmine-node -- spec", | |||
"jasmine": "node node_modules/jasmine-node/bin/jasmine-node --captureExceptions --color spec", | |||
"jshint": "node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint spec", | |||
"test": "npm run jshint && npm run jasmine" | |||
}, | |||
"version": "1.3.0" | |||
} |
@@ -0,0 +1,10 @@ | |||
{ | |||
"node": true | |||
, "bitwise": true | |||
, "undef": true | |||
, "trailing": true | |||
, "quotmark": true | |||
, "indent": 4 | |||
, "unused": "vars" | |||
, "latedef": "nofunc" | |||
} |
@@ -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; |
@@ -0,0 +1,323 @@ | |||
/* | |||
* | |||
* 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 Windows10, Windows8.1 and WP8.1 | |||
var substs = ['package.phone.appxmanifest', 'package.windows.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.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.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.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 ****/ |
@@ -0,0 +1,212 @@ | |||
/* | |||
* 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 modules = {}; | |||
var addProperty = require('../util/addProperty'); | |||
// Use delay loading to ensure plist and other node modules to not get loaded | |||
// on Android, Windows platforms | |||
addProperty(module, 'bplist', 'bplist-parser', modules); | |||
addProperty(module, 'et', 'elementtree', modules); | |||
addProperty(module, 'glob', 'glob', modules); | |||
addProperty(module, 'plist', 'plist', modules); | |||
addProperty(module, 'plist_helpers', '../util/plist-helpers', modules); | |||
addProperty(module, 'xml_helpers', '../util/xml-helpers', modules); | |||
/****************************************************************************** | |||
* 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 = modules.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) ? | |||
modules.bplist.parseBuffer(fs.readFileSync(filepath)) : | |||
modules.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, modules.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 = [modules.et.XML(xml_child.xml)]; | |||
result = modules.xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after); | |||
if ( !result) { | |||
throw new Error('Unable to graft xml at selector "' + selector + '" from "' + filepath + '" during config install'); | |||
} | |||
} else { | |||
// plist file | |||
result = modules.plist_helpers.graftPLIST(self.data, xml_child.xml, selector); | |||
if ( !result ) { | |||
throw new Error('Unable to graft plist "' + filepath + '" during config install'); | |||
} | |||
} | |||
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 = [modules.et.XML(xml_child.xml)]; | |||
result = modules.xml_helpers.pruneXML(self.data, xml_to_graft, selector); | |||
} else { | |||
// plist file | |||
result = modules.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 = modules.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 = modules.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 = modules.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; |
@@ -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; |
@@ -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); | |||
}; |
@@ -0,0 +1,500 @@ | |||
/** | |||
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.target = elt.attrib.target || undefined; | |||
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; |
@@ -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); |
@@ -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; |
@@ -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; |
@@ -0,0 +1,220 @@ | |||
/* | |||
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 ansi = require('ansi'); | |||
var EventEmitter = require('events').EventEmitter; | |||
var CordovaError = require('./CordovaError/CordovaError'); | |||
var EOL = require('os').EOL; | |||
var INSTANCE; | |||
/** | |||
* @class CordovaLogger | |||
* | |||
* Implements logging facility that anybody could use. Should not be | |||
* instantiated directly, `CordovaLogger.get()` method should be used instead | |||
* to acquire logger instance | |||
*/ | |||
function CordovaLogger () { | |||
this.levels = {}; | |||
this.colors = {}; | |||
this.stdout = process.stdout; | |||
this.stderr = process.stderr; | |||
this.stdoutCursor = ansi(this.stdout); | |||
this.stderrCursor = ansi(this.stderr); | |||
this.addLevel('verbose', 1000, 'grey'); | |||
this.addLevel('normal' , 2000); | |||
this.addLevel('warn' , 2000, 'yellow'); | |||
this.addLevel('info' , 3000, 'blue'); | |||
this.addLevel('error' , 5000, 'red'); | |||
this.addLevel('results' , 10000); | |||
this.setLevel('normal'); | |||
} | |||
/** | |||
* Static method to create new or acquire existing instance. | |||
* | |||
* @return {CordovaLogger} Logger instance | |||
*/ | |||
CordovaLogger.get = function () { | |||
return INSTANCE || (INSTANCE = new CordovaLogger()); | |||
}; | |||
CordovaLogger.VERBOSE = 'verbose'; | |||
CordovaLogger.NORMAL = 'normal'; | |||
CordovaLogger.WARN = 'warn'; | |||
CordovaLogger.INFO = 'info'; | |||
CordovaLogger.ERROR = 'error'; | |||
CordovaLogger.RESULTS = 'results'; | |||
/** | |||
* Emits log message to process' stdout/stderr depending on message's severity | |||
* and current log level. If severity is less than current logger's level, | |||
* then the message is ignored. | |||
* | |||
* @param {String} logLevel The message's log level. The logger should have | |||
* corresponding level added (via logger.addLevel), otherwise | |||
* `CordovaLogger.NORMAL` level will be used. | |||
* @param {String} message The message, that should be logged to process' | |||
* stdio | |||
* | |||
* @return {CordovaLogger} Current instance, to allow calls chaining. | |||
*/ | |||
CordovaLogger.prototype.log = function (logLevel, message) { | |||
// if there is no such logLevel defined, or provided level has | |||
// less severity than active level, then just ignore this call and return | |||
if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) | |||
// return instance to allow to chain calls | |||
return this; | |||
var isVerbose = this.logLevel === 'verbose'; | |||
var cursor = this.stdoutCursor; | |||
if (message instanceof Error || logLevel === CordovaLogger.ERROR) { | |||
message = formatError(message, isVerbose); | |||
cursor = this.stderrCursor; | |||
} | |||
var color = this.colors[logLevel]; | |||
if (color) { | |||
cursor.bold().fg[color](); | |||
} | |||
cursor.write(message).reset().write(EOL); | |||
return this; | |||
}; | |||
/** | |||
* Adds a new level to logger instance. This method also creates a shortcut | |||
* method to log events with the level provided (i.e. after adding new level | |||
* 'debug', the method `debug(message)`, equal to logger.log('debug', message), | |||
* will be added to logger instance) | |||
* | |||
* @param {String} level A log level name. The levels with the following | |||
* names added by default to every instance: 'verbose', 'normal', 'warn', | |||
* 'info', 'error', 'results' | |||
* @param {Number} severity A number that represents level's severity. | |||
* @param {String} color A valid color name, that will be used to log | |||
* messages with this level. Any CSS color code or RGB value is allowed | |||
* (according to ansi documentation: | |||
* https://github.com/TooTallNate/ansi.js#features) | |||
* | |||
* @return {CordovaLogger} Current instance, to allow calls chaining. | |||
*/ | |||
CordovaLogger.prototype.addLevel = function (level, severity, color) { | |||
this.levels[level] = severity; | |||
if (color) { | |||
this.colors[level] = color; | |||
} | |||
// Define own method with corresponding name | |||
if (!this[level]) { | |||
this[level] = this.log.bind(this, level); | |||
} | |||
return this; | |||
}; | |||
/** | |||
* Sets the current logger level to provided value. If logger doesn't have level | |||
* with this name, `CordovaLogger.NORMAL` will be used. | |||
* | |||
* @param {String} logLevel Level name. The level with this name should be | |||
* added to logger before. | |||
* | |||
* @return {CordovaLogger} Current instance, to allow calls chaining. | |||
*/ | |||
CordovaLogger.prototype.setLevel = function (logLevel) { | |||
this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL; | |||
return this; | |||
}; | |||
/** | |||
* Adjusts the current logger level according to the passed options. | |||
* | |||
* @param {Object|Array} opts An object or args array with options | |||
* | |||
* @return {CordovaLogger} Current instance, to allow calls chaining. | |||
*/ | |||
CordovaLogger.prototype.adjustLevel = function (opts) { | |||
if (opts.verbose || (Array.isArray(opts) && opts.indexOf('--verbose') !== -1)) { | |||
this.setLevel('verbose'); | |||
} else if (opts.silent || (Array.isArray(opts) && opts.indexOf('--silent') !== -1)) { | |||
this.setLevel('error'); | |||
} | |||
return this; | |||
}; | |||
/** | |||
* Attaches logger to EventEmitter instance provided. | |||
* | |||
* @param {EventEmitter} eventEmitter An EventEmitter instance to attach | |||
* logger to. | |||
* | |||
* @return {CordovaLogger} Current instance, to allow calls chaining. | |||
*/ | |||
CordovaLogger.prototype.subscribe = function (eventEmitter) { | |||
if (!(eventEmitter instanceof EventEmitter)) | |||
throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); | |||
eventEmitter.on('verbose', this.verbose) | |||
.on('log', this.normal) | |||
.on('info', this.info) | |||
.on('warn', this.warn) | |||
.on('warning', this.warn) | |||
// Set up event handlers for logging and results emitted as events. | |||
.on('results', this.results); | |||
return this; | |||
}; | |||
function formatError(error, isVerbose) { | |||
var message = ''; | |||
if (error instanceof CordovaError) { | |||
message = error.toString(isVerbose); | |||
} else if (error instanceof Error) { | |||
if (isVerbose) { | |||
message = error.stack; | |||
} else { | |||
message = error.message; | |||
} | |||
} else { | |||
// Plain text error message | |||
message = error; | |||
} | |||
if (typeof message === 'string' && message.toUpperCase().indexOf('ERROR:') !== 0) { | |||
// Needed for backward compatibility with external tools | |||
message = 'Error: ' + message; | |||
} | |||
return message; | |||
} | |||
module.exports = CordovaLogger; |
@@ -0,0 +1,422 @@ | |||
/** | |||
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. | |||
*/ | |||
"use strict"; | |||
var fs = require("fs"); | |||
var path = require("path"); | |||
var shell = require("shelljs"); | |||
var minimatch = require("minimatch"); | |||
/** | |||
* Logging callback used in the FileUpdater methods. | |||
* @callback loggingCallback | |||
* @param {string} message A message describing a single file update operation. | |||
*/ | |||
/** | |||
* Updates a target file or directory with a source file or directory. (Directory updates are | |||
* not recursive.) Stats for target and source items must be passed in. This is an internal | |||
* helper function used by other methods in this module. | |||
* | |||
* @param {?string} sourcePath Source file or directory to be used to update the | |||
* destination. If the source is null, then the destination is deleted if it exists. | |||
* @param {?fs.Stats} sourceStats An instance of fs.Stats for the source path, or null if | |||
* the source does not exist. | |||
* @param {string} targetPath Required destination file or directory to be updated. If it does | |||
* not exist, it will be created. | |||
* @param {?fs.Stats} targetStats An instance of fs.Stats for the target path, or null if | |||
* the target does not exist. | |||
* @param {Object} [options] Optional additional parameters for the update. | |||
* @param {string} [options.rootDir] Optional root directory (such as a project) to which target | |||
* and source path parameters are relative; may be omitted if the paths are absolute. The | |||
* rootDir is always omitted from any logged paths, to make the logs easier to read. | |||
* @param {boolean} [options.all] If true, all files are copied regardless of last-modified times. | |||
* Otherwise, a file is copied if the source's last-modified time is greather than or | |||
* equal to the target's last-modified time, or if the file sizes are different. | |||
* @param {loggingCallback} [log] Optional logging callback that takes a string message | |||
* describing any file operations that are performed. | |||
* @return {boolean} true if any changes were made, or false if the force flag is not set | |||
* and everything was up to date | |||
*/ | |||
function updatePathWithStats(sourcePath, sourceStats, targetPath, targetStats, options, log) { | |||
var updated = false; | |||
var rootDir = (options && options.rootDir) || ""; | |||
var copyAll = (options && options.all) || false; | |||
var targetFullPath = path.join(rootDir || "", targetPath); | |||
if (sourceStats) { | |||
var sourceFullPath = path.join(rootDir || "", sourcePath); | |||
if (targetStats) { | |||
// The target exists. But if the directory status doesn't match the source, delete it. | |||
if (targetStats.isDirectory() && !sourceStats.isDirectory()) { | |||
log("rmdir " + targetPath + " (source is a file)"); | |||
shell.rm("-rf", targetFullPath); | |||
targetStats = null; | |||
updated = true; | |||
} else if (!targetStats.isDirectory() && sourceStats.isDirectory()) { | |||
log("delete " + targetPath + " (source is a directory)"); | |||
shell.rm("-f", targetFullPath); | |||
targetStats = null; | |||
updated = true; | |||
} | |||
} | |||
if (!targetStats) { | |||
if (sourceStats.isDirectory()) { | |||
// The target directory does not exist, so it should be created. | |||
log("mkdir " + targetPath); | |||
shell.mkdir("-p", targetFullPath); | |||
updated = true; | |||
} else if (sourceStats.isFile()) { | |||
// The target file does not exist, so it should be copied from the source. | |||
log("copy " + sourcePath + " " + targetPath + (copyAll ? "" : " (new file)")); | |||
shell.cp("-f", sourceFullPath, targetFullPath); | |||
updated = true; | |||
} | |||
} else if (sourceStats.isFile() && targetStats.isFile()) { | |||
// The source and target paths both exist and are files. | |||
if (copyAll) { | |||
// The caller specified all files should be copied. | |||
log("copy " + sourcePath + " " + targetPath); | |||
shell.cp("-f", sourceFullPath, targetFullPath); | |||
updated = true; | |||
} else { | |||
// Copy if the source has been modified since it was copied to the target, or if | |||
// the file sizes are different. (The latter catches most cases in which something | |||
// was done to the file after copying.) Comparison is >= rather than > to allow | |||
// for timestamps lacking sub-second precision in some filesystems. | |||
if (sourceStats.mtime.getTime() >= targetStats.mtime.getTime() || | |||
sourceStats.size !== targetStats.size) { | |||
log("copy " + sourcePath + " " + targetPath + " (updated file)"); | |||
shell.cp("-f", sourceFullPath, targetFullPath); | |||
updated = true; | |||
} | |||
} | |||
} | |||
} else if (targetStats) { | |||
// The target exists but the source is null, so the target should be deleted. | |||
if (targetStats.isDirectory()) { | |||
log("rmdir " + targetPath + (copyAll ? "" : " (no source)")); | |||
shell.rm("-rf", targetFullPath); | |||
} else { | |||
log("delete " + targetPath + (copyAll ? "" : " (no source)")); | |||
shell.rm("-f", targetFullPath); | |||
} | |||
updated = true; | |||
} | |||
return updated; | |||
} | |||
/** | |||
* Helper for updatePath and updatePaths functions. Queries stats for source and target | |||
* and ensures target directory exists before copying a file. | |||
*/ | |||
function updatePathInternal(sourcePath, targetPath, options, log) { | |||
var rootDir = (options && options.rootDir) || ""; | |||
var targetFullPath = path.join(rootDir, targetPath); | |||
var targetStats = fs.existsSync(targetFullPath) ? fs.statSync(targetFullPath) : null; | |||
var sourceStats = null; | |||
if (sourcePath) { | |||
// A non-null source path was specified. It should exist. | |||
var sourceFullPath = path.join(rootDir, sourcePath); | |||
if (!fs.existsSync(sourceFullPath)) { | |||
throw new Error("Source path does not exist: " + sourcePath); | |||
} | |||
sourceStats = fs.statSync(sourceFullPath); | |||
// Create the target's parent directory if it doesn't exist. | |||
var parentDir = path.dirname(targetFullPath); | |||
if (!fs.existsSync(parentDir)) { | |||
shell.mkdir("-p", parentDir); | |||
} | |||
} | |||
return updatePathWithStats(sourcePath, sourceStats, targetPath, targetStats, options, log); | |||
} | |||
/** | |||
* Updates a target file or directory with a source file or directory. (Directory updates are | |||
* not recursive.) | |||
* | |||
* @param {?string} sourcePath Source file or directory to be used to update the | |||
* destination. If the source is null, then the destination is deleted if it exists. | |||
* @param {string} targetPath Required destination file or directory to be updated. If it does | |||
* not exist, it will be created. | |||
* @param {Object} [options] Optional additional parameters for the update. | |||
* @param {string} [options.rootDir] Optional root directory (such as a project) to which target | |||
* and source path parameters are relative; may be omitted if the paths are absolute. The | |||
* rootDir is always omitted from any logged paths, to make the logs easier to read. | |||
* @param {boolean} [options.all] If true, all files are copied regardless of last-modified times. | |||
* Otherwise, a file is copied if the source's last-modified time is greather than or | |||
* equal to the target's last-modified time, or if the file sizes are different. | |||
* @param {loggingCallback} [log] Optional logging callback that takes a string message | |||
* describing any file operations that are performed. | |||
* @return {boolean} true if any changes were made, or false if the force flag is not set | |||
* and everything was up to date | |||
*/ | |||
function updatePath(sourcePath, targetPath, options, log) { | |||
if (sourcePath !== null && typeof sourcePath !== "string") { | |||
throw new Error("A source path (or null) is required."); | |||
} | |||
if (!targetPath || typeof targetPath !== "string") { | |||
throw new Error("A target path is required."); | |||
} | |||
log = log || function(message) { }; | |||
return updatePathInternal(sourcePath, targetPath, options, log); | |||
} | |||
/** | |||
* Updates files and directories based on a mapping from target paths to source paths. Targets | |||
* with null sources in the map are deleted. | |||
* | |||
* @param {Object} pathMap A dictionary mapping from target paths to source paths. | |||
* @param {Object} [options] Optional additional parameters for the update. | |||
* @param {string} [options.rootDir] Optional root directory (such as a project) to which target | |||
* and source path parameters are relative; may be omitted if the paths are absolute. The | |||
* rootDir is always omitted from any logged paths, to make the logs easier to read. | |||
* @param {boolean} [options.all] If true, all files are copied regardless of last-modified times. | |||
* Otherwise, a file is copied if the source's last-modified time is greather than or | |||
* equal to the target's last-modified time, or if the file sizes are different. | |||
* @param {loggingCallback} [log] Optional logging callback that takes a string message | |||
* describing any file operations that are performed. | |||
* @return {boolean} true if any changes were made, or false if the force flag is not set | |||
* and everything was up to date | |||
*/ | |||
function updatePaths(pathMap, options, log) { | |||
if (!pathMap || typeof pathMap !== "object" || Array.isArray(pathMap)) { | |||
throw new Error("An object mapping from target paths to source paths is required."); | |||
} | |||
log = log || function(message) { }; | |||
var updated = false; | |||
// Iterate in sorted order to ensure directories are created before files under them. | |||
Object.keys(pathMap).sort().forEach(function (targetPath) { | |||
var sourcePath = pathMap[targetPath]; | |||
updated = updatePathInternal(sourcePath, targetPath, options, log) || updated; | |||
}); | |||
return updated; | |||
} | |||
/** | |||
* Updates a target directory with merged files and subdirectories from source directories. | |||
* | |||
* @param {string|string[]} sourceDirs Required source directory or array of source directories | |||
* to be merged into the target. The directories are listed in order of precedence; files in | |||
* directories later in the array supersede files in directories earlier in the array | |||
* (regardless of timestamps). | |||
* @param {string} targetDir Required destination directory to be updated. If it does not exist, | |||
* it will be created. If it exists, newer files from source directories will be copied over, | |||
* and files missing in the source directories will be deleted. | |||
* @param {Object} [options] Optional additional parameters for the update. | |||
* @param {string} [options.rootDir] Optional root directory (such as a project) to which target | |||
* and source path parameters are relative; may be omitted if the paths are absolute. The | |||
* rootDir is always omitted from any logged paths, to make the logs easier to read. | |||
* @param {boolean} [options.all] If true, all files are copied regardless of last-modified times. | |||
* Otherwise, a file is copied if the source's last-modified time is greather than or | |||
* equal to the target's last-modified time, or if the file sizes are different. | |||
* @param {string|string[]} [options.include] Optional glob string or array of glob strings that | |||
* are tested against both target and source relative paths to determine if they are included | |||
* in the merge-and-update. If unspecified, all items are included. | |||
* @param {string|string[]} [options.exclude] Optional glob string or array of glob strings that | |||
* are tested against both target and source relative paths to determine if they are excluded | |||
* from the merge-and-update. Exclusions override inclusions. If unspecified, no items are | |||
* excluded. | |||
* @param {loggingCallback} [log] Optional logging callback that takes a string message | |||
* describing any file operations that are performed. | |||
* @return {boolean} true if any changes were made, or false if the force flag is not set | |||
* and everything was up to date | |||
*/ | |||
function mergeAndUpdateDir(sourceDirs, targetDir, options, log) { | |||
if (sourceDirs && typeof sourceDirs === "string") { | |||
sourceDirs = [ sourceDirs ]; | |||
} else if (!Array.isArray(sourceDirs)) { | |||
throw new Error("A source directory path or array of paths is required."); | |||
} | |||
if (!targetDir || typeof targetDir !== "string") { | |||
throw new Error("A target directory path is required."); | |||
} | |||
log = log || function(message) { }; | |||
var rootDir = (options && options.rootDir) || ""; | |||
var include = (options && options.include) || [ "**" ]; | |||
if (typeof include === "string") { | |||
include = [ include ]; | |||
} else if (!Array.isArray(include)) { | |||
throw new Error("Include parameter must be a glob string or array of glob strings."); | |||
} | |||
var exclude = (options && options.exclude) || []; | |||
if (typeof exclude === "string") { | |||
exclude = [ exclude ]; | |||
} else if (!Array.isArray(exclude)) { | |||
throw new Error("Exclude parameter must be a glob string or array of glob strings."); | |||
} | |||
// Scan the files in each of the source directories. | |||
var sourceMaps = []; | |||
for (var i in sourceDirs) { | |||
var sourceFullPath = path.join(rootDir, sourceDirs[i]); | |||
if (!fs.existsSync(sourceFullPath)) { | |||
throw new Error("Source directory does not exist: " + sourceDirs[i]); | |||
} | |||
sourceMaps[i] = mapDirectory(rootDir, sourceDirs[i], include, exclude); | |||
} | |||
// Scan the files in the target directory, if it exists. | |||
var targetMap = {}; | |||
var targetFullPath = path.join(rootDir, targetDir); | |||
if (fs.existsSync(targetFullPath)) { | |||
targetMap = mapDirectory(rootDir, targetDir, include, exclude); | |||
} | |||
var pathMap = mergePathMaps(sourceMaps, targetMap, targetDir); | |||
var updated = false; | |||
// Iterate in sorted order to ensure directories are created before files under them. | |||
Object.keys(pathMap).sort().forEach(function (subPath) { | |||
var entry = pathMap[subPath]; | |||
updated = updatePathWithStats( | |||
entry.sourcePath, | |||
entry.sourceStats, | |||
entry.targetPath, | |||
entry.targetStats, | |||
options, | |||
log) || updated; | |||
}); | |||
return updated; | |||
} | |||
/** | |||
* Creates a dictionary map of all files and directories under a path. | |||
*/ | |||
function mapDirectory(rootDir, subDir, include, exclude) { | |||
var dirMap = { "": { subDir: subDir, stats: fs.statSync(path.join(rootDir, subDir)) } }; | |||
mapSubdirectory(rootDir, subDir, "", include, exclude, dirMap); | |||
return dirMap; | |||
function mapSubdirectory(rootDir, subDir, relativeDir, include, exclude, dirMap) { | |||
var itemMapped = false; | |||
var items = fs.readdirSync(path.join(rootDir, subDir, relativeDir)); | |||
for (var i in items) { | |||
var relativePath = path.join(relativeDir, items[i]); | |||
// Skip any files or directories (and everything under) that match an exclude glob. | |||
if (matchGlobArray(relativePath, exclude)) { | |||
continue; | |||
} | |||
// Stats obtained here (required at least to know where to recurse in directories) | |||
// are saved for later, where the modified times may also be used. This minimizes | |||
// the number of file I/O operations performed. | |||
var fullPath = path.join(rootDir, subDir, relativePath); | |||
var stats = fs.statSync(fullPath); | |||
if (stats.isDirectory()) { | |||
// Directories are included if either something under them is included or they | |||
// match an include glob. | |||
if (mapSubdirectory(rootDir, subDir, relativePath, include, exclude, dirMap) || | |||
matchGlobArray(relativePath, include)) { | |||
dirMap[relativePath] = { subDir: subDir, stats: stats }; | |||
itemMapped = true; | |||
} | |||
} else if (stats.isFile()) { | |||
// Files are included only if they match an include glob. | |||
if (matchGlobArray(relativePath, include)) { | |||
dirMap[relativePath] = { subDir: subDir, stats: stats }; | |||
itemMapped = true; | |||
} | |||
} | |||
} | |||
return itemMapped; | |||
} | |||
function matchGlobArray(path, globs) { | |||
for (var i in globs) { | |||
if (minimatch(path, globs[i])) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
} | |||
/** | |||
* Merges together multiple source maps and a target map into a single mapping from | |||
* relative paths to objects with target and source paths and stats. | |||
*/ | |||
function mergePathMaps(sourceMaps, targetMap, targetDir) { | |||
// Merge multiple source maps together, along with target path info. | |||
// Entries in later source maps override those in earlier source maps. | |||
// Target stats will be filled in below for targets that exist. | |||
var pathMap = {}; | |||
sourceMaps.forEach(function (sourceMap) { | |||
for (var sourceSubPath in sourceMap) { | |||
var sourceEntry = sourceMap[sourceSubPath]; | |||
pathMap[sourceSubPath] = { | |||
targetPath: path.join(targetDir, sourceSubPath), | |||
targetStats: null, | |||
sourcePath: path.join(sourceEntry.subDir, sourceSubPath), | |||
sourceStats: sourceEntry.stats | |||
}; | |||
} | |||
}); | |||
// Fill in target stats for targets that exist, and create entries | |||
// for targets that don't have any corresponding sources. | |||
for (var subPath in targetMap) { | |||
var entry = pathMap[subPath]; | |||
if (entry) { | |||
entry.targetStats = targetMap[subPath].stats; | |||
} else { | |||
pathMap[subPath] = { | |||
targetPath: path.join(targetDir, subPath), | |||
targetStats: targetMap[subPath].stats, | |||
sourcePath: null, | |||
sourceStats: null | |||
}; | |||
} | |||
} | |||
return pathMap; | |||
} | |||
module.exports = { | |||
updatePath: updatePath, | |||
updatePaths: updatePaths, | |||
mergeAndUpdateDir: mergeAndUpdateDir | |||
}; | |||
@@ -0,0 +1,279 @@ | |||
/* | |||
* 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; | |||
}; | |||
/** | |||
* @chaining | |||
* Generates and adds metadata for provided plugin into associated <platform>.json file | |||
* | |||
* @param {PluginInfo} pluginInfo A pluginInfo instance to add metadata from | |||
* @returns {this} Current PlatformJson instance to allow calls chaining | |||
*/ | |||
PlatformJson.prototype.addPluginMetadata = function (pluginInfo) { | |||
var installedModules = this.root.modules || []; | |||
var installedPaths = installedModules.map(function (installedModule) { | |||
return installedModule.file; | |||
}); | |||
var modulesToInstall = pluginInfo.getJsModules(this.platform) | |||
.map(function (module) { | |||
return new ModuleMetadata(pluginInfo.id, module); | |||
}) | |||
.filter(function (metadata) { | |||
// Filter out modules which are already added to metadata | |||
return installedPaths.indexOf(metadata.file) === -1; | |||
}); | |||
this.root.modules = installedModules.concat(modulesToInstall); | |||
this.root.plugin_metadata = this.root.plugin_metadata || {}; | |||
this.root.plugin_metadata[pluginInfo.id] = pluginInfo.version; | |||
return this; | |||
}; | |||
PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) { | |||
var pluginsList = isTopLevel ? | |||
this.root.installed_plugins : | |||
this.root.dependent_plugins; | |||
delete pluginsList[pluginId]; | |||
return this; | |||
}; | |||
/** | |||
* @chaining | |||
* Removes metadata for provided plugin from associated file | |||
* | |||
* @param {PluginInfo} pluginInfo A PluginInfo instance to which modules' metadata | |||
* we need to remove | |||
* | |||
* @returns {this} Current PlatformJson instance to allow calls chaining | |||
*/ | |||
PlatformJson.prototype.removePluginMetadata = function (pluginInfo) { | |||
var modulesToRemove = pluginInfo.getJsModules(this.platform) | |||
.map(function (jsModule) { | |||
return ['plugins', pluginInfo.id, jsModule.src].join('/'); | |||
}); | |||
var installedModules = this.root.modules || []; | |||
this.root.modules = installedModules | |||
.filter(function (installedModule) { | |||
// Leave only those metadatas which 'file' is not in removed modules | |||
return (modulesToRemove.indexOf(installedModule.file) === -1); | |||
}); | |||
if (this.root.plugin_metadata) { | |||
delete this.root.plugin_metadata[pluginInfo.id]; | |||
} | |||
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; | |||
}; | |||
/** | |||
* Generates a metadata for all installed plugins and js modules. The resultant | |||
* string is ready to be written to 'cordova_plugins.js' | |||
* | |||
* @returns {String} cordova_plugins.js contents | |||
*/ | |||
PlatformJson.prototype.generateMetadata = function () { | |||
return [ | |||
'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {', | |||
'module.exports = ' + JSON.stringify(this.root.modules, null, 4) + ';', | |||
'module.exports.metadata = ', | |||
'// TOP OF METADATA', | |||
JSON.stringify(this.root.plugin_metadata, null, 4) + ';', | |||
'// BOTTOM OF METADATA', | |||
'});' // Close cordova.define. | |||
].join('\n'); | |||
}; | |||
/** | |||
* @chaining | |||
* Generates and then saves metadata to specified file. Doesn't check if file exists. | |||
* | |||
* @param {String} destination File metadata will be written to | |||
* @return {PlatformJson} PlatformJson instance | |||
*/ | |||
PlatformJson.prototype.generateAndSaveMetadata = function (destination) { | |||
var meta = this.generateMetadata(); | |||
shelljs.mkdir('-p', path.dirname(destination)); | |||
fs.writeFileSync(destination, meta, 'utf-8'); | |||
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; | |||
} | |||
/** | |||
* @constructor | |||
* @class ModuleMetadata | |||
* | |||
* Creates a ModuleMetadata object that represents module entry in 'cordova_plugins.js' | |||
* file at run time | |||
* | |||
* @param {String} pluginId Plugin id where this module installed from | |||
* @param (JsModule|Object) jsModule A js-module entry from PluginInfo class to generate metadata for | |||
*/ | |||
function ModuleMetadata (pluginId, jsModule) { | |||
if (!pluginId) throw new TypeError('pluginId argument must be a valid plugin id'); | |||
if (!jsModule.src && !jsModule.name) throw new TypeError('jsModule argument must contain src or/and name properties'); | |||
this.id = pluginId + '.' + ( jsModule.name || jsModule.src.match(/([^\/]+)\.js/)[1] ); | |||
this.file = ['plugins', pluginId, jsModule.src].join('/'); | |||
this.pluginId = pluginId; | |||
if (jsModule.clobbers && jsModule.clobbers.length > 0) { | |||
this.clobbers = jsModule.clobbers.map(function(o) { return o.target; }); | |||
} | |||
if (jsModule.merges && jsModule.merges.length > 0) { | |||
this.merges = jsModule.merges.map(function(o) { return o.target; }); | |||
} | |||
if (jsModule.runs) { | |||
this.runs = true; | |||
} | |||
} | |||
module.exports = PlatformJson; | |||
@@ -0,0 +1,406 @@ | |||
/** | |||
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, laxcomma:true, laxbreak:true */ | |||
/* | |||
A class for holidng the information currently stored in plugin.xml | |||
It should also be able to answer questions like whether the plugin | |||
is compatible with a given engine version. | |||
TODO (kamrik): refactor this to not use sync functions and return promises. | |||
*/ | |||
var path = require('path') | |||
, fs = require('fs') | |||
, xml_helpers = require('../util/xml-helpers') | |||
, CordovaError = require('../CordovaError/CordovaError') | |||
; | |||
function PluginInfo(dirname) { | |||
var self = this; | |||
// METHODS | |||
// Defined inside the constructor to avoid the "this" binding problems. | |||
// <preference> tag | |||
// Example: <preference name="API_KEY" /> | |||
// Used to require a variable to be specified via --variable when installing the plugin. | |||
// returns { key : default | null} | |||
self.getPreferences = getPreferences; | |||
function getPreferences(platform) { | |||
return _getTags(self._et, 'preference', platform, _parsePreference) | |||
.reduce(function (preferences, pref) { | |||
preferences[pref.preference] = pref.default; | |||
return preferences; | |||
}, {}); | |||
} | |||
function _parsePreference(prefTag) { | |||
var name = prefTag.attrib.name.toUpperCase(); | |||
var def = prefTag.attrib.default || null; | |||
return {preference: name, default: def}; | |||
} | |||
// <asset> | |||
self.getAssets = getAssets; | |||
function getAssets(platform) { | |||
var assets = _getTags(self._et, 'asset', platform, _parseAsset); | |||
return assets; | |||
} | |||
function _parseAsset(tag) { | |||
var src = tag.attrib.src; | |||
var target = tag.attrib.target; | |||
if ( !src || !target) { | |||
var msg = | |||
'Malformed <asset> tag. Both "src" and "target" attributes' | |||
+ 'must be specified in\n' | |||
+ self.filepath | |||
; | |||
throw new Error(msg); | |||
} | |||
var asset = { | |||
itemType: 'asset', | |||
src: src, | |||
target: target | |||
}; | |||
return asset; | |||
} | |||
// <dependency> | |||
// Example: | |||
// <dependency id="com.plugin.id" | |||
// url="https://github.com/myuser/someplugin" | |||
// commit="428931ada3891801" | |||
// subdir="some/path/here" /> | |||
self.getDependencies = getDependencies; | |||
function getDependencies(platform) { | |||
var deps = _getTags( | |||
self._et, | |||
'dependency', | |||
platform, | |||
_parseDependency | |||
); | |||
return deps; | |||
} | |||
function _parseDependency(tag) { | |||
var dep = | |||
{ id : tag.attrib.id | |||
, url : tag.attrib.url || '' | |||
, subdir : tag.attrib.subdir || '' | |||
, commit : tag.attrib.commit | |||
}; | |||
dep.git_ref = dep.commit; | |||
if ( !dep.id ) { | |||
var msg = | |||
'<dependency> tag is missing id attribute in ' | |||
+ self.filepath | |||
; | |||
throw new CordovaError(msg); | |||
} | |||
return dep; | |||
} | |||
// <config-file> tag | |||
self.getConfigFiles = getConfigFiles; | |||
function getConfigFiles(platform) { | |||
var configFiles = _getTags(self._et, 'config-file', platform, _parseConfigFile); | |||
return configFiles; | |||
} | |||
function _parseConfigFile(tag) { | |||
var configFile = | |||
{ target : tag.attrib['target'] | |||
, parent : tag.attrib['parent'] | |||
, after : tag.attrib['after'] | |||
, xmls : tag.getchildren() | |||
// To support demuxing via versions | |||
, versions : tag.attrib['versions'] | |||
, deviceTarget: tag.attrib['device-target'] | |||
}; | |||
return configFile; | |||
} | |||
// <info> tags, both global and within a <platform> | |||
// TODO (kamrik): Do we ever use <info> under <platform>? Example wanted. | |||
self.getInfo = getInfo; | |||
function getInfo(platform) { | |||
var infos = _getTags( | |||
self._et, | |||
'info', | |||
platform, | |||
function(elem) { return elem.text; } | |||
); | |||
// Filter out any undefined or empty strings. | |||
infos = infos.filter(Boolean); | |||
return infos; | |||
} | |||
// <source-file> | |||
// Examples: | |||
// <source-file src="src/ios/someLib.a" framework="true" /> | |||
// <source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" /> | |||
self.getSourceFiles = getSourceFiles; | |||
function getSourceFiles(platform) { | |||
var sourceFiles = _getTagsInPlatform(self._et, 'source-file', platform, _parseSourceFile); | |||
return sourceFiles; | |||
} | |||
function _parseSourceFile(tag) { | |||
return { | |||
itemType: 'source-file', | |||
src: tag.attrib.src, | |||
framework: isStrTrue(tag.attrib.framework), | |||
weak: isStrTrue(tag.attrib.weak), | |||
compilerFlags: tag.attrib['compiler-flags'], | |||
targetDir: tag.attrib['target-dir'] | |||
}; | |||
} | |||
// <header-file> | |||
// Example: | |||
// <header-file src="CDVFoo.h" /> | |||
self.getHeaderFiles = getHeaderFiles; | |||
function getHeaderFiles(platform) { | |||
var headerFiles = _getTagsInPlatform(self._et, 'header-file', platform, function(tag) { | |||
return { | |||
itemType: 'header-file', | |||
src: tag.attrib.src, | |||
targetDir: tag.attrib['target-dir'] | |||
}; | |||
}); | |||
return headerFiles; | |||
} | |||
// <resource-file> | |||
// Example: | |||
// <resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" device-target="win" arch="x86" versions=">=8.1" /> | |||
self.getResourceFiles = getResourceFiles; | |||
function getResourceFiles(platform) { | |||
var resourceFiles = _getTagsInPlatform(self._et, 'resource-file', platform, function(tag) { | |||
return { | |||
itemType: 'resource-file', | |||
src: tag.attrib.src, | |||
target: tag.attrib.target, | |||
versions: tag.attrib.versions, | |||
deviceTarget: tag.attrib['device-target'], | |||
arch: tag.attrib.arch | |||
}; | |||
}); | |||
return resourceFiles; | |||
} | |||
// <lib-file> | |||
// Example: | |||
// <lib-file src="src/BlackBerry10/native/device/libfoo.so" arch="device" /> | |||
self.getLibFiles = getLibFiles; | |||
function getLibFiles(platform) { | |||
var libFiles = _getTagsInPlatform(self._et, 'lib-file', platform, function(tag) { | |||
return { | |||
itemType: 'lib-file', | |||
src: tag.attrib.src, | |||
arch: tag.attrib.arch, | |||
Include: tag.attrib.Include, | |||
versions: tag.attrib.versions, | |||
deviceTarget: tag.attrib['device-target'] || tag.attrib.target | |||
}; | |||
}); | |||
return libFiles; | |||
} | |||
// <hook> | |||
// Example: | |||
// <hook type="before_build" src="scripts/beforeBuild.js" /> | |||
self.getHookScripts = getHookScripts; | |||
function getHookScripts(hook, platforms) { | |||
var scriptElements = self._et.findall('./hook'); | |||
if(platforms) { | |||
platforms.forEach(function (platform) { | |||
scriptElements = scriptElements.concat(self._et.findall('./platform[@name="' + platform + '"]/hook')); | |||
}); | |||
} | |||
function filterScriptByHookType(el) { | |||
return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook; | |||
} | |||
return scriptElements.filter(filterScriptByHookType); | |||
} | |||
self.getJsModules = getJsModules; | |||
function getJsModules(platform) { | |||
var modules = _getTags(self._et, 'js-module', platform, _parseJsModule); | |||
return modules; | |||
} | |||
function _parseJsModule(tag) { | |||
var ret = { | |||
itemType: 'js-module', | |||
name: tag.attrib.name, | |||
src: tag.attrib.src, | |||
clobbers: tag.findall('clobbers').map(function(tag) { return { target: tag.attrib.target }; }), | |||
merges: tag.findall('merges').map(function(tag) { return { target: tag.attrib.target }; }), | |||
runs: tag.findall('runs').length > 0 | |||
}; | |||
return ret; | |||
} | |||
self.getEngines = function() { | |||
return self._et.findall('engines/engine').map(function(n) { | |||
return { | |||
name: n.attrib.name, | |||
version: n.attrib.version, | |||
platform: n.attrib.platform, | |||
scriptSrc: n.attrib.scriptSrc | |||
}; | |||
}); | |||
}; | |||
self.getPlatforms = function() { | |||
return self._et.findall('platform').map(function(n) { | |||
return { name: n.attrib.name }; | |||
}); | |||
}; | |||
self.getPlatformsArray = function() { | |||
return self._et.findall('platform').map(function(n) { | |||
return n.attrib.name; | |||
}); | |||
}; | |||
self.getFrameworks = function(platform) { | |||
return _getTags(self._et, 'framework', platform, function(el) { | |||
var ret = { | |||
itemType: 'framework', | |||
type: el.attrib.type, | |||
parent: el.attrib.parent, | |||
custom: isStrTrue(el.attrib.custom), | |||
src: el.attrib.src, | |||
weak: isStrTrue(el.attrib.weak), | |||
versions: el.attrib.versions, | |||
targetDir: el.attrib['target-dir'], | |||
deviceTarget: el.attrib['device-target'] || el.attrib.target, | |||
arch: el.attrib.arch | |||
}; | |||
return ret; | |||
}); | |||
}; | |||
self.getFilesAndFrameworks = getFilesAndFrameworks; | |||
function getFilesAndFrameworks(platform) { | |||
// Please avoid changing the order of the calls below, files will be | |||
// installed in this order. | |||
var items = [].concat( | |||
self.getSourceFiles(platform), | |||
self.getHeaderFiles(platform), | |||
self.getResourceFiles(platform), | |||
self.getFrameworks(platform), | |||
self.getLibFiles(platform) | |||
); | |||
return items; | |||
} | |||
///// End of PluginInfo methods ///// | |||
///// PluginInfo Constructor logic ///// | |||
self.filepath = path.join(dirname, 'plugin.xml'); | |||
if (!fs.existsSync(self.filepath)) { | |||
throw new CordovaError('Cannot find plugin.xml for plugin "' + path.basename(dirname) + '". Please try adding it again.'); | |||
} | |||
self.dir = dirname; | |||
var et = self._et = xml_helpers.parseElementtreeSync(self.filepath); | |||
var pelem = et.getroot(); | |||
self.id = pelem.attrib.id; | |||
self.version = pelem.attrib.version; | |||
// Optional fields | |||
self.name = pelem.findtext('name'); | |||
self.description = pelem.findtext('description'); | |||
self.license = pelem.findtext('license'); | |||
self.repo = pelem.findtext('repo'); | |||
self.issue = pelem.findtext('issue'); | |||
self.keywords = pelem.findtext('keywords'); | |||
self.info = pelem.findtext('info'); | |||
if (self.keywords) { | |||
self.keywords = self.keywords.split(',').map( function(s) { return s.trim(); } ); | |||
} | |||
self.getKeywordsAndPlatforms = function () { | |||
var ret = self.keywords || []; | |||
return ret.concat('ecosystem:cordova').concat(addCordova(self.getPlatformsArray())); | |||
}; | |||
} // End of PluginInfo constructor. | |||
// Helper function used to prefix every element of an array with cordova- | |||
// Useful when we want to modify platforms to be cordova-platform | |||
function addCordova(someArray) { | |||
var newArray = someArray.map(function(element) { | |||
return 'cordova-' + element; | |||
}); | |||
return newArray; | |||
} | |||
// Helper function used by most of the getSomething methods of PluginInfo. | |||
// Get all elements of a given name. Both in root and in platform sections | |||
// for the given platform. If transform is given and is a function, it is | |||
// applied to each element. | |||
function _getTags(pelem, tag, platform, transform) { | |||
var platformTag = pelem.find('./platform[@name="' + platform + '"]'); | |||
var tagsInRoot = pelem.findall(tag); | |||
tagsInRoot = tagsInRoot || []; | |||
var tagsInPlatform = platformTag ? platformTag.findall(tag) : []; | |||
var tags = tagsInRoot.concat(tagsInPlatform); | |||
if ( typeof transform === 'function' ) { | |||
tags = tags.map(transform); | |||
} | |||
return tags; | |||
} | |||
// Same as _getTags() but only looks inside a platfrom section. | |||
function _getTagsInPlatform(pelem, tag, platform, transform) { | |||
var platformTag = pelem.find('./platform[@name="' + platform + '"]'); | |||
var tags = platformTag ? platformTag.findall(tag) : []; | |||
if ( typeof transform === 'function' ) { | |||
tags = tags.map(transform); | |||
} | |||
return tags; | |||
} | |||
// Check if x is a string 'true'. | |||
function isStrTrue(x) { | |||
return String(x).toLowerCase() == 'true'; | |||
} | |||
module.exports = PluginInfo; | |||
// Backwards compat: | |||
PluginInfo.PluginInfo = PluginInfo; | |||
PluginInfo.loadPluginsDir = function(dir) { | |||
var PluginInfoProvider = require('./PluginInfoProvider'); | |||
return new PluginInfoProvider().getAllWithinSearchPath(dir); | |||
}; |
@@ -0,0 +1,82 @@ | |||
/** | |||
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, laxcomma:true, laxbreak:true */ | |||
var fs = require('fs'); | |||
var path = require('path'); | |||
var PluginInfo = require('./PluginInfo'); | |||
var events = require('../events'); | |||
function PluginInfoProvider() { | |||
this._cache = {}; | |||
this._getAllCache = {}; | |||
} | |||
PluginInfoProvider.prototype.get = function(dirName) { | |||
var absPath = path.resolve(dirName); | |||
if (!this._cache[absPath]) { | |||
this._cache[absPath] = new PluginInfo(dirName); | |||
} | |||
return this._cache[absPath]; | |||
}; | |||
// Normally you don't need to put() entries, but it's used | |||
// when copying plugins, and in unit tests. | |||
PluginInfoProvider.prototype.put = function(pluginInfo) { | |||
var absPath = path.resolve(pluginInfo.dir); | |||
this._cache[absPath] = pluginInfo; | |||
}; | |||
// Used for plugin search path processing. | |||
// Given a dir containing multiple plugins, create a PluginInfo object for | |||
// each of them and return as array. | |||
// Should load them all in parallel and return a promise, but not yet. | |||
PluginInfoProvider.prototype.getAllWithinSearchPath = function(dirName) { | |||
var absPath = path.resolve(dirName); | |||
if (!this._getAllCache[absPath]) { | |||
this._getAllCache[absPath] = getAllHelper(absPath, this); | |||
} | |||
return this._getAllCache[absPath]; | |||
}; | |||
function getAllHelper(absPath, provider) { | |||
if (!fs.existsSync(absPath)){ | |||
return []; | |||
} | |||
// If dir itself is a plugin, return it in an array with one element. | |||
if (fs.existsSync(path.join(absPath, 'plugin.xml'))) { | |||
return [provider.get(absPath)]; | |||
} | |||
var subdirs = fs.readdirSync(absPath); | |||
var plugins = []; | |||
subdirs.forEach(function(subdir) { | |||
var d = path.join(absPath, subdir); | |||
if (fs.existsSync(path.join(d, 'plugin.xml'))) { | |||
try { | |||
plugins.push(provider.get(d)); | |||
} catch (e) { | |||
events.emit('warn', 'Error parsing ' + path.join(d, 'plugin.xml.\n' + e.stack)); | |||
} | |||
} | |||
}); | |||
return plugins; | |||
} | |||
module.exports = PluginInfoProvider; |
@@ -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 Q = require('q'); | |||
var fs = require('fs'); | |||
var path = require('path'); | |||
var ActionStack = require('./ActionStack'); | |||
var PlatformJson = require('./PlatformJson'); | |||
var CordovaError = require('./CordovaError/CordovaError'); | |||
var PlatformMunger = require('./ConfigChanges/ConfigChanges').PlatformMunger; | |||
var PluginInfoProvider = require('./PluginInfo/PluginInfoProvider'); | |||
/** | |||
* @constructor | |||
* @class PluginManager | |||
* Represents an entity for adding/removing plugins for platforms | |||
* | |||
* @param {String} platform Platform name | |||
* @param {Object} locations - Platform files and directories | |||
* @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from | |||
*/ | |||
function PluginManager(platform, locations, ideProject) { | |||
this.platform = platform; | |||
this.locations = locations; | |||
this.project = ideProject; | |||
var platformJson = PlatformJson.load(locations.root, platform); | |||
this.munger = new PlatformMunger(platform, locations.root, platformJson, new PluginInfoProvider()); | |||
} | |||
/** | |||
* @constructs PluginManager | |||
* A convenience shortcut to new PluginManager(...) | |||
* | |||
* @param {String} platform Platform name | |||
* @param {Object} locations - Platform files and directories | |||
* @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from | |||
* @returns new PluginManager instance | |||
*/ | |||
PluginManager.get = function(platform, locations, ideProject) { | |||
return new PluginManager(platform, locations, ideProject); | |||
}; | |||
PluginManager.INSTALL = 'install'; | |||
PluginManager.UNINSTALL = 'uninstall'; | |||
module.exports = PluginManager; | |||
/** | |||
* Describes and implements common plugin installation/uninstallation routine. The flow is the following: | |||
* * Validate and set defaults for options. Note that options are empty by default. Everything | |||
* needed for platform IDE project must be passed from outside. Plugin variables (which | |||
* are the part of the options) also must be already populated with 'PACKAGE_NAME' variable. | |||
* * Collect all plugin's native and web files, get installers/uninstallers and process | |||
* all these via ActionStack. | |||
* * Save the IDE project, so the changes made by installers are persisted. | |||
* * Generate config changes munge for plugin and apply it to all required files | |||
* * Generate metadata for plugin and plugin modules and save it to 'cordova_plugins.js' | |||
* | |||
* @param {PluginInfo} plugin A PluginInfo structure representing plugin to install | |||
* @param {Object} [options={}] An installation options. It is expected but is not necessary | |||
* that options would contain 'variables' inner object with 'PACKAGE_NAME' field set by caller. | |||
* | |||
* @returns {Promise} Returns a Q promise, either resolved in case of success, rejected otherwise. | |||
*/ | |||
PluginManager.prototype.doOperation = function (operation, plugin, options) { | |||
if (operation !== PluginManager.INSTALL && operation !== PluginManager.UNINSTALL) | |||
return Q.reject(new CordovaError('The parameter is incorrect. The opeation must be either "add" or "remove"')); | |||
if (!plugin || plugin.constructor.name !== 'PluginInfo') | |||
return Q.reject(new CordovaError('The parameter is incorrect. The first parameter should be a PluginInfo instance')); | |||
// Set default to empty object to play safe when accesing properties | |||
options = options || {}; | |||
var self = this; | |||
var actions = new ActionStack(); | |||
// gather all files need to be handled during operation ... | |||
plugin.getFilesAndFrameworks(this.platform) | |||
.concat(plugin.getAssets(this.platform)) | |||
.concat(plugin.getJsModules(this.platform)) | |||
// ... put them into stack ... | |||
.forEach(function(item) { | |||
var installer = self.project.getInstaller(item.itemType); | |||
var uninstaller = self.project.getUninstaller(item.itemType); | |||
var actionArgs = [item, plugin, self.project, options]; | |||
var action; | |||
if (operation === PluginManager.INSTALL) { | |||
action = actions.createAction.apply(actions, [installer, actionArgs, uninstaller, actionArgs]); | |||
} else /* op === PluginManager.UNINSTALL */{ | |||
action = actions.createAction.apply(actions, [uninstaller, actionArgs, installer, actionArgs]); | |||
} | |||
actions.push(action); | |||
}); | |||
// ... and run through the action stack | |||
return actions.process(this.platform) | |||
.then(function () { | |||
if (self.project.write) { | |||
self.project.write(); | |||
} | |||
if (operation === PluginManager.INSTALL) { | |||
// Ignore passed `is_top_level` option since platform itself doesn't know | |||
// anything about managing dependencies - it's responsibility of caller. | |||
self.munger.add_plugin_changes(plugin, options.variables, /*is_top_level=*/true, /*should_increment=*/true); | |||
self.munger.platformJson.addPluginMetadata(plugin); | |||
} else { | |||
self.munger.remove_plugin_changes(plugin, /*is_top_level=*/true); | |||
self.munger.platformJson.removePluginMetadata(plugin); | |||
} | |||
// Save everything (munge and plugin/modules metadata) | |||
self.munger.save_all(); | |||
var metadata = self.munger.platformJson.generateMetadata(); | |||
fs.writeFileSync(path.join(self.locations.www, 'cordova_plugins.js'), metadata, 'utf-8'); | |||
// CB-11022 save plugin metadata to both www and platform_www if options.usePlatformWww is specified | |||
if (options.usePlatformWww) { | |||
fs.writeFileSync(path.join(self.locations.platformWww, 'cordova_plugins.js'), metadata, 'utf-8'); | |||
} | |||
}); | |||
}; | |||
PluginManager.prototype.addPlugin = function (plugin, installOptions) { | |||
return this.doOperation(PluginManager.INSTALL, plugin, installOptions); | |||
}; | |||
PluginManager.prototype.removePlugin = function (plugin, uninstallOptions) { | |||
return this.doOperation(PluginManager.UNINSTALL, plugin, uninstallOptions); | |||
}; |
@@ -0,0 +1,72 @@ | |||
/** | |||
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 EventEmitter = require('events').EventEmitter; | |||
var INSTANCE = new EventEmitter(); | |||
var EVENTS_RECEIVER; | |||
module.exports = INSTANCE; | |||
/** | |||
* Sets up current instance to forward emitted events to another EventEmitter | |||
* instance. | |||
* | |||
* @param {EventEmitter} [eventEmitter] The emitter instance to forward | |||
* events to. Falsy value, when passed, disables forwarding. | |||
*/ | |||
module.exports.forwardEventsTo = function (eventEmitter) { | |||
// If no argument is specified disable events forwarding | |||
if (!eventEmitter) { | |||
EVENTS_RECEIVER = undefined; | |||
return; | |||
} | |||
if (!(eventEmitter instanceof EventEmitter)) | |||
throw new Error('Cordova events can be redirected to another EventEmitter instance only'); | |||
// CB-10940 Skipping forwarding to self to avoid infinite recursion. | |||
// This is the case when the modules are npm-linked. | |||
if (this !== eventEmitter) { | |||
EVENTS_RECEIVER = eventEmitter; | |||
} else { | |||
// Reset forwarding if we are subscribing to self | |||
EVENTS_RECEIVER = undefined; | |||
} | |||
}; | |||
var emit = INSTANCE.emit; | |||
/** | |||
* This method replaces original 'emit' method to allow events forwarding. | |||
* | |||
* @return {eventEmitter} Current instance to allow calls chaining, as | |||
* original 'emit' does | |||
*/ | |||
module.exports.emit = function () { | |||
var args = Array.prototype.slice.call(arguments); | |||
if (EVENTS_RECEIVER) { | |||
EVENTS_RECEIVER.emit.apply(EVENTS_RECEIVER, args); | |||
} | |||
return emit.apply(this, args); | |||
}; |
@@ -0,0 +1,184 @@ | |||
/** | |||
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 child_process = require('child_process'); | |||
var fs = require('fs'); | |||
var path = require('path'); | |||
var _ = require('underscore'); | |||
var Q = require('q'); | |||
var shell = require('shelljs'); | |||
var events = require('./events'); | |||
var iswin32 = process.platform == 'win32'; | |||
// On Windows, spawn() for batch files requires absolute path & having the extension. | |||
function resolveWindowsExe(cmd) { | |||
var winExtensions = ['.exe', '.bat', '.cmd', '.js', '.vbs']; | |||
function isValidExe(c) { | |||
return winExtensions.indexOf(path.extname(c)) !== -1 && fs.existsSync(c); | |||
} | |||
if (isValidExe(cmd)) { | |||
return cmd; | |||
} | |||
cmd = shell.which(cmd) || cmd; | |||
if (!isValidExe(cmd)) { | |||
winExtensions.some(function(ext) { | |||
if (fs.existsSync(cmd + ext)) { | |||
cmd = cmd + ext; | |||
return true; | |||
} | |||
}); | |||
} | |||
return cmd; | |||
} | |||
function maybeQuote(a) { | |||
if (/^[^"].*[ &].*[^"]/.test(a)) return '"' + a + '"'; | |||
return a; | |||
} | |||
/** | |||
* A special implementation for child_process.spawn that handles | |||
* Windows-specific issues with batch files and spaces in paths. Returns a | |||
* promise that succeeds only for return code 0. It is also possible to | |||
* subscribe on spawned process' stdout and stderr streams using progress | |||
* handler for resultant promise. | |||
* | |||
* @example spawn('mycommand', [], {stdio: 'pipe'}) .progress(function (stdio){ | |||
* if (stdio.stderr) { console.error(stdio.stderr); } }) | |||
* .then(function(result){ // do other stuff }) | |||
* | |||
* @param {String} cmd A command to spawn | |||
* @param {String[]} [args=[]] An array of arguments, passed to spawned | |||
* process | |||
* @param {Object} [opts={}] A configuration object | |||
* @param {String|String[]|Object} opts.stdio Property that configures how | |||
* spawned process' stdio will behave. Has the same meaning and possible | |||
* values as 'stdio' options for child_process.spawn method | |||
* (https://nodejs.org/api/child_process.html#child_process_options_stdio). | |||
* @param {Object} [env={}] A map of extra environment variables | |||
* @param {String} [cwd=process.cwd()] Working directory for the command | |||
* @param {Boolean} [chmod=false] If truthy, will attempt to set the execute | |||
* bit before executing on non-Windows platforms | |||
* | |||
* @return {Promise} A promise that is either fulfilled if the spawned | |||
* process is exited with zero error code or rejected otherwise. If the | |||
* 'stdio' option set to 'default' or 'pipe', the promise also emits progress | |||
* messages with the following contents: | |||
* { | |||
* 'stdout': ..., | |||
* 'stderr': ... | |||
* } | |||
*/ | |||
exports.spawn = function(cmd, args, opts) { | |||
args = args || []; | |||
opts = opts || {}; | |||
var spawnOpts = {}; | |||
var d = Q.defer(); | |||
if (iswin32) { | |||
cmd = resolveWindowsExe(cmd); | |||
// If we couldn't find the file, likely we'll end up failing, | |||
// but for things like "del", cmd will do the trick. | |||
if (path.extname(cmd) != '.exe') { | |||
var cmdArgs = '"' + [cmd].concat(args).map(maybeQuote).join(' ') + '"'; | |||
// We need to use /s to ensure that spaces are parsed properly with cmd spawned content | |||
args = [['/s', '/c', cmdArgs].join(' ')]; | |||
cmd = 'cmd'; | |||
spawnOpts.windowsVerbatimArguments = true; | |||
} else if (!fs.existsSync(cmd)) { | |||
// We need to use /s to ensure that spaces are parsed properly with cmd spawned content | |||
args = ['/s', '/c', cmd].concat(args).map(maybeQuote); | |||
} | |||
} | |||
if (opts.stdio !== 'default') { | |||
// Ignore 'default' value for stdio because it corresponds to child_process's default 'pipe' option | |||
spawnOpts.stdio = opts.stdio; | |||
} | |||
if (opts.cwd) { | |||
spawnOpts.cwd = opts.cwd; | |||
} | |||
if (opts.env) { | |||
spawnOpts.env = _.extend(_.extend({}, process.env), opts.env); | |||
} | |||
if (opts.chmod && !iswin32) { | |||
try { | |||
// This fails when module is installed in a system directory (e.g. via sudo npm install) | |||
fs.chmodSync(cmd, '755'); | |||
} catch (e) { | |||
// If the perms weren't set right, then this will come as an error upon execution. | |||
} | |||
} | |||
events.emit(opts.printCommand ? 'log' : 'verbose', 'Running command: ' + maybeQuote(cmd) + ' ' + args.map(maybeQuote).join(' ')); | |||
var child = child_process.spawn(cmd, args, spawnOpts); | |||
var capturedOut = ''; | |||
var capturedErr = ''; | |||
if (child.stdout) { | |||
child.stdout.setEncoding('utf8'); | |||
child.stdout.on('data', function(data) { | |||
capturedOut += data; | |||
d.notify({'stdout': data}); | |||
}); | |||
} | |||
if (child.stderr) { | |||
child.stderr.setEncoding('utf8'); | |||
child.stderr.on('data', function(data) { | |||
capturedErr += data; | |||
d.notify({'stderr': data}); | |||
}); | |||
} | |||
child.on('close', whenDone); | |||
child.on('error', whenDone); | |||
function whenDone(arg) { | |||
child.removeListener('close', whenDone); | |||
child.removeListener('error', whenDone); | |||
var code = typeof arg == 'number' ? arg : arg && arg.code; | |||
events.emit('verbose', 'Command finished with error code ' + code + ': ' + cmd + ' ' + args); | |||
if (code === 0) { | |||
d.resolve(capturedOut.trim()); | |||
} else { | |||
var errMsg = cmd + ': Command failed with exit code ' + code; | |||
if (capturedErr) { | |||
errMsg += ' Error output:\n' + capturedErr.trim(); | |||
} | |||
var err = new Error(errMsg); | |||
err.code = code; | |||
d.reject(err); | |||
} | |||
} | |||
return d.promise; | |||
}; | |||
exports.maybeSpawn = function(cmd, args, opts) { | |||
if (fs.existsSync(cmd)) { | |||
return exports.spawn(cmd, args, opts); | |||
} | |||
return Q(null); | |||
}; | |||
@@ -0,0 +1,32 @@ | |||
/* | |||
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. | |||
*/ | |||
module.exports = function addProperty(module, property, modulePath, obj) { | |||
obj = obj || module.exports; | |||
// Add properties as getter to delay load the modules on first invocation | |||
Object.defineProperty(obj, property, { | |||
configurable: true, | |||
get: function () { | |||
var delayLoadedModule = module.require(modulePath); | |||
obj[property] = delayLoadedModule; | |||
return delayLoadedModule; | |||
} | |||
}); | |||
}; |
@@ -0,0 +1,101 @@ | |||
/* | |||
* | |||
* Copyright 2013 Brett Rudd | |||
* | |||
* 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. | |||
* | |||
*/ | |||
// contains PLIST utility functions | |||
var __ = require('underscore'); | |||
var plist = require('plist'); | |||
// adds node to doc at selector | |||
module.exports.graftPLIST = graftPLIST; | |||
function graftPLIST(doc, xml, selector) { | |||
var obj = plist.parse('<plist>'+xml+'</plist>'); | |||
var node = doc[selector]; | |||
if (node && Array.isArray(node) && Array.isArray(obj)){ | |||
node = node.concat(obj); | |||
for (var i =0;i<node.length; i++){ | |||
for (var j=i+1; j<node.length; ++j) { | |||
if (nodeEqual(node[i], node[j])) | |||
node.splice(j--,1); | |||
} | |||
} | |||
doc[selector] = node; | |||
} else { | |||
//plist uses objects for <dict>. If we have two dicts we merge them instead of | |||
// overriding the old one. See CB-6472 | |||
if (node && __.isObject(node) && __.isObject(obj) && !__.isDate(node) && !__.isDate(obj)){//arrays checked above | |||
__.extend(obj,node); | |||
} | |||
doc[selector] = obj; | |||
} | |||
return true; | |||
} | |||
// removes node from doc at selector | |||
module.exports.prunePLIST = prunePLIST; | |||
function prunePLIST(doc, xml, selector) { | |||
var obj = plist.parse('<plist>'+xml+'</plist>'); | |||
pruneOBJECT(doc, selector, obj); | |||
return true; | |||
} | |||
function pruneOBJECT(doc, selector, fragment) { | |||
if (Array.isArray(fragment) && Array.isArray(doc[selector])) { | |||
var empty = true; | |||
for (var i in fragment) { | |||
for (var j in doc[selector]) { | |||
empty = pruneOBJECT(doc[selector], j, fragment[i]) && empty; | |||
} | |||
} | |||
if (empty) | |||
{ | |||
delete doc[selector]; | |||
return true; | |||
} | |||
} | |||
else if (nodeEqual(doc[selector], fragment)) { | |||
delete doc[selector]; | |||
return true; | |||
} | |||
return false; | |||
} | |||
function nodeEqual(node1, node2) { | |||
if (typeof node1 != typeof node2) | |||
return false; | |||
else if (typeof node1 == 'string') { | |||
node2 = escapeRE(node2).replace(new RegExp('\\$[a-zA-Z0-9-_]+','gm'),'(.*?)'); | |||
return new RegExp('^' + node2 + '$').test(node1); | |||
} | |||
else { | |||
for (var key in node2) { | |||
if (!nodeEqual(node1[key], node2[key])) return false; | |||
} | |||
return true; | |||
} | |||
} | |||
// escape string for use in regex | |||
function escapeRE(str) { | |||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '$&'); | |||
} |
@@ -0,0 +1,289 @@ | |||
/* | |||
* | |||
* 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. | |||
* | |||
*/ | |||
/* jshint sub:true, laxcomma:true */ | |||
/** | |||
* contains XML utility functions, some of which are specific to elementtree | |||
*/ | |||
var fs = require('fs') | |||
, path = require('path') | |||
, _ = require('underscore') | |||
, et = require('elementtree') | |||
; | |||
module.exports = { | |||
// compare two et.XML nodes, see if they match | |||
// compares tagName, text, attributes and children (recursively) | |||
equalNodes: function(one, two) { | |||
if (one.tag != two.tag) { | |||
return false; | |||
} else if (one.text.trim() != two.text.trim()) { | |||
return false; | |||
} else if (one._children.length != two._children.length) { | |||
return false; | |||
} | |||
var oneAttribKeys = Object.keys(one.attrib), | |||
twoAttribKeys = Object.keys(two.attrib), | |||
i = 0, attribName; | |||
if (oneAttribKeys.length != twoAttribKeys.length) { | |||
return false; | |||
} | |||
for (i; i < oneAttribKeys.length; i++) { | |||
attribName = oneAttribKeys[i]; | |||
if (one.attrib[attribName] != two.attrib[attribName]) { | |||
return false; | |||
} | |||
} | |||
for (i; i < one._children.length; i++) { | |||
if (!module.exports.equalNodes(one._children[i], two._children[i])) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
}, | |||
// adds node to doc at selector, creating parent if it doesn't exist | |||
graftXML: function(doc, nodes, selector, after) { | |||
var parent = resolveParent(doc, selector); | |||
if (!parent) { | |||
//Try to create the parent recursively if necessary | |||
try { | |||
var parentToCreate = et.XML('<' + path.basename(selector) + '>'), | |||
parentSelector = path.dirname(selector); | |||
this.graftXML(doc, [parentToCreate], parentSelector); | |||
} catch (e) { | |||
return false; | |||
} | |||
parent = resolveParent(doc, selector); | |||
if (!parent) return false; | |||
} | |||
nodes.forEach(function (node) { | |||
// check if child is unique first | |||
if (uniqueChild(node, parent)) { | |||
var children = parent.getchildren(); | |||
var insertIdx = after ? findInsertIdx(children, after) : children.length; | |||
//TODO: replace with parent.insert after the bug in ElementTree is fixed | |||
parent.getchildren().splice(insertIdx, 0, node); | |||
} | |||
}); | |||
return true; | |||
}, | |||
// removes node from doc at selector | |||
pruneXML: function(doc, nodes, selector) { | |||
var parent = resolveParent(doc, selector); | |||
if (!parent) return false; | |||
nodes.forEach(function (node) { | |||
var matchingKid = null; | |||
if ((matchingKid = findChild(node, parent)) !== null) { | |||
// stupid elementtree takes an index argument it doesn't use | |||
// and does not conform to the python lib | |||
parent.remove(matchingKid); | |||
} | |||
}); | |||
return true; | |||
}, | |||
parseElementtreeSync: function (filename) { | |||
var contents = fs.readFileSync(filename, 'utf-8'); | |||
if(contents) { | |||
//Windows is the BOM. Skip the Byte Order Mark. | |||
contents = contents.substring(contents.indexOf('<')); | |||
} | |||
return new et.ElementTree(et.XML(contents)); | |||
} | |||
}; | |||
function findChild(node, parent) { | |||
var matchingKids = parent.findall(node.tag) | |||
, i, j; | |||
for (i = 0, j = matchingKids.length ; i < j ; i++) { | |||
if (module.exports.equalNodes(node, matchingKids[i])) { | |||
return matchingKids[i]; | |||
} | |||
} | |||
return null; | |||
} | |||
function uniqueChild(node, parent) { | |||
var matchingKids = parent.findall(node.tag) | |||
, i = 0; | |||
if (matchingKids.length === 0) { | |||
return true; | |||
} else { | |||
for (i; i < matchingKids.length; i++) { | |||
if (module.exports.equalNodes(node, matchingKids[i])) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
} | |||
var ROOT = /^\/([^\/]*)/, | |||
ABSOLUTE = /^\/([^\/]*)\/(.*)/; | |||
function resolveParent(doc, selector) { | |||
var parent, tagName, subSelector; | |||
// handle absolute selector (which elementtree doesn't like) | |||
if (ROOT.test(selector)) { | |||
tagName = selector.match(ROOT)[1]; | |||
// test for wildcard "any-tag" root selector | |||
if (tagName == '*' || tagName === doc._root.tag) { | |||
parent = doc._root; | |||
// could be an absolute path, but not selecting the root | |||
if (ABSOLUTE.test(selector)) { | |||
subSelector = selector.match(ABSOLUTE)[2]; | |||
parent = parent.find(subSelector); | |||
} | |||
} else { | |||
return false; | |||
} | |||
} else { | |||
parent = doc.find(selector); | |||
} | |||
return parent; | |||
} | |||
// Find the index at which to insert an entry. After is a ;-separated priority list | |||
// of tags after which the insertion should be made. E.g. If we need to | |||
// insert an element C, and the rule is that the order of children has to be | |||
// As, Bs, Cs. After will be equal to "C;B;A". | |||
function findInsertIdx(children, after) { | |||
var childrenTags = children.map(function(child) { return child.tag; }); | |||
var afters = after.split(';'); | |||
var afterIndexes = afters.map(function(current) { return childrenTags.lastIndexOf(current); }); | |||
var foundIndex = _.find(afterIndexes, function(index) { return index != -1; }); | |||
//add to the beginning if no matching nodes are found | |||
return typeof foundIndex === 'undefined' ? 0 : foundIndex+1; | |||
} | |||
var BLACKLIST = ['platform', 'feature','plugin','engine']; | |||
var SINGLETONS = ['content', 'author', 'name']; | |||
function mergeXml(src, dest, platform, clobber) { | |||
// Do nothing for blacklisted tags. | |||
if (BLACKLIST.indexOf(src.tag) != -1) return; | |||
//Handle attributes | |||
Object.getOwnPropertyNames(src.attrib).forEach(function (attribute) { | |||
if (clobber || !dest.attrib[attribute]) { | |||
dest.attrib[attribute] = src.attrib[attribute]; | |||
} | |||
}); | |||
//Handle text | |||
if (src.text && (clobber || !dest.text)) { | |||
dest.text = src.text; | |||
} | |||
//Handle children | |||
src.getchildren().forEach(mergeChild); | |||
//Handle platform | |||
if (platform) { | |||
src.findall('platform[@name="' + platform + '"]').forEach(function (platformElement) { | |||
platformElement.getchildren().forEach(mergeChild); | |||
}); | |||
} | |||
//Handle duplicate preference tags (by name attribute) | |||
removeDuplicatePreferences(dest); | |||
function mergeChild (srcChild) { | |||
var srcTag = srcChild.tag, | |||
destChild = new et.Element(srcTag), | |||
foundChild, | |||
query = srcTag + '', | |||
shouldMerge = true; | |||
if (BLACKLIST.indexOf(srcTag) === -1) { | |||
if (SINGLETONS.indexOf(srcTag) !== -1) { | |||
foundChild = dest.find(query); | |||
if (foundChild) { | |||
destChild = foundChild; | |||
dest.remove(destChild); | |||
} | |||
} else { | |||
//Check for an exact match and if you find one don't add | |||
Object.getOwnPropertyNames(srcChild.attrib).forEach(function (attribute) { | |||
query += '[@' + attribute + '="' + srcChild.attrib[attribute] + '"]'; | |||
}); | |||
var foundChildren = dest.findall(query); | |||
for(var i = 0; i < foundChildren.length; i++) { | |||
foundChild = foundChildren[i]; | |||
if (foundChild && textMatch(srcChild, foundChild) && (Object.keys(srcChild.attrib).length==Object.keys(foundChild.attrib).length)) { | |||
destChild = foundChild; | |||
dest.remove(destChild); | |||
shouldMerge = false; | |||
break; | |||
} | |||
} | |||
} | |||
mergeXml(srcChild, destChild, platform, clobber && shouldMerge); | |||
dest.append(destChild); | |||
} | |||
} | |||
function removeDuplicatePreferences(xml) { | |||
// reduce preference tags to a hashtable to remove dupes | |||
var prefHash = xml.findall('preference[@name][@value]').reduce(function(previousValue, currentValue) { | |||
previousValue[ currentValue.attrib.name ] = currentValue.attrib.value; | |||
return previousValue; | |||
}, {}); | |||
// remove all preferences | |||
xml.findall('preference[@name][@value]').forEach(function(pref) { | |||
xml.remove(pref); | |||
}); | |||
// write new preferences | |||
Object.keys(prefHash).forEach(function(key, index) { | |||
var element = et.SubElement(xml, 'preference'); | |||
element.set('name', key); | |||
element.set('value', this[key]); | |||
}, prefHash); | |||
} | |||
} | |||
// Expose for testing. | |||
module.exports.mergeXml = mergeXml; | |||
function textMatch(elm1, elm2) { | |||
var text1 = elm1.text ? elm1.text.replace(/\s+/, '') : '', | |||
text2 = elm2.text ? elm2.text.replace(/\s+/, '') : ''; | |||
return (text1 === '' || text1 === text2); | |||
} |
@@ -0,0 +1 @@ | |||
node_modules |
@@ -0,0 +1,7 @@ | |||
language: node_js | |||
sudo: false | |||
node_js: | |||
- "0.10" | |||
install: npm install | |||
script: | |||
- npm test |
@@ -0,0 +1,14 @@ | |||
[![Build Status](https://travis-ci.org/stevengill/cordova-registry-mapper.svg?branch=master)](https://travis-ci.org/stevengill/cordova-registry-mapper) | |||
#Cordova Registry Mapper | |||
This module is used to map Cordova plugin ids to package names and vice versa. | |||
When Cordova users add plugins to their projects using ids | |||
(e.g. `cordova plugin add org.apache.cordova.device`), | |||
this module will map that id to the corresponding package name so `cordova-lib` knows what to fetch from **npm**. | |||
This module was created so the Apache Cordova project could migrate its plugins from | |||
the [Cordova Registry](http://registry.cordova.io/) | |||
to [npm](https://registry.npmjs.com/) | |||
instead of having to maintain a registry. |
@@ -0,0 +1,204 @@ | |||
var map = { | |||
'org.apache.cordova.battery-status':'cordova-plugin-battery-status', | |||
'org.apache.cordova.camera':'cordova-plugin-camera', | |||
'org.apache.cordova.console':'cordova-plugin-console', | |||
'org.apache.cordova.contacts':'cordova-plugin-contacts', | |||
'org.apache.cordova.device':'cordova-plugin-device', | |||
'org.apache.cordova.device-motion':'cordova-plugin-device-motion', | |||
'org.apache.cordova.device-orientation':'cordova-plugin-device-orientation', | |||
'org.apache.cordova.dialogs':'cordova-plugin-dialogs', | |||
'org.apache.cordova.file':'cordova-plugin-file', | |||
'org.apache.cordova.file-transfer':'cordova-plugin-file-transfer', | |||
'org.apache.cordova.geolocation':'cordova-plugin-geolocation', | |||
'org.apache.cordova.globalization':'cordova-plugin-globalization', | |||
'org.apache.cordova.inappbrowser':'cordova-plugin-inappbrowser', | |||
'org.apache.cordova.media':'cordova-plugin-media', | |||
'org.apache.cordova.media-capture':'cordova-plugin-media-capture', | |||
'org.apache.cordova.network-information':'cordova-plugin-network-information', | |||
'org.apache.cordova.splashscreen':'cordova-plugin-splashscreen', | |||
'org.apache.cordova.statusbar':'cordova-plugin-statusbar', | |||
'org.apache.cordova.vibration':'cordova-plugin-vibration', | |||
'org.apache.cordova.test-framework':'cordova-plugin-test-framework', | |||
'com.msopentech.websql' : 'cordova-plugin-websql', | |||
'com.msopentech.indexeddb' : 'cordova-plugin-indexeddb', | |||
'com.microsoft.aad.adal' : 'cordova-plugin-ms-adal', | |||
'com.microsoft.capptain' : 'capptain-cordova', | |||
'com.microsoft.services.aadgraph' : 'cordova-plugin-ms-aad-graph', | |||
'com.microsoft.services.files' : 'cordova-plugin-ms-files', | |||
'om.microsoft.services.outlook' : 'cordova-plugin-ms-outlook', | |||
'com.pbakondy.sim' : 'cordova-plugin-sim', | |||
'android.support.v4' : 'cordova-plugin-android-support-v4', | |||
'android.support.v7-appcompat' : 'cordova-plugin-android-support-v7-appcompat', | |||
'com.google.playservices' : 'cordova-plugin-googleplayservices', | |||
'com.google.cordova.admob' : 'cordova-plugin-admobpro', | |||
'com.rjfun.cordova.extension' : 'cordova-plugin-extension', | |||
'com.rjfun.cordova.plugin.admob' : 'cordova-plugin-admob', | |||
'com.rjfun.cordova.flurryads' : 'cordova-plugin-flurry', | |||
'com.rjfun.cordova.facebookads' : 'cordova-plugin-facebookads', | |||
'com.rjfun.cordova.httpd' : 'cordova-plugin-httpd', | |||
'com.rjfun.cordova.iad' : 'cordova-plugin-iad', | |||
'com.rjfun.cordova.iflyspeech' : 'cordova-plugin-iflyspeech', | |||
'com.rjfun.cordova.lianlianpay' : 'cordova-plugin-lianlianpay', | |||
'com.rjfun.cordova.mobfox' : 'cordova-plugin-mobfox', | |||
'com.rjfun.cordova.mopub' : 'cordova-plugin-mopub', | |||
'com.rjfun.cordova.mmedia' : 'cordova-plugin-mmedia', | |||
'com.rjfun.cordova.nativeaudio' : 'cordova-plugin-nativeaudio', | |||
'com.rjfun.cordova.plugin.paypalmpl' : 'cordova-plugin-paypalmpl', | |||
'com.rjfun.cordova.smartadserver' : 'cordova-plugin-smartadserver', | |||
'com.rjfun.cordova.sms' : 'cordova-plugin-sms', | |||
'com.rjfun.cordova.wifi' : 'cordova-plugin-wifi', | |||
'com.ohh2ahh.plugins.appavailability' : 'cordova-plugin-appavailability', | |||
'org.adapt-it.cordova.fonts' : 'cordova-plugin-fonts', | |||
'de.martinreinhardt.cordova.plugins.barcodeScanner' : 'cordova-plugin-barcodescanner', | |||
'de.martinreinhardt.cordova.plugins.urlhandler' : 'cordova-plugin-urlhandler', | |||
'de.martinreinhardt.cordova.plugins.email' : 'cordova-plugin-email', | |||
'de.martinreinhardt.cordova.plugins.certificates' : 'cordova-plugin-certificates', | |||
'de.martinreinhardt.cordova.plugins.sqlite' : 'cordova-plugin-sqlite', | |||
'fr.smile.cordova.fileopener' : 'cordova-plugin-fileopener', | |||
'org.smile.websqldatabase.initializer' : 'cordova-plugin-websqldatabase-initializer', | |||
'org.smile.websqldatabase.wpdb' : 'cordova-plugin-websqldatabase', | |||
'org.jboss.aerogear.cordova.push' : 'aerogear-cordova-push', | |||
'org.jboss.aerogear.cordova.oauth2' : 'aerogear-cordova-oauth2', | |||
'org.jboss.aerogear.cordova.geo' : 'aerogear-cordova-geo', | |||
'org.jboss.aerogear.cordova.crypto' : 'aerogear-cordova-crypto', | |||
'org.jboss.aerogaer.cordova.otp' : 'aerogear-cordova-otp', | |||
'uk.co.ilee.applewatch' : 'cordova-plugin-apple-watch', | |||
'uk.co.ilee.directions' : 'cordova-plugin-directions', | |||
'uk.co.ilee.gamecenter' : 'cordova-plugin-game-center', | |||
'uk.co.ilee.jailbreakdetection' : 'cordova-plugin-jailbreak-detection', | |||
'uk.co.ilee.nativetransitions' : 'cordova-plugin-native-transitions', | |||
'uk.co.ilee.pedometer' : 'cordova-plugin-pedometer', | |||
'uk.co.ilee.shake' : 'cordova-plugin-shake', | |||
'uk.co.ilee.touchid' : 'cordova-plugin-touchid', | |||
'com.knowledgecode.cordova.websocket' : 'cordova-plugin-websocket', | |||
'com.elixel.plugins.settings' : 'cordova-plugin-settings', | |||
'com.cowbell.cordova.geofence' : 'cordova-plugin-geofence', | |||
'com.blackberry.community.preventsleep' : 'cordova-plugin-preventsleep', | |||
'com.blackberry.community.gamepad' : 'cordova-plugin-gamepad', | |||
'com.blackberry.community.led' : 'cordova-plugin-led', | |||
'com.blackberry.community.thumbnail' : 'cordova-plugin-thumbnail', | |||
'com.blackberry.community.mediakeys' : 'cordova-plugin-mediakeys', | |||
'com.blackberry.community.simplebtlehrplugin' : 'cordova-plugin-bluetoothheartmonitor', | |||
'com.blackberry.community.simplebeaconplugin' : 'cordova-plugin-bluetoothibeacon', | |||
'com.blackberry.community.simplebtsppplugin' : 'cordova-plugin-bluetoothspp', | |||
'com.blackberry.community.clipboard' : 'cordova-plugin-clipboard', | |||
'com.blackberry.community.curl' : 'cordova-plugin-curl', | |||
'com.blackberry.community.qt' : 'cordova-plugin-qtbridge', | |||
'com.blackberry.community.upnp' : 'cordova-plugin-upnp', | |||
'com.blackberry.community.PasswordCrypto' : 'cordova-plugin-password-crypto', | |||
'com.blackberry.community.deviceinfoplugin' : 'cordova-plugin-deviceinfo', | |||
'com.blackberry.community.gsecrypto' : 'cordova-plugin-bb-crypto', | |||
'com.blackberry.community.mongoose' : 'cordova-plugin-mongoose', | |||
'com.blackberry.community.sysdialog' : 'cordova-plugin-bb-sysdialog', | |||
'com.blackberry.community.screendisplay' : 'cordova-plugin-screendisplay', | |||
'com.blackberry.community.messageplugin' : 'cordova-plugin-bb-messageretrieve', | |||
'com.blackberry.community.emailsenderplugin' : 'cordova-plugin-emailsender', | |||
'com.blackberry.community.audiometadata' : 'cordova-plugin-audiometadata', | |||
'com.blackberry.community.deviceemails' : 'cordova-plugin-deviceemails', | |||
'com.blackberry.community.audiorecorder' : 'cordova-plugin-audiorecorder', | |||
'com.blackberry.community.vibration' : 'cordova-plugin-vibrate-intense', | |||
'com.blackberry.community.SMSPlugin' : 'cordova-plugin-bb-sms', | |||
'com.blackberry.community.extractZipFile' : 'cordova-plugin-bb-zip', | |||
'com.blackberry.community.lowlatencyaudio' : 'cordova-plugin-bb-nativeaudio', | |||
'com.blackberry.community.barcodescanner' : 'phonegap-plugin-barcodescanner', | |||
'com.blackberry.app' : 'cordova-plugin-bb-app', | |||
'com.blackberry.bbm.platform' : 'cordova-plugin-bbm', | |||
'com.blackberry.connection' : 'cordova-plugin-bb-connection', | |||
'com.blackberry.identity' : 'cordova-plugin-bb-identity', | |||
'com.blackberry.invoke.card' : 'cordova-plugin-bb-card', | |||
'com.blackberry.invoke' : 'cordova-plugin-bb-invoke', | |||
'com.blackberry.invoked' : 'cordova-plugin-bb-invoked', | |||
'com.blackberry.io.filetransfer' : 'cordova-plugin-bb-filetransfer', | |||
'com.blackberry.io' : 'cordova-plugin-bb-io', | |||
'com.blackberry.notification' : 'cordova-plugin-bb-notification', | |||
'com.blackberry.payment' : 'cordova-plugin-bb-payment', | |||
'com.blackberry.pim.calendar' : 'cordova-plugin-bb-calendar', | |||
'com.blackberry.pim.contacts' : 'cordova-plugin-bb-contacts', | |||
'com.blackberry.pim.lib' : 'cordova-plugin-bb-pimlib', | |||
'com.blackberry.push' : 'cordova-plugin-bb-push', | |||
'com.blackberry.screenshot' : 'cordova-plugin-screenshot', | |||
'com.blackberry.sensors' : 'cordova-plugin-bb-sensors', | |||
'com.blackberry.system' : 'cordova-plugin-bb-system', | |||
'com.blackberry.ui.contextmenu' : 'cordova-plugin-bb-ctxmenu', | |||
'com.blackberry.ui.cover' : 'cordova-plugin-bb-cover', | |||
'com.blackberry.ui.dialog' : 'cordova-plugin-bb-dialog', | |||
'com.blackberry.ui.input' : 'cordova-plugin-touch-keyboard', | |||
'com.blackberry.ui.toast' : 'cordova-plugin-toast', | |||
'com.blackberry.user.identity' : 'cordova-plugin-bb-idservice', | |||
'com.blackberry.utils' : 'cordova-plugin-bb-utils', | |||
'net.yoik.cordova.plugins.screenorientation' : 'cordova-plugin-screen-orientation', | |||
'com.phonegap.plugins.barcodescanner' : 'phonegap-plugin-barcodescanner', | |||
'com.manifoldjs.hostedwebapp' : 'cordova-plugin-hostedwebapp', | |||
'com.initialxy.cordova.themeablebrowser' : 'cordova-plugin-themeablebrowser', | |||
'gr.denton.photosphere' : 'cordova-plugin-panoramaviewer', | |||
'nl.x-services.plugins.actionsheet' : 'cordova-plugin-actionsheet', | |||
'nl.x-services.plugins.socialsharing' : 'cordova-plugin-x-socialsharing', | |||
'nl.x-services.plugins.googleplus' : 'cordova-plugin-googleplus', | |||
'nl.x-services.plugins.insomnia' : 'cordova-plugin-insomnia', | |||
'nl.x-services.plugins.toast' : 'cordova-plugin-x-toast', | |||
'nl.x-services.plugins.calendar' : 'cordova-plugin-calendar', | |||
'nl.x-services.plugins.launchmyapp' : 'cordova-plugin-customurlscheme', | |||
'nl.x-services.plugins.flashlight' : 'cordova-plugin-flashlight', | |||
'nl.x-services.plugins.sslcertificatechecker' : 'cordova-plugin-sslcertificatechecker', | |||
'com.bridge.open' : 'cordova-open', | |||
'com.bridge.safe' : 'cordova-safe', | |||
'com.disusered.open' : 'cordova-open', | |||
'com.disusered.safe' : 'cordova-safe', | |||
'me.apla.cordova.app-preferences' : 'cordova-plugin-app-preferences', | |||
'com.konotor.cordova' : 'cordova-plugin-konotor', | |||
'io.intercom.cordova' : 'cordova-plugin-intercom', | |||
'com.onesignal.plugins.onesignal' : 'onesignal-cordova-plugin', | |||
'com.danjarvis.document-contract': 'cordova-plugin-document-contract', | |||
'com.eface2face.iosrtc' : 'cordova-plugin-iosrtc', | |||
'com.mobileapptracking.matplugin' : 'cordova-plugin-tune', | |||
'com.marianhello.cordova.background-geolocation' : 'cordova-plugin-mauron85-background-geolocation', | |||
'fr.louisbl.cordova.locationservices' : 'cordova-plugin-locationservices', | |||
'fr.louisbl.cordova.gpslocation' : 'cordova-plugin-gpslocation', | |||
'com.hiliaox.weibo' : 'cordova-plugin-weibo', | |||
'com.uxcam.cordova.plugin' : 'cordova-uxcam', | |||
'de.fastr.phonegap.plugins.downloader' : 'cordova-plugin-fastrde-downloader', | |||
'de.fastr.phonegap.plugins.injectView' : 'cordova-plugin-fastrde-injectview', | |||
'de.fastr.phonegap.plugins.CheckGPS' : 'cordova-plugin-fastrde-checkgps', | |||
'de.fastr.phonegap.plugins.md5chksum' : 'cordova-plugin-fastrde-md5', | |||
'io.repro.cordova' : 'cordova-plugin-repro', | |||
're.notifica.cordova': 'cordova-plugin-notificare-push', | |||
'com.megster.cordova.ble': 'cordova-plugin-ble-central', | |||
'com.megster.cordova.bluetoothserial': 'cordova-plugin-bluetooth-serial', | |||
'com.megster.cordova.rfduino': 'cordova-plugin-rfduino', | |||
'cz.velda.cordova.plugin.devicefeedback': 'cordova-plugin-velda-devicefeedback', | |||
'cz.Velda.cordova.plugin.devicefeedback': 'cordova-plugin-velda-devicefeedback', | |||
'org.scriptotek.appinfo': 'cordova-plugin-appinfo', | |||
'com.yezhiming.cordova.appinfo': 'cordova-plugin-appinfo', | |||
'pl.makingwaves.estimotebeacons': 'cordova-plugin-estimote', | |||
'com.evothings.ble': 'cordova-plugin-ble', | |||
'com.appsee.plugin' : 'cordova-plugin-appsee', | |||
'am.armsoft.plugins.listpicker': 'cordova-plugin-listpicker', | |||
'com.pushbots.push': 'pushbots-cordova-plugin', | |||
'com.admob.google': 'cordova-admob', | |||
'admob.ads.google': 'cordova-admob-ads', | |||
'admob.google.plugin': 'admob-google', | |||
'com.admob.admobads': 'admob-ads', | |||
'com.connectivity.monitor': 'cordova-connectivity-monitor', | |||
'com.ios.libgoogleadmobads': 'cordova-libgoogleadmobads', | |||
'com.google.play.services': 'cordova-google-play-services', | |||
'android.support.v13': 'cordova-android-support-v13', | |||
'android.support.v4': 'cordova-android-support-v4', // Duplicated key ;) | |||
'com.analytics.google': 'cordova-plugin-analytics', | |||
'com.analytics.adid.google': 'cordova-plugin-analytics-adid', | |||
'com.chariotsolutions.nfc.plugin': 'phonegap-nfc', | |||
'com.samz.mixpanel': 'cordova-plugin-mixpanel', | |||
'de.appplant.cordova.common.RegisterUserNotificationSettings': 'cordova-plugin-registerusernotificationsettings', | |||
'plugin.google.maps': 'cordova-plugin-googlemaps', | |||
'xu.li.cordova.wechat': 'cordova-plugin-wechat', | |||
'es.keensoft.fullscreenimage': 'cordova-plugin-fullscreenimage', | |||
'com.arcoirislabs.plugin.mqtt' : 'cordova-plugin-mqtt' | |||
}; | |||
module.exports.oldToNew = map; | |||
var reverseMap = {}; | |||
Object.keys(map).forEach(function(elem){ | |||
reverseMap[map[elem]] = elem; | |||
}); | |||
module.exports.newToOld = reverseMap; |
@@ -0,0 +1,86 @@ | |||
{ | |||
"_args": [ | |||
[ | |||
{ | |||
"raw": "cordova-registry-mapper@^1.1.8", | |||
"scope": null, | |||
"escapedName": "cordova-registry-mapper", | |||
"name": "cordova-registry-mapper", | |||
"rawSpec": "^1.1.8", | |||
"spec": ">=1.1.8 <2.0.0", | |||
"type": "range" | |||
}, | |||
"d:\\cordova\\cordova-android\\node_modules\\cordova-common" | |||
] | |||
], | |||
"_from": "cordova-registry-mapper@>=1.1.8 <2.0.0", | |||
"_id": "cordova-registry-mapper@1.1.15", | |||
"_inCache": true, | |||
"_installable": true, | |||
"_location": "/cordova-registry-mapper", | |||
"_nodeVersion": "5.4.1", | |||
"_npmUser": { | |||
"name": "stevegill", | |||
"email": "stevengill97@gmail.com" | |||
}, | |||
"_npmVersion": "3.5.3", | |||
"_phantomChildren": {}, | |||
"_requested": { | |||
"raw": "cordova-registry-mapper@^1.1.8", | |||
"scope": null, | |||
"escapedName": "cordova-registry-mapper", | |||
"name": "cordova-registry-mapper", | |||
"rawSpec": "^1.1.8", | |||
"spec": ">=1.1.8 <2.0.0", | |||
"type": "range" | |||
}, | |||
"_requiredBy": [ | |||
"/cordova-common" | |||
], | |||
"_resolved": "https://registry.npmjs.org/cordova-registry-mapper/-/cordova-registry-mapper-1.1.15.tgz", | |||
"_shasum": "e244b9185b8175473bff6079324905115f83dc7c", | |||
"_shrinkwrap": null, | |||
"_spec": "cordova-registry-mapper@^1.1.8", | |||
"_where": "d:\\cordova\\cordova-android\\node_modules\\cordova-common", | |||
"author": { | |||
"name": "Steve Gill" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/stevengill/cordova-registry-mapper/issues" | |||
}, | |||
"dependencies": {}, | |||
"description": "Maps old plugin ids to new plugin names for fetching from npm", | |||
"devDependencies": { | |||
"tape": "^3.5.0" | |||
}, | |||
"directories": {}, | |||
"dist": { | |||
"shasum": "e244b9185b8175473bff6079324905115f83dc7c", | |||
"tarball": "https://registry.npmjs.org/cordova-registry-mapper/-/cordova-registry-mapper-1.1.15.tgz" | |||
}, | |||
"gitHead": "00af0f028ec94154a364eeabe38b8e22320647bd", | |||
"homepage": "https://github.com/stevengill/cordova-registry-mapper#readme", | |||
"keywords": [ | |||
"cordova", | |||
"plugins" | |||
], | |||
"license": "Apache version 2.0", | |||
"main": "index.js", | |||
"maintainers": [ | |||
{ | |||
"name": "stevegill", | |||
"email": "stevengill97@gmail.com" | |||
} | |||
], | |||
"name": "cordova-registry-mapper", | |||
"optionalDependencies": {}, | |||
"readme": "ERROR: No README data found!", | |||
"repository": { | |||
"type": "git", | |||
"url": "git+https://github.com/stevengill/cordova-registry-mapper.git" | |||
}, | |||
"scripts": { | |||
"test": "node tests/test.js" | |||
}, | |||
"version": "1.1.15" | |||
} |
@@ -0,0 +1,11 @@ | |||
var test = require('tape'); | |||
var oldToNew = require('../index').oldToNew; | |||
var newToOld = require('../index').newToOld; | |||
test('plugin mappings exist', function(t) { | |||
t.plan(2); | |||
t.equal('cordova-plugin-device', oldToNew['org.apache.cordova.device']); | |||
t.equal('org.apache.cordova.device', newToOld['cordova-plugin-device']); | |||
}) |
@@ -0,0 +1 @@ | |||
node_modules |
@@ -0,0 +1,10 @@ | |||
language: node_js | |||
node_js: | |||
- 0.6 | |||
script: make test | |||
notifications: | |||
email: | |||
- tomaz+travisci@tomaz.me |
@@ -0,0 +1,39 @@ | |||
elementtree v0.1.6 (in development) | |||
* Add support for CData elements. (#14) | |||
[hermannpencole] | |||
elementtree v0.1.5 - 2012-11-14 | |||
* Fix a bug in the find() and findtext() method which could manifest itself | |||
under some conditions. | |||
[metagriffin] | |||
elementtree v0.1.4 - 2012-10-15 | |||
* Allow user to use namespaced attributes when using find* functions. | |||
[Andrew Lunny] | |||
elementtree v0.1.3 - 2012-09-21 | |||
* Improve the output of text content in the tags (strip unnecessary line break | |||
characters). | |||
[Darryl Pogue] | |||
elementtree v0.1.2 - 2012-09-04 | |||
* Allow user to pass 'indent' option to ElementTree.write method. If this | |||
option is specified (e.g. {'indent': 4}). XML will be pretty printed. | |||
[Darryl Pogue, Tomaz Muraus] | |||
* Bump sax dependency version. | |||
elementtree v0.1.1 - 2011-09-23 | |||
* Improve special character escaping. | |||
[Ryan Phillips] | |||
elementtree v0.1.0 - 2011-09-05 | |||
* Initial release. |
@@ -0,0 +1,203 @@ | |||
Apache License | |||
Version 2.0, January 2004 | |||
http://www.apache.org/licenses/ | |||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
1. Definitions. | |||
"License" shall mean the terms and conditions for use, reproduction, | |||
and distribution as defined by Sections 1 through 9 of this document. | |||
"Licensor" shall mean the copyright owner or entity authorized by | |||
the copyright owner that is granting the License. | |||
"Legal Entity" shall mean the union of the acting entity and all | |||
other entities that control, are controlled by, or are under common | |||
control with that entity. For the purposes of this definition, | |||
"control" means (i) the power, direct or indirect, to cause the | |||
direction or management of such entity, whether by contract or | |||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
outstanding shares, or (iii) beneficial ownership of such entity. | |||
"You" (or "Your") shall mean an individual or Legal Entity | |||
exercising permissions granted by this License. | |||
"Source" form shall mean the preferred form for making modifications, | |||
including but not limited to software source code, documentation | |||
source, and configuration files. | |||
"Object" form shall mean any form resulting from mechanical | |||
transformation or translation of a Source form, including but | |||
not limited to compiled object code, generated documentation, | |||
and conversions to other media types. | |||
"Work" shall mean the work of authorship, whether in Source or | |||
Object form, made available under the License, as indicated by a | |||
copyright notice that is included in or attached to the work | |||
(an example is provided in the Appendix below). | |||
"Derivative Works" shall mean any work, whether in Source or Object | |||
form, that is based on (or derived from) the Work and for which the | |||
editorial revisions, annotations, elaborations, or other modifications | |||
represent, as a whole, an original work of authorship. For the purposes | |||
of this License, Derivative Works shall not include works that remain | |||
separable from, or merely link (or bind by name) to the interfaces of, | |||
the Work and Derivative Works thereof. | |||
"Contribution" shall mean any work of authorship, including | |||
the original version of the Work and any modifications or additions | |||
to that Work or Derivative Works thereof, that is intentionally | |||
submitted to Licensor for inclusion in the Work by the copyright owner | |||
or by an individual or Legal Entity authorized to submit on behalf of | |||
the copyright owner. For the purposes of this definition, "submitted" | |||
means any form of electronic, verbal, or written communication sent | |||
to the Licensor or its representatives, including but not limited to | |||
communication on electronic mailing lists, source code control systems, | |||
and issue tracking systems that are managed by, or on behalf of, the | |||
Licensor for the purpose of discussing and improving the Work, but | |||
excluding communication that is conspicuously marked or otherwise | |||
designated in writing by the copyright owner as "Not a Contribution." | |||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||
on behalf of whom a Contribution has been received by Licensor and | |||
subsequently incorporated within the Work. | |||
2. Grant of Copyright License. Subject to the terms and conditions of | |||
this License, each Contributor hereby grants to You a perpetual, | |||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
copyright license to reproduce, prepare Derivative Works of, | |||
publicly display, publicly perform, sublicense, and distribute the | |||
Work and such Derivative Works in Source or Object form. | |||
3. Grant of Patent License. Subject to the terms and conditions of | |||
this License, each Contributor hereby grants to You a perpetual, | |||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
(except as stated in this section) patent license to make, have made, | |||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||
where such license applies only to those patent claims licensable | |||
by such Contributor that are necessarily infringed by their | |||
Contribution(s) alone or by combination of their Contribution(s) | |||
with the Work to which such Contribution(s) was submitted. If You | |||
institute patent litigation against any entity (including a | |||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
or a Contribution incorporated within the Work constitutes direct | |||
or contributory patent infringement, then any patent licenses | |||
granted to You under this License for that Work shall terminate | |||
as of the date such litigation is filed. | |||
4. Redistribution. You may reproduce and distribute copies of the | |||
Work or Derivative Works thereof in any medium, with or without | |||
modifications, and in Source or Object form, provided that You | |||
meet the following conditions: | |||
(a) You must give any other recipients of the Work or | |||
Derivative Works a copy of this License; and | |||
(b) You must cause any modified files to carry prominent notices | |||
stating that You changed the files; and | |||
(c) You must retain, in the Source form of any Derivative Works | |||
that You distribute, all copyright, patent, trademark, and | |||
attribution notices from the Source form of the Work, | |||
excluding those notices that do not pertain to any part of | |||
the Derivative Works; and | |||
(d) If the Work includes a "NOTICE" text file as part of its | |||
distribution, then any Derivative Works that You distribute must | |||
include a readable copy of the attribution notices contained | |||
within such NOTICE file, excluding those notices that do not | |||
pertain to any part of the Derivative Works, in at least one | |||
of the following places: within a NOTICE text file distributed | |||
as part of the Derivative Works; within the Source form or | |||
documentation, if provided along with the Derivative Works; or, | |||
within a display generated by the Derivative Works, if and | |||
wherever such third-party notices normally appear. The contents | |||
of the NOTICE file are for informational purposes only and | |||
do not modify the License. You may add Your own attribution | |||
notices within Derivative Works that You distribute, alongside | |||
or as an addendum to the NOTICE text from the Work, provided | |||
that such additional attribution notices cannot be construed | |||
as modifying the License. | |||
You may add Your own copyright statement to Your modifications and | |||
may provide additional or different license terms and conditions | |||
for use, reproduction, or distribution of Your modifications, or | |||
for any such Derivative Works as a whole, provided Your use, | |||
reproduction, and distribution of the Work otherwise complies with | |||
the conditions stated in this License. | |||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||
any Contribution intentionally submitted for inclusion in the Work | |||
by You to the Licensor shall be under the terms and conditions of | |||
this License, without any additional terms or conditions. | |||
Notwithstanding the above, nothing herein shall supersede or modify | |||
the terms of any separate license agreement you may have executed | |||
with Licensor regarding such Contributions. | |||
6. Trademarks. This License does not grant permission to use the trade | |||
names, trademarks, service marks, or product names of the Licensor, | |||
except as required for reasonable and customary use in describing the | |||
origin of the Work and reproducing the content of the NOTICE file. | |||
7. Disclaimer of Warranty. Unless required by applicable law or | |||
agreed to in writing, Licensor provides the Work (and each | |||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
implied, including, without limitation, any warranties or conditions | |||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||
appropriateness of using or redistributing the Work and assume any | |||
risks associated with Your exercise of permissions under this License. | |||
8. Limitation of Liability. In no event and under no legal theory, | |||
whether in tort (including negligence), contract, or otherwise, | |||
unless required by applicable law (such as deliberate and grossly | |||
negligent acts) or agreed to in writing, shall any Contributor be | |||
liable to You for damages, including any direct, indirect, special, | |||
incidental, or consequential damages of any character arising as a | |||
result of this License or out of the use or inability to use the | |||
Work (including but not limited to damages for loss of goodwill, | |||
work stoppage, computer failure or malfunction, or any and all | |||
other commercial damages or losses), even if such Contributor | |||
has been advised of the possibility of such damages. | |||
9. Accepting Warranty or Additional Liability. While redistributing | |||
the Work or Derivative Works thereof, You may choose to offer, | |||
and charge a fee for, acceptance of support, warranty, indemnity, | |||
or other liability obligations and/or rights consistent with this | |||
License. However, in accepting such obligations, You may act only | |||
on Your own behalf and on Your sole responsibility, not on behalf | |||
of any other Contributor, and only if You agree to indemnify, | |||
defend, and hold each Contributor harmless for any liability | |||
incurred by, or claims asserted against, such Contributor by reason | |||
of your accepting any such warranty or additional liability. | |||
END OF TERMS AND CONDITIONS | |||
APPENDIX: How to apply the Apache License to your work. | |||
To apply the Apache License to your work, attach the following | |||
boilerplate notice, with the fields enclosed by brackets "[]" | |||
replaced with your own identifying information. (Don't include | |||
the brackets!) The text should be enclosed in the appropriate | |||
comment syntax for the file format. We also recommend that a | |||
file or class name and description of purpose be included on the | |||
same "printed page" as the copyright notice for easier | |||
identification within third-party archives. | |||
Copyright [yyyy] [name of copyright owner] | |||
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. | |||
@@ -0,0 +1,21 @@ | |||
TESTS := \ | |||
tests/test-simple.js | |||
PATH := ./node_modules/.bin:$(PATH) | |||
WHISKEY := $(shell bash -c 'PATH=$(PATH) type -p whiskey') | |||
default: test | |||
test: | |||
NODE_PATH=`pwd`/lib/ ${WHISKEY} --scope-leaks --sequential --real-time --tests "${TESTS}" | |||
tap: | |||
NODE_PATH=`pwd`/lib/ ${WHISKEY} --test-reporter tap --sequential --real-time --tests "${TESTS}" | |||
coverage: | |||
NODE_PATH=`pwd`/lib/ ${WHISKEY} --sequential --coverage --coverage-reporter html --coverage-dir coverage_html --tests "${TESTS}" | |||
.PHONY: default test coverage tap scope |
@@ -0,0 +1,5 @@ | |||
node-elementtree | |||
Copyright (c) 2011, Rackspace, Inc. | |||
The ElementTree toolkit is Copyright (c) 1999-2007 by Fredrik Lundh | |||
@@ -0,0 +1,141 @@ | |||
node-elementtree | |||
==================== | |||
node-elementtree is a [Node.js](http://nodejs.org) XML parser and serializer based upon the [Python ElementTree v1.3](http://effbot.org/zone/element-index.htm) module. | |||
Installation | |||
==================== | |||
$ npm install elementtree | |||
Using the library | |||
==================== | |||
For the usage refer to the Python ElementTree library documentation - [http://effbot.org/zone/element-index.htm#usage](http://effbot.org/zone/element-index.htm#usage). | |||
Supported XPath expressions in `find`, `findall` and `findtext` methods are listed on [http://effbot.org/zone/element-xpath.htm](http://effbot.org/zone/element-xpath.htm). | |||
Example 1 – Creating An XML Document | |||
==================== | |||
This example shows how to build a valid XML document that can be published to | |||
Atom Hopper. Atom Hopper is used internally as a bridge from products all the | |||
way to collecting revenue, called “Usage.” MaaS and other products send similar | |||
events to it every time user performs an action on a resource | |||
(e.g. creates,updates or deletes). Below is an example of leveraging the API | |||
to create a new XML document. | |||
```javascript | |||
var et = require('elementtree'); | |||
var XML = et.XML; | |||
var ElementTree = et.ElementTree; | |||
var element = et.Element; | |||
var subElement = et.SubElement; | |||
var date, root, tenantId, serviceName, eventType, usageId, dataCenter, region, | |||
checks, resourceId, category, startTime, resourceName, etree, xml; | |||
date = new Date(); | |||
root = element('entry'); | |||
root.set('xmlns', 'http://www.w3.org/2005/Atom'); | |||
tenantId = subElement(root, 'TenantId'); | |||
tenantId.text = '12345'; | |||
serviceName = subElement(root, 'ServiceName'); | |||
serviceName.text = 'MaaS'; | |||
resourceId = subElement(root, 'ResourceID'); | |||
resourceId.text = 'enAAAA'; | |||
usageId = subElement(root, 'UsageID'); | |||
usageId.text = '550e8400-e29b-41d4-a716-446655440000'; | |||
eventType = subElement(root, 'EventType'); | |||
eventType.text = 'create'; | |||
category = subElement(root, 'category'); | |||
category.set('term', 'monitoring.entity.create'); | |||
dataCenter = subElement(root, 'DataCenter'); | |||
dataCenter.text = 'global'; | |||
region = subElement(root, 'Region'); | |||
region.text = 'global'; | |||
startTime = subElement(root, 'StartTime'); | |||
startTime.text = date; | |||
resourceName = subElement(root, 'ResourceName'); | |||
resourceName.text = 'entity'; | |||
etree = new ElementTree(root); | |||
xml = etree.write({'xml_declaration': false}); | |||
console.log(xml); | |||
``` | |||
As you can see, both et.Element and et.SubElement are factory methods which | |||
return a new instance of Element and SubElement class, respectively. | |||
When you create a new element (tag) you can use set method to set an attribute. | |||
To set the tag value, assign a value to the .text attribute. | |||
This example would output a document that looks like this: | |||
```xml | |||
<entry xmlns="http://www.w3.org/2005/Atom"> | |||
<TenantId>12345</TenantId> | |||
<ServiceName>MaaS</ServiceName> | |||
<ResourceID>enAAAA</ResourceID> | |||
<UsageID>550e8400-e29b-41d4-a716-446655440000</UsageID> | |||
<EventType>create</EventType> | |||
<category term="monitoring.entity.create"/> | |||
<DataCenter>global</DataCenter> | |||
<Region>global</Region> | |||
<StartTime>Sun Apr 29 2012 16:37:32 GMT-0700 (PDT)</StartTime> | |||
<ResourceName>entity</ResourceName> | |||
</entry> | |||
``` | |||
Example 2 – Parsing An XML Document | |||
==================== | |||
This example shows how to parse an XML document and use simple XPath selectors. | |||
For demonstration purposes, we will use the XML document located at | |||
https://gist.github.com/2554343. | |||
Behind the scenes, node-elementtree uses Isaac’s sax library for parsing XML, | |||
but the library has a concept of “parsers,” which means it’s pretty simple to | |||
add support for a different parser. | |||
```javascript | |||
var fs = require('fs'); | |||
var et = require('elementtree'); | |||
var XML = et.XML; | |||
var ElementTree = et.ElementTree; | |||
var element = et.Element; | |||
var subElement = et.SubElement; | |||
var data, etree; | |||
data = fs.readFileSync('document.xml').toString(); | |||
etree = et.parse(data); | |||
console.log(etree.findall('./entry/TenantId').length); // 2 | |||
console.log(etree.findtext('./entry/ServiceName')); // MaaS | |||
console.log(etree.findall('./entry/category')[0].get('term')); // monitoring.entity.create | |||
console.log(etree.findall('*/category/[@term="monitoring.entity.update"]').length); // 1 | |||
``` | |||
Build status | |||
==================== | |||
[![Build Status](https://secure.travis-ci.org/racker/node-elementtree.png)](http://travis-ci.org/racker/node-elementtree) | |||
License | |||
==================== | |||
node-elementtree is distributed under the [Apache license](http://www.apache.org/licenses/LICENSE-2.0.html). |