[Pkg-javascript-commits] [node-utf8] 01/06: Imported Upstream version 2.0.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Fri Apr 3 14:12:35 UTC 2015


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository node-utf8.

commit 172cf1295aae986c92556c2cfc9c3bb5344638be
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Mon Mar 30 00:16:21 2015 +0200

    Imported Upstream version 2.0.0
---
 .gitattributes                |    2 +
 .gitignore                    |   20 +
 .travis.yml                   |   19 +
 Gruntfile.js                  |   72 +++
 LICENSE-GPL.txt               |  278 +++++++++++
 LICENSE-MIT.txt               |   20 +
 README.md                     |  119 +++++
 bower.json                    |   14 +
 component.json                |   16 +
 coverage/index.html           |  333 +++++++++++++
 coverage/prettify.css         |    1 +
 coverage/prettify.js          |    1 +
 coverage/utf8.js/index.html   |  333 +++++++++++++
 coverage/utf8.js/utf8.js.html | 1028 +++++++++++++++++++++++++++++++++++++++++
 package.json                  |   49 ++
 tests/generate-test-data.py   |   47 ++
 tests/index.html              |   35 ++
 tests/tests.js                |  242 ++++++++++
 utf8.js                       |  239 ++++++++++
 19 files changed, 2868 insertions(+)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0a91f75
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Automatically normalize line endings for all text-based files
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c4b384e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+# Generated test data file (> 100 MB)
+tests/data.json
+
+# JSON version of coverage report
+coverage/coverage.json
+
+# Installed npm modules
+node_modules
+
+# Folder view configuration files
+.DS_Store
+Desktop.ini
+
+# Thumbnail cache files
+._*
+Thumbs.db
+
+# Files that might appear on external disks
+.Spotlight-V100
+.Trashes
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..58fe013
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.8"
+before_script:
+  - "npm install -g grunt-cli"
+  # Narwhal uses a hardcoded path to openjdk v6, so use that version
+  - "sudo apt-get update -qq"
+  - "sudo apt-get install -qq openjdk-6-jre"
+  - "PACKAGE=rhino1_7R3; wget http://ftp.mozilla.org/pub/mozilla.org/js/$PACKAGE.zip && sudo unzip $PACKAGE -d /opt/ && rm $PACKAGE.zip"
+  - "PACKAGE=rhino1_7R3; echo -e '#!/bin/sh\\njava -jar /opt/'$PACKAGE'/js.jar $@' | sudo tee /usr/local/bin/rhino && sudo chmod +x /usr/local/bin/rhino"
+  - "PACKAGE=ringojs-0.9; wget http://ringojs.org/downloads/$PACKAGE.zip && sudo unzip $PACKAGE -d /opt/ && rm $PACKAGE.zip"
+  - "PACKAGE=ringojs-0.9; sudo ln -s /opt/$PACKAGE/bin/ringo /usr/local/bin/ringo && sudo chmod +x /usr/local/bin/ringo"
+  - "PACKAGE=v0.3.2; wget https://github.com/280north/narwhal/archive/$PACKAGE.zip && sudo unzip $PACKAGE -d /opt/ && rm $PACKAGE.zip"
+  - "PACKAGE=narwhal-0.3.2; sudo ln -s /opt/$PACKAGE/bin/narwhal /usr/local/bin/narwhal && sudo chmod +x /usr/local/bin/narwhal"
+  # If the enviroment stores rt.jar in a different directory, find it and symlink the directory
+  - "PREFIX=/usr/lib/jvm; if [ ! -d $PREFIX/java-6-openjdk ]; then for d in $PREFIX/java-6-openjdk-*; do if [ -e $d/jre/lib/rt.jar ]; then sudo ln -s $d $PREFIX/java-6-openjdk; break; fi; done; fi"
+script:
+  "grunt ci"
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..6d5a6d1
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,72 @@
+module.exports = function(grunt) {
+
+	grunt.initConfig({
+		'shell': {
+			'options': {
+				'stdout': true,
+				'stderr': true,
+				'failOnError': true
+			},
+			'generate-test-data': { // Only when needed
+				'command': 'if [ ! -f data.json ]; then echo python generate-test-data.py; fi',
+				'options': {
+					'execOptions': {
+						'cwd': 'tests'
+					}
+				}
+			},
+			'cover': {
+				'command': 'istanbul cover --report "html" --verbose --dir "coverage" "tests/tests.js"; istanbul report --root "coverage" --format "html"'
+			},
+			'test-narwhal': {
+				'command': 'echo "Testing in Narwhal..."; export NARWHAL_OPTIMIZATION=-1; narwhal "tests/tests.js"'
+			},
+			'test-phantomjs': {
+				'command': 'echo "Testing in PhantomJS..."; phantomjs "tests/tests.js"'
+			},
+			// Rhino 1.7R4 has a bug that makes it impossible to test in.
+			// https://bugzilla.mozilla.org/show_bug.cgi?id=775566
+			// To test, use Rhino 1.7R3, or wait (heh) for the 1.7R5 release.
+			'test-rhino': {
+				'command': 'echo "Testing in Rhino..."; rhino -opt -1 "tests.js"',
+				'options': {
+					'execOptions': {
+						'cwd': 'tests'
+					}
+				}
+			},
+			'test-ringo': {
+				'command': 'echo "Testing in Ringo..."; ringo -o -1 "tests/tests.js"'
+			},
+			'test-node': {
+				'command': 'echo "Testing in Node..."; node "tests/tests.js" --extended'
+			},
+			'test-browser': {
+				'command': 'echo "Testing in a browser..."; open "tests/index.html"'
+			}
+		}
+	});
+
+	grunt.loadNpmTasks('grunt-shell');
+
+	grunt.registerTask('cover', 'shell:cover');
+	grunt.registerTask('ci', [
+		'shell:generate-test-data',
+		'shell:test-narwhal',
+		'shell:test-phantomjs',
+		'shell:test-rhino',
+		'shell:test-ringo',
+		'shell:test-node',
+	]);
+	grunt.registerTask('test', [
+		'shell:generate-test-data',
+		'ci',
+		'shell:test-browser'
+	]);
+
+	grunt.registerTask('default', [
+		'shell:test-node',
+		'cover'
+	]);
+
+};
diff --git a/LICENSE-GPL.txt b/LICENSE-GPL.txt
new file mode 100644
index 0000000..11dddd0
--- /dev/null
+++ b/LICENSE-GPL.txt
@@ -0,0 +1,278 @@
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
diff --git a/LICENSE-MIT.txt b/LICENSE-MIT.txt
new file mode 100644
index 0000000..97067e5
--- /dev/null
+++ b/LICENSE-MIT.txt
@@ -0,0 +1,20 @@
+Copyright Mathias Bynens <http://mathiasbynens.be/>
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2362b66
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+# utf8.js [![Build status](https://travis-ci.org/mathiasbynens/utf8.js.png?branch=master)](https://travis-ci.org/mathiasbynens/utf8.js) [![Dependency status](https://gemnasium.com/mathiasbynens/utf8.js.png)](https://gemnasium.com/mathiasbynens/utf8.js)
+
+_utf8.js_ is a well-tested UTF-8 encoder/decoder written in JavaScript. Unlike many other JavaScript solutions, it is designed to be a _proper_ UTF-8 encoder/decoder: it can encode/decode any given Unicode code point, including astral symbols and unpaired surrogates.
+
+Feel free to fork if you see possible improvements!
+
+## Installation
+
+Via [npm](http://npmjs.org/):
+
+```bash
+npm install utf8
+```
+
+Via [Bower](http://bower.io/):
+
+```bash
+bower install utf8
+```
+
+Via [Component](https://github.com/component/component):
+
+```bash
+component install mathiasbynens/utf8.js
+```
+
+In a browser:
+
+```html
+<script src="utf8.js"></script>
+```
+
+In [Narwhal](http://narwhaljs.org/), [Node.js](http://nodejs.org/), and [RingoJS ≥ v0.8.0](http://ringojs.org/):
+
+```js
+var utf8 = require('utf8');
+```
+
+In [Rhino](http://www.mozilla.org/rhino/):
+
+```js
+load('utf8.js');
+```
+
+Using an AMD loader like [RequireJS](http://requirejs.org/):
+
+```js
+require(
+  {
+    'paths': {
+      'utf8': 'path/to/utf8'
+    }
+  },
+  ['utf8'],
+  function(utf8) {
+    console.log(utf8);
+  }
+);
+```
+
+## API
+
+### `utf8.encode(string)`
+
+Encodes any given JavaScript string (`string`) as UTF-8, and returns the UTF-8-encoded version of the string.
+
+```js
+// U+00A9 COPYRIGHT SIGN; see http://codepoints.net/U+00A9
+utf8.encode('\xA9');
+// → '\xC2\xA9'
+// U+10001 LINEAR B SYLLABLE B038 E; see http://codepoints.net/U+10001
+utf8.encode('\uD800\uDC01');
+// → '\xF0\x90\x80\x81'
+```
+
+### `utf8.decode(byteString)`
+
+Encodes any given UTF-8-encoded string (`byteString`) as UTF-8, and returns the UTF-8-decoded version of the string. It throws an error when malformed UTF-8 is detected.
+
+```js
+utf8.decode('\xC2\xA9');
+// → '\xA9'
+
+utf8.decode('\xF0\x90\x80\x81');
+// → '\uD800\uDC01'
+// → U+10001 LINEAR B SYLLABLE B038 E
+```
+
+### `utf8.version`
+
+A string representing the semantic version number.
+
+## Support
+
+utf8.js has been tested in at least Chrome 27-29, Firefox 3-22, Safari 4-6, Opera 10-12, IE 6-10, Node.js v0.10.0, Narwhal 0.3.2, RingoJS 0.8-0.9, PhantomJS 1.9.0, and Rhino 1.7RC4.
+
+## Unit tests & code coverage
+
+After cloning this repository, run `npm install` to install the dependencies needed for development and testing. You may want to install Istanbul _globally_ using `npm install istanbul -g`.
+
+Once that’s done, you can run the unit tests in Node using `npm test` or `node tests/tests.js`. To run the tests in Rhino, Ringo, Narwhal, PhantomJS, and web browsers as well, use `grunt test`.
+
+To generate [the code coverage report](http://rawgithub.com/mathiasbynens/utf8.js/master/coverage/utf8.js/utf8.js.html), use `grunt cover`.
+
+## FAQ
+
+### Why is the first release named v2.0.0? Haven’t you heard of [semantic versioning](http://semver.org/)?
+
+Long before utf8.js was created, the `utf8` module on npm was registered and used by another (slightly buggy) library. @ryanmcgrath was kind enough to give me access to the `utf8` package on npm when I told him about utf8.js. Since there has already been a v1.0.0 release of the old library, and to avoid breaking backwards compatibility with projects that rely on the `utf8` npm package, I decided the tag the first release of utf8.js as v2.0.0 and take it from there.
+
+## Author
+
+| [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](http://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|
+| [Mathias Bynens](http://mathiasbynens.be/) |
+
+## License
+
+utf8.js is dual licensed under the [MIT](http://mths.be/mit) and [GPL](http://mths.be/gpl) licenses.
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..fe8fa72
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,14 @@
+{
+	"name": "utf8",
+	"version": "2.0.0",
+	"main": "utf8.js",
+	"ignore": [
+		"coverage",
+		"tests",
+		".*",
+		"component.json",
+		"Gruntfile.js",
+		"node_modules",
+		"package.json"
+	]
+}
diff --git a/component.json b/component.json
new file mode 100644
index 0000000..5165d4f
--- /dev/null
+++ b/component.json
@@ -0,0 +1,16 @@
+{
+	"name": "utf8",
+	"version": "2.0.0",
+	"description": "A well-tested UTF-8 encoder/decoder written in JavaScript.",
+	"repo": "mathiasbynens/utf8.js",
+	"license": "MIT/GPL",
+	"scripts": [
+		"utf8.js"
+	],
+	"keywords": [
+		"charset",
+		"encoding",
+		"unicode",
+		"utf8"
+	]
+}
diff --git a/coverage/index.html b/coverage/index.html
new file mode 100644
index 0000000..68f3574
--- /dev/null
+++ b/coverage/index.html
@@ -0,0 +1,333 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <title>Code coverage report for All files</title>
+    <meta charset="utf-8">
+
+    <link rel="stylesheet" href="prettify.css">
+
+    <style>
+        body, html {
+            margin:0; padding: 0;
+        }
+        body {
+            font-family: Helvetica Neue, Helvetica,Arial;
+            font-size: 10pt;
+        }
+        div.header, div.footer {
+            background: #eee;
+            padding: 1em;
+        }
+        div.header {
+            z-index: 100;
+            position: fixed;
+            top: 0;
+            border-bottom: 1px solid #666;
+            width: 100%;
+        }
+        div.footer {
+            border-top: 1px solid #666;
+        }
+        div.body {
+            margin-top: 10em;
+        }
+        div.meta {
+            font-size: 90%;
+            text-align: center;
+        }
+        h1, h2, h3 {
+            font-weight: normal;
+        }
+        h1 {
+            font-size: 12pt;
+        }
+        h2 {
+            font-size: 10pt;
+        }
+        pre {
+            font-family: Consolas, Menlo, Monaco, monospace;
+            margin: 0;
+            padding: 0;
+            line-height: 14px;
+            font-size: 14px;
+            -moz-tab-size: 2;
+            -o-tab-size:  2;
+            tab-size: 2;
+        }
+
+        div.path { font-size: 110%; }
+        div.path a:link, div.path a:visited { color: #000; }
+        table.coverage { border-collapse: collapse; margin:0; padding: 0 }
+
+        table.coverage td {
+            margin: 0;
+            padding: 0;
+            color: #111;
+            vertical-align: top;
+        }
+        table.coverage td.line-count {
+            width: 50px;
+            text-align: right;
+            padding-right: 5px;
+        }
+        table.coverage td.line-coverage {
+            color: #777 !important;
+            text-align: right;
+            border-left: 1px solid #666;
+            border-right: 1px solid #666;
+        }
+
+        table.coverage td.text {
+        }
+
+        table.coverage td span.cline-any {
+            display: inline-block;
+            padding: 0 5px;
+            width: 40px;
+        }
+        table.coverage td span.cline-neutral {
+            background: #eee;
+        }
+        table.coverage td span.cline-yes {
+            background: #b5d592;
+            color: #999;
+        }
+        table.coverage td span.cline-no {
+            background: #fc8c84;
+        }
+
+        .cstat-yes { color: #111; }
+        .cstat-no { background: #fc8c84; color: #111; }
+        .fstat-no { background: #ffc520; color: #111 !important; }
+        .cbranch-no { background:  yellow !important; color: #111; }
+        .missing-if-branch {
+            display: inline-block;
+            margin-right: 10px;
+            position: relative;
+            padding: 0 4px;
+            background: black;
+            color: yellow;
+            xtext-decoration: line-through;
+        }
+        .missing-if-branch .typ {
+            color: inherit !important;
+        }
+
+        .entity, .metric { font-weight: bold; }
+        .metric { display: inline-block; border: 1px solid #333; padding: 0.3em; background: white; }
+        .metric small { font-size: 80%; font-weight: normal; color: #666; }
+
+        div.coverage-summary table { border-collapse: collapse; margin: 3em; font-size: 110%; }
+        div.coverage-summary td, div.coverage-summary table  th { margin: 0; padding: 0.25em 1em; border-top: 1px solid #666; border-bottom: 1px solid #666; }
+        div.coverage-summary th { text-align: left; border: 1px solid #666; background: #eee; font-weight: normal; }
+        div.coverage-summary th.file { border-right: none !important; }
+        div.coverage-summary th.pic { border-left: none !important; text-align: right; }
+        div.coverage-summary th.pct { border-right: none !important; }
+        div.coverage-summary th.abs { border-left: none !important; text-align: right; }
+        div.coverage-summary td.pct { text-align: right; border-left: 1px solid #666; }
+        div.coverage-summary td.abs { text-align: right; font-size: 90%; color: #444; border-right: 1px solid #666; }
+        div.coverage-summary td.file { text-align: right; border-left: 1px solid #666; white-space: nowrap;  }
+        div.coverage-summary td.pic { min-width: 120px !important;  }
+        div.coverage-summary a:link { text-decoration: none; color: #000; }
+        div.coverage-summary a:visited { text-decoration: none; color: #333; }
+        div.coverage-summary a:hover { text-decoration: underline; }
+        div.coverage-summary tfoot td { border-top: 1px solid #666; }
+
+        div.coverage-summary .yui3-datatable-sort-indicator, div.coverage-summary .dummy-sort-indicator {
+            height: 10px;
+            width: 7px;
+            display: inline-block;
+            margin-left: 0.5em;
+        }
+        div.coverage-summary .yui3-datatable-sort-indicator {
+            background: url("http://yui.yahooapis.com/3.6.0/build/datatable-sort/assets/skins/sam/sort-arrow-sprite.png") no-repeat scroll 0 0 transparent;
+        }
+        div.coverage-summary .yui3-datatable-sorted .yui3-datatable-sort-indicator {
+            background-position: 0 -20px;
+        }
+        div.coverage-summary .yui3-datatable-sorted-desc .yui3-datatable-sort-indicator {
+            background-position: 0 -10px;
+        }
+
+        .high { background: #b5d592 !important; }
+        .medium { background: #ffe87c !important; }
+        .low { background: #fc8c84 !important; }
+
+        span.cover-fill, span.cover-empty {
+            display:inline-block;
+            border:1px solid #444;
+            background: white;
+            height: 12px;
+        }
+        span.cover-fill {
+            background: #ccc;
+            border-right: 1px solid #444;
+        }
+        span.cover-empty {
+            background: white;
+            border-left: none;
+        }
+        span.cover-full {
+            border-right: none !important;
+        }
+        pre.prettyprint {
+            border: none !important;
+            padding: 0 !important;
+            margin: 0 !important;
+        }
+        .com { color: #999 !important; }
+    </style>
+</head>
+<body>
+<div class="header high">
+    <h1>Code coverage report for <span class="entity">All files</span></h1>
+    <h2>
+        
+        Statements: <span class="metric">92.31% <small>(120 / 130)</small></span>     
+        
+        
+        Branches: <span class="metric">78.79% <small>(52 / 66)</small></span>     
+        
+        
+        Functions: <span class="metric">90% <small>(9 / 10)</small></span>     
+        
+        
+        Lines: <span class="metric">92.31% <small>(120 / 130)</small></span>     
+        
+    </h2>
+    <div class="path"></div>
+</div>
+<div class="body">
+<div class="coverage-summary">
+<table>
+<thead>
+<tr>
+   <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
+   <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
+   <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
+   <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
+   <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
+   <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
+   <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
+</tr>
+</thead>
+<tbody><tr>
+	<td class="file high" data-value="utf8.js/"><a href="utf8.js/index.html">utf8.js/</a></td>
+	<td data-value="92.31" class="pic high"><span class="cover-fill" style="width: 92px;"></span><span class="cover-empty" style="width:8px;"></span></td>
+	<td data-value="92.31" class="pct high">92.31%</td>
+	<td data-value="130" class="abs high">(120 / 130)</td>
+	<td data-value="78.79" class="pct medium">78.79%</td>
+	<td data-value="66" class="abs medium">(52 / 66)</td>
+	<td data-value="90" class="pct high">90%</td>
+	<td data-value="10" class="abs high">(9 / 10)</td>
+	<td data-value="92.31" class="pct high">92.31%</td>
+	<td data-value="130" class="abs high">(120 / 130)</td>
+	</tr>
+
+</tbody>
+</table>
+</div>
+</div>
+<div class="footer">
+    <div class="meta">Generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Thu Jun 20 2013 14:00:05 GMT+0200 (CEST)</div>
+</div>
+
+<script src="prettify.js"></script>
+
+<script src="http://yui.yahooapis.com/3.6.0/build/yui/yui-min.js"></script>
+<script>
+
+    YUI().use('datatable', function (Y) {
+
+        var formatters = {
+          pct: function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              try {
+                  return o.value.toFixed(2) + '%';
+              } catch (ex) { return o.value + '%'; }
+          },
+          html: function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              return o.record.get(o.column.key + '_html');
+          }
+        },
+          defaultFormatter = function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              return o.value;
+          };
+
+        function getColumns(theadNode) {
+            var colNodes = theadNode.all('tr th'),
+                cols = [],
+                col;
+            colNodes.each(function (colNode) {
+                col = {
+                    key: colNode.getAttribute('data-col'),
+                    label: colNode.get('innerHTML') || ' ',
+                    sortable: !colNode.getAttribute('data-nosort'),
+                    className: colNode.getAttribute('class'),
+                    type: colNode.getAttribute('data-type'),
+                    allowHTML: colNode.getAttribute('data-html') === 'true' || colNode.getAttribute('data-fmt') === 'html'
+                };
+                col.formatter = formatters[colNode.getAttribute('data-fmt')] || defaultFormatter;
+                cols.push(col);
+            });
+            return cols;
+        }
+
+        function getRowData(trNode, cols) {
+            var tdNodes = trNode.all('td'),
+                    i,
+                    row = { classes: {} },
+                    node,
+                    name;
+            for (i = 0; i < cols.length; i += 1) {
+                name = cols[i].key;
+                node = tdNodes.item(i);
+                row[name] = node.getAttribute('data-value') || node.get('innerHTML');
+                row[name + '_html'] = node.get('innerHTML');
+                row.classes[name] = node.getAttribute('class');
+                //Y.log('Name: ' + name + '; Value: ' + row[name]);
+                if (cols[i].type === 'number') { row[name] = row[name] * 1; }
+            }
+            //Y.log(row);
+            return row;
+        }
+
+        function getData(tbodyNode, cols) {
+            var data = [];
+            tbodyNode.all('tr').each(function (trNode) {
+                data.push(getRowData(trNode, cols));
+            });
+            return data;
+        }
+
+        function replaceTable(node) {
+            if (!node) { return; }
+            var cols = getColumns(node.one('thead')),
+                data = getData(node.one('tbody'), cols),
+                table,
+                parent = node.get('parentNode');
+
+            table = new Y.DataTable({
+                columns: cols,
+                data: data,
+                sortBy: 'file'
+            });
+            parent.set('innerHTML', '');
+            table.render(parent);
+        }
+
+        Y.on('domready', function () {
+            replaceTable(Y.one('div.coverage-summary table'));
+            if (typeof prettyPrint === 'function') {
+                prettyPrint();
+            }
+        });
+    });
+</script>
+</body>
+</html>
diff --git a/coverage/prettify.css b/coverage/prettify.css
new file mode 100644
index 0000000..b317a7c
--- /dev/null
+++ b/coverage/prettify.css
@@ -0,0 +1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding [...]
diff --git a/coverage/prettify.js b/coverage/prettify.js
new file mode 100644
index 0000000..ef51e03
--- /dev/null
+++ b/coverage/prettify.js
@@ -0,0 +1 @@
+window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,expl [...]
diff --git a/coverage/utf8.js/index.html b/coverage/utf8.js/index.html
new file mode 100644
index 0000000..926b3f6
--- /dev/null
+++ b/coverage/utf8.js/index.html
@@ -0,0 +1,333 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <title>Code coverage report for utf8.js/</title>
+    <meta charset="utf-8">
+
+    <link rel="stylesheet" href="../prettify.css">
+
+    <style>
+        body, html {
+            margin:0; padding: 0;
+        }
+        body {
+            font-family: Helvetica Neue, Helvetica,Arial;
+            font-size: 10pt;
+        }
+        div.header, div.footer {
+            background: #eee;
+            padding: 1em;
+        }
+        div.header {
+            z-index: 100;
+            position: fixed;
+            top: 0;
+            border-bottom: 1px solid #666;
+            width: 100%;
+        }
+        div.footer {
+            border-top: 1px solid #666;
+        }
+        div.body {
+            margin-top: 10em;
+        }
+        div.meta {
+            font-size: 90%;
+            text-align: center;
+        }
+        h1, h2, h3 {
+            font-weight: normal;
+        }
+        h1 {
+            font-size: 12pt;
+        }
+        h2 {
+            font-size: 10pt;
+        }
+        pre {
+            font-family: Consolas, Menlo, Monaco, monospace;
+            margin: 0;
+            padding: 0;
+            line-height: 14px;
+            font-size: 14px;
+            -moz-tab-size: 2;
+            -o-tab-size:  2;
+            tab-size: 2;
+        }
+
+        div.path { font-size: 110%; }
+        div.path a:link, div.path a:visited { color: #000; }
+        table.coverage { border-collapse: collapse; margin:0; padding: 0 }
+
+        table.coverage td {
+            margin: 0;
+            padding: 0;
+            color: #111;
+            vertical-align: top;
+        }
+        table.coverage td.line-count {
+            width: 50px;
+            text-align: right;
+            padding-right: 5px;
+        }
+        table.coverage td.line-coverage {
+            color: #777 !important;
+            text-align: right;
+            border-left: 1px solid #666;
+            border-right: 1px solid #666;
+        }
+
+        table.coverage td.text {
+        }
+
+        table.coverage td span.cline-any {
+            display: inline-block;
+            padding: 0 5px;
+            width: 40px;
+        }
+        table.coverage td span.cline-neutral {
+            background: #eee;
+        }
+        table.coverage td span.cline-yes {
+            background: #b5d592;
+            color: #999;
+        }
+        table.coverage td span.cline-no {
+            background: #fc8c84;
+        }
+
+        .cstat-yes { color: #111; }
+        .cstat-no { background: #fc8c84; color: #111; }
+        .fstat-no { background: #ffc520; color: #111 !important; }
+        .cbranch-no { background:  yellow !important; color: #111; }
+        .missing-if-branch {
+            display: inline-block;
+            margin-right: 10px;
+            position: relative;
+            padding: 0 4px;
+            background: black;
+            color: yellow;
+            xtext-decoration: line-through;
+        }
+        .missing-if-branch .typ {
+            color: inherit !important;
+        }
+
+        .entity, .metric { font-weight: bold; }
+        .metric { display: inline-block; border: 1px solid #333; padding: 0.3em; background: white; }
+        .metric small { font-size: 80%; font-weight: normal; color: #666; }
+
+        div.coverage-summary table { border-collapse: collapse; margin: 3em; font-size: 110%; }
+        div.coverage-summary td, div.coverage-summary table  th { margin: 0; padding: 0.25em 1em; border-top: 1px solid #666; border-bottom: 1px solid #666; }
+        div.coverage-summary th { text-align: left; border: 1px solid #666; background: #eee; font-weight: normal; }
+        div.coverage-summary th.file { border-right: none !important; }
+        div.coverage-summary th.pic { border-left: none !important; text-align: right; }
+        div.coverage-summary th.pct { border-right: none !important; }
+        div.coverage-summary th.abs { border-left: none !important; text-align: right; }
+        div.coverage-summary td.pct { text-align: right; border-left: 1px solid #666; }
+        div.coverage-summary td.abs { text-align: right; font-size: 90%; color: #444; border-right: 1px solid #666; }
+        div.coverage-summary td.file { text-align: right; border-left: 1px solid #666; white-space: nowrap;  }
+        div.coverage-summary td.pic { min-width: 120px !important;  }
+        div.coverage-summary a:link { text-decoration: none; color: #000; }
+        div.coverage-summary a:visited { text-decoration: none; color: #333; }
+        div.coverage-summary a:hover { text-decoration: underline; }
+        div.coverage-summary tfoot td { border-top: 1px solid #666; }
+
+        div.coverage-summary .yui3-datatable-sort-indicator, div.coverage-summary .dummy-sort-indicator {
+            height: 10px;
+            width: 7px;
+            display: inline-block;
+            margin-left: 0.5em;
+        }
+        div.coverage-summary .yui3-datatable-sort-indicator {
+            background: url("http://yui.yahooapis.com/3.6.0/build/datatable-sort/assets/skins/sam/sort-arrow-sprite.png") no-repeat scroll 0 0 transparent;
+        }
+        div.coverage-summary .yui3-datatable-sorted .yui3-datatable-sort-indicator {
+            background-position: 0 -20px;
+        }
+        div.coverage-summary .yui3-datatable-sorted-desc .yui3-datatable-sort-indicator {
+            background-position: 0 -10px;
+        }
+
+        .high { background: #b5d592 !important; }
+        .medium { background: #ffe87c !important; }
+        .low { background: #fc8c84 !important; }
+
+        span.cover-fill, span.cover-empty {
+            display:inline-block;
+            border:1px solid #444;
+            background: white;
+            height: 12px;
+        }
+        span.cover-fill {
+            background: #ccc;
+            border-right: 1px solid #444;
+        }
+        span.cover-empty {
+            background: white;
+            border-left: none;
+        }
+        span.cover-full {
+            border-right: none !important;
+        }
+        pre.prettyprint {
+            border: none !important;
+            padding: 0 !important;
+            margin: 0 !important;
+        }
+        .com { color: #999 !important; }
+    </style>
+</head>
+<body>
+<div class="header high">
+    <h1>Code coverage report for <span class="entity">utf8.js/</span></h1>
+    <h2>
+        
+        Statements: <span class="metric">92.31% <small>(120 / 130)</small></span>     
+        
+        
+        Branches: <span class="metric">78.79% <small>(52 / 66)</small></span>     
+        
+        
+        Functions: <span class="metric">90% <small>(9 / 10)</small></span>     
+        
+        
+        Lines: <span class="metric">92.31% <small>(120 / 130)</small></span>     
+        
+    </h2>
+    <div class="path"><a href="../index.html">All files</a> » utf8.js/</div>
+</div>
+<div class="body">
+<div class="coverage-summary">
+<table>
+<thead>
+<tr>
+   <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
+   <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
+   <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
+   <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
+   <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
+   <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
+   <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
+</tr>
+</thead>
+<tbody><tr>
+	<td class="file high" data-value="utf8.js"><a href="utf8.js.html">utf8.js</a></td>
+	<td data-value="92.31" class="pic high"><span class="cover-fill" style="width: 92px;"></span><span class="cover-empty" style="width:8px;"></span></td>
+	<td data-value="92.31" class="pct high">92.31%</td>
+	<td data-value="130" class="abs high">(120 / 130)</td>
+	<td data-value="78.79" class="pct medium">78.79%</td>
+	<td data-value="66" class="abs medium">(52 / 66)</td>
+	<td data-value="90" class="pct high">90%</td>
+	<td data-value="10" class="abs high">(9 / 10)</td>
+	<td data-value="92.31" class="pct high">92.31%</td>
+	<td data-value="130" class="abs high">(120 / 130)</td>
+	</tr>
+
+</tbody>
+</table>
+</div>
+</div>
+<div class="footer">
+    <div class="meta">Generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Thu Jun 20 2013 14:00:05 GMT+0200 (CEST)</div>
+</div>
+
+<script src="../prettify.js"></script>
+
+<script src="http://yui.yahooapis.com/3.6.0/build/yui/yui-min.js"></script>
+<script>
+
+    YUI().use('datatable', function (Y) {
+
+        var formatters = {
+          pct: function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              try {
+                  return o.value.toFixed(2) + '%';
+              } catch (ex) { return o.value + '%'; }
+          },
+          html: function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              return o.record.get(o.column.key + '_html');
+          }
+        },
+          defaultFormatter = function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              return o.value;
+          };
+
+        function getColumns(theadNode) {
+            var colNodes = theadNode.all('tr th'),
+                cols = [],
+                col;
+            colNodes.each(function (colNode) {
+                col = {
+                    key: colNode.getAttribute('data-col'),
+                    label: colNode.get('innerHTML') || ' ',
+                    sortable: !colNode.getAttribute('data-nosort'),
+                    className: colNode.getAttribute('class'),
+                    type: colNode.getAttribute('data-type'),
+                    allowHTML: colNode.getAttribute('data-html') === 'true' || colNode.getAttribute('data-fmt') === 'html'
+                };
+                col.formatter = formatters[colNode.getAttribute('data-fmt')] || defaultFormatter;
+                cols.push(col);
+            });
+            return cols;
+        }
+
+        function getRowData(trNode, cols) {
+            var tdNodes = trNode.all('td'),
+                    i,
+                    row = { classes: {} },
+                    node,
+                    name;
+            for (i = 0; i < cols.length; i += 1) {
+                name = cols[i].key;
+                node = tdNodes.item(i);
+                row[name] = node.getAttribute('data-value') || node.get('innerHTML');
+                row[name + '_html'] = node.get('innerHTML');
+                row.classes[name] = node.getAttribute('class');
+                //Y.log('Name: ' + name + '; Value: ' + row[name]);
+                if (cols[i].type === 'number') { row[name] = row[name] * 1; }
+            }
+            //Y.log(row);
+            return row;
+        }
+
+        function getData(tbodyNode, cols) {
+            var data = [];
+            tbodyNode.all('tr').each(function (trNode) {
+                data.push(getRowData(trNode, cols));
+            });
+            return data;
+        }
+
+        function replaceTable(node) {
+            if (!node) { return; }
+            var cols = getColumns(node.one('thead')),
+                data = getData(node.one('tbody'), cols),
+                table,
+                parent = node.get('parentNode');
+
+            table = new Y.DataTable({
+                columns: cols,
+                data: data,
+                sortBy: 'file'
+            });
+            parent.set('innerHTML', '');
+            table.render(parent);
+        }
+
+        Y.on('domready', function () {
+            replaceTable(Y.one('div.coverage-summary table'));
+            if (typeof prettyPrint === 'function') {
+                prettyPrint();
+            }
+        });
+    });
+</script>
+</body>
+</html>
diff --git a/coverage/utf8.js/utf8.js.html b/coverage/utf8.js/utf8.js.html
new file mode 100644
index 0000000..8923045
--- /dev/null
+++ b/coverage/utf8.js/utf8.js.html
@@ -0,0 +1,1028 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <title>Code coverage report for utf8.js/utf8.js</title>
+    <meta charset="utf-8">
+
+    <link rel="stylesheet" href="../prettify.css">
+
+    <style>
+        body, html {
+            margin:0; padding: 0;
+        }
+        body {
+            font-family: Helvetica Neue, Helvetica,Arial;
+            font-size: 10pt;
+        }
+        div.header, div.footer {
+            background: #eee;
+            padding: 1em;
+        }
+        div.header {
+            z-index: 100;
+            position: fixed;
+            top: 0;
+            border-bottom: 1px solid #666;
+            width: 100%;
+        }
+        div.footer {
+            border-top: 1px solid #666;
+        }
+        div.body {
+            margin-top: 10em;
+        }
+        div.meta {
+            font-size: 90%;
+            text-align: center;
+        }
+        h1, h2, h3 {
+            font-weight: normal;
+        }
+        h1 {
+            font-size: 12pt;
+        }
+        h2 {
+            font-size: 10pt;
+        }
+        pre {
+            font-family: Consolas, Menlo, Monaco, monospace;
+            margin: 0;
+            padding: 0;
+            line-height: 14px;
+            font-size: 14px;
+            -moz-tab-size: 2;
+            -o-tab-size:  2;
+            tab-size: 2;
+        }
+
+        div.path { font-size: 110%; }
+        div.path a:link, div.path a:visited { color: #000; }
+        table.coverage { border-collapse: collapse; margin:0; padding: 0 }
+
+        table.coverage td {
+            margin: 0;
+            padding: 0;
+            color: #111;
+            vertical-align: top;
+        }
+        table.coverage td.line-count {
+            width: 50px;
+            text-align: right;
+            padding-right: 5px;
+        }
+        table.coverage td.line-coverage {
+            color: #777 !important;
+            text-align: right;
+            border-left: 1px solid #666;
+            border-right: 1px solid #666;
+        }
+
+        table.coverage td.text {
+        }
+
+        table.coverage td span.cline-any {
+            display: inline-block;
+            padding: 0 5px;
+            width: 40px;
+        }
+        table.coverage td span.cline-neutral {
+            background: #eee;
+        }
+        table.coverage td span.cline-yes {
+            background: #b5d592;
+            color: #999;
+        }
+        table.coverage td span.cline-no {
+            background: #fc8c84;
+        }
+
+        .cstat-yes { color: #111; }
+        .cstat-no { background: #fc8c84; color: #111; }
+        .fstat-no { background: #ffc520; color: #111 !important; }
+        .cbranch-no { background:  yellow !important; color: #111; }
+        .missing-if-branch {
+            display: inline-block;
+            margin-right: 10px;
+            position: relative;
+            padding: 0 4px;
+            background: black;
+            color: yellow;
+            xtext-decoration: line-through;
+        }
+        .missing-if-branch .typ {
+            color: inherit !important;
+        }
+
+        .entity, .metric { font-weight: bold; }
+        .metric { display: inline-block; border: 1px solid #333; padding: 0.3em; background: white; }
+        .metric small { font-size: 80%; font-weight: normal; color: #666; }
+
+        div.coverage-summary table { border-collapse: collapse; margin: 3em; font-size: 110%; }
+        div.coverage-summary td, div.coverage-summary table  th { margin: 0; padding: 0.25em 1em; border-top: 1px solid #666; border-bottom: 1px solid #666; }
+        div.coverage-summary th { text-align: left; border: 1px solid #666; background: #eee; font-weight: normal; }
+        div.coverage-summary th.file { border-right: none !important; }
+        div.coverage-summary th.pic { border-left: none !important; text-align: right; }
+        div.coverage-summary th.pct { border-right: none !important; }
+        div.coverage-summary th.abs { border-left: none !important; text-align: right; }
+        div.coverage-summary td.pct { text-align: right; border-left: 1px solid #666; }
+        div.coverage-summary td.abs { text-align: right; font-size: 90%; color: #444; border-right: 1px solid #666; }
+        div.coverage-summary td.file { text-align: right; border-left: 1px solid #666; white-space: nowrap;  }
+        div.coverage-summary td.pic { min-width: 120px !important;  }
+        div.coverage-summary a:link { text-decoration: none; color: #000; }
+        div.coverage-summary a:visited { text-decoration: none; color: #333; }
+        div.coverage-summary a:hover { text-decoration: underline; }
+        div.coverage-summary tfoot td { border-top: 1px solid #666; }
+
+        div.coverage-summary .yui3-datatable-sort-indicator, div.coverage-summary .dummy-sort-indicator {
+            height: 10px;
+            width: 7px;
+            display: inline-block;
+            margin-left: 0.5em;
+        }
+        div.coverage-summary .yui3-datatable-sort-indicator {
+            background: url("http://yui.yahooapis.com/3.6.0/build/datatable-sort/assets/skins/sam/sort-arrow-sprite.png") no-repeat scroll 0 0 transparent;
+        }
+        div.coverage-summary .yui3-datatable-sorted .yui3-datatable-sort-indicator {
+            background-position: 0 -20px;
+        }
+        div.coverage-summary .yui3-datatable-sorted-desc .yui3-datatable-sort-indicator {
+            background-position: 0 -10px;
+        }
+
+        .high { background: #b5d592 !important; }
+        .medium { background: #ffe87c !important; }
+        .low { background: #fc8c84 !important; }
+
+        span.cover-fill, span.cover-empty {
+            display:inline-block;
+            border:1px solid #444;
+            background: white;
+            height: 12px;
+        }
+        span.cover-fill {
+            background: #ccc;
+            border-right: 1px solid #444;
+        }
+        span.cover-empty {
+            background: white;
+            border-left: none;
+        }
+        span.cover-full {
+            border-right: none !important;
+        }
+        pre.prettyprint {
+            border: none !important;
+            padding: 0 !important;
+            margin: 0 !important;
+        }
+        .com { color: #999 !important; }
+    </style>
+</head>
+<body>
+<div class="header high">
+    <h1>Code coverage report for <span class="entity">utf8.js/utf8.js</span></h1>
+    <h2>
+        
+        Statements: <span class="metric">92.31% <small>(120 / 130)</small></span>     
+        
+        
+        Branches: <span class="metric">78.79% <small>(52 / 66)</small></span>     
+        
+        
+        Functions: <span class="metric">90% <small>(9 / 10)</small></span>     
+        
+        
+        Lines: <span class="metric">92.31% <small>(120 / 130)</small></span>     
+        
+    </h2>
+    <div class="path"><a href="../index.html">All files</a> » <a href="index.html">utf8.js/</a> » utf8.js</div>
+</div>
+<div class="body">
+<pre><table class="coverage">
+<tr><td class="line-count">1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242</td><td class="line-coverage"><span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-yes">128</span>
+<span class="cline-any cline-yes">128</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">8</span>
+<span class="cline-any cline-yes">8</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">3</span>
+<span class="cline-any cline-yes">3</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">120</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">52</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">29</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">27</span>
+<span class="cline-any cline-yes">27</span>
+<span class="cline-any cline-yes">3</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">19</span>
+<span class="cline-any cline-yes">19</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">27</span>
+<span class="cline-any cline-yes">27</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">59</span>
+<span class="cline-any cline-yes">59</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">59</span>
+<span class="cline-any cline-yes">57</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">2</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">60</span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">36</span>
+<span class="cline-any cline-yes">36</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">36</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">31</span>
+<span class="cline-any cline-yes">4</span>
+<span class="cline-any cline-yes">3</span>
+<span class="cline-any cline-yes">3</span>
+<span class="cline-any cline-yes">3</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">27</span>
+<span class="cline-any cline-yes">20</span>
+<span class="cline-any cline-yes">19</span>
+<span class="cline-any cline-yes">19</span>
+<span class="cline-any cline-yes">19</span>
+<span class="cline-any cline-yes">19</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">7</span>
+<span class="cline-any cline-yes">6</span>
+<span class="cline-any cline-yes">6</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-yes">5</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">28</span>
+<span class="cline-any cline-yes">28</span>
+<span class="cline-any cline-yes">28</span>
+<span class="cline-any cline-yes">28</span>
+<span class="cline-any cline-yes">28</span>
+<span class="cline-any cline-yes">28</span>
+<span class="cline-any cline-yes">32</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">24</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-yes">1</span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-no"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span>
+<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/*! http://mths.be/utf8js v2.0.0 by @mathias */
+;(function(root) {
+ 
+	// Detect free variables `exports`
+	var freeExports = typeof exports == 'object' && exports;
+ 
+	// Detect free variable `module`
+	var freeModule = typeof module == 'object' && module &&
+		module.exports == freeExports && module;
+ 
+	// Detect free variable `global`, from Node.js or Browserified code,
+	// and use it as `root`
+	var freeGlobal = typeof global == 'object' && global;
+	<span class="missing-if-branch" title="else path not taken"" >E</span>if (freeGlobal.global === freeGlobal || <span class="branch-1 cbranch-no" title="branch not covered" >freeGlobal.window === freeGlobal)</span> {
+		root = freeGlobal;
+	}
+ 
+	/*--------------------------------------------------------------------------*/
+ 
+	var stringFromCharCode = String.fromCharCode;
+ 
+	// Taken from http://mths.be/punycode
+	function ucs2decode(string) {
+		var output = [];
+		var counter = 0;
+		var length = string.length;
+		var value;
+		var extra;
+		while (counter < length) {
+			value = string.charCodeAt(counter++);
+			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+				// high surrogate, and there is a next character
+				extra = string.charCodeAt(counter++);
+				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+				} else {
+					// unmatched surrogate; only append this code unit, in case the next
+					// code unit is the high surrogate of a surrogate pair
+					output.push(value);
+					counter--;
+				}
+			} else {
+				output.push(value);
+			}
+		}
+		return output;
+	}
+ 
+	// Taken from http://mths.be/punycode
+	function ucs2encode(array) {
+		var length = array.length;
+		var index = -1;
+		var value;
+		var output = '';
+		while (++index < length) {
+			value = array[index];
+			if (value > 0xFFFF) {
+				value -= 0x10000;
+				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+				value = 0xDC00 | value & 0x3FF;
+			}
+			output += stringFromCharCode(value);
+		}
+		return output;
+	}
+ 
+	/*--------------------------------------------------------------------------*/
+ 
+	// https://github.com/php/php-src/blob/master/ext/mbstring/oniguruma/enc/utf8.c
+	function createByte(codePoint, shift) {
+		return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
+	}
+ 
+	function encodeCodePoint(codePoint) {
+		if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
+			return stringFromCharCode(codePoint);
+		}
+		var symbol = '';
+		if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
+			symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
+		}
+		else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
+			symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
+			symbol += createByte(codePoint, 6);
+		}
+		else <span class="missing-if-branch" title="else path not taken"" >E</span>if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
+			symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
+			symbol += createByte(codePoint, 12);
+			symbol += createByte(codePoint, 6);
+		}
+		symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
+		return symbol;
+	}
+ 
+	function utf8encode(string) {
+		var codePoints = ucs2decode(string);
+ 
+		// console.log(JSON.stringify(codePoints.map(function(x) {
+		// 	return 'U+' + x.toString(16).toUpperCase();
+		// })));
+ 
+		var length = codePoints.length;
+		var index = -1;
+		var codePoint;
+		var byteString = '';
+		while (++index < length) {
+			codePoint = codePoints[index];
+			byteString += encodeCodePoint(codePoint);
+		}
+		return byteString;
+	}
+ 
+	/*--------------------------------------------------------------------------*/
+ 
+	// https://github.com/php/php-src/blob/master/ext/json/utf8_decode.c
+	function readContinuationByte() {
+		if (byteIndex >= byteCount) {
+			throw Error('Invalid byte index');
+		}
+ 
+		var continuationByte = byteArray[byteIndex] & 0xFF;
+		byteIndex++;
+ 
+		if ((continuationByte & 0xC0) == 0x80) {
+			return continuationByte & 0x3F;
+		}
+ 
+		// If we end up here, it’s not a continuation byte
+		throw Error('Invalid continuation byte');
+	}
+ 
+	function decodeSymbol() {
+		var byte1;
+		var byte2;
+		var byte3;
+		var byte4;
+		var codePoint;
+ 
+		<span class="missing-if-branch" title="if path not taken"" >I</span>if (byteIndex > byteCount) {
+<span class="cstat-no" title="statement not covered" >			throw Error('Invalid byte index');</span>
+		}
+ 
+		if (byteIndex == byteCount) {
+			return false;
+		}
+ 
+		// Read first byte
+		byte1 = byteArray[byteIndex] & 0xFF;
+		byteIndex++;
+ 
+		// 1-byte sequence (no continuation bytes)
+		if ((byte1 & 0x80) == 0) {
+			return byte1;
+		}
+ 
+		// 2-byte sequence
+		if ((byte1 & 0xE0) == 0xC0) {
+			var byte2 = readContinuationByte();
+			codePoint = ((byte1 & 0x1F) << 6) | byte2;
+			<span class="missing-if-branch" title="else path not taken"" >E</span>if (codePoint >= 0x80) {
+				return codePoint;
+			} else {
+<span class="cstat-no" title="statement not covered" >				throw Error('Invalid continuation byte');</span>
+			}
+		}
+ 
+		// 3-byte sequence (may include unpaired surrogates)
+		if ((byte1 & 0xF0) == 0xE0) {
+			byte2 = readContinuationByte();
+			byte3 = readContinuationByte();
+			codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
+			<span class="missing-if-branch" title="else path not taken"" >E</span>if (codePoint >= 0x0800) {
+				return codePoint;
+			} else {
+<span class="cstat-no" title="statement not covered" >				throw Error('Invalid continuation byte');</span>
+			}
+		}
+ 
+		// 4-byte sequence
+		if ((byte1 & 0xF8) == 0xF0) {
+			byte2 = readContinuationByte();
+			byte3 = readContinuationByte();
+			byte4 = readContinuationByte();
+			codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) |
+				(byte3 << 0x06) | byte4;
+			<span class="missing-if-branch" title="else path not taken"" >E</span>if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+				return codePoint;
+			}
+		}
+ 
+		throw Error('Invalid UTF-8 detected');
+	}
+ 
+	var byteArray;
+	var byteCount;
+	var byteIndex;
+	function utf8decode(byteString) {
+		byteArray = ucs2decode(byteString);
+		byteCount = byteArray.length;
+		byteIndex = 0;
+		var codePoints = [];
+		var tmp;
+		while ((tmp = decodeSymbol()) !== false) {
+			codePoints.push(tmp);
+		}
+		return ucs2encode(codePoints);
+	}
+ 
+	/*--------------------------------------------------------------------------*/
+ 
+	var utf8 = {
+		'version': '2.0.0',
+		'encode': utf8encode,
+		'decode': utf8decode
+	};
+ 
+	// Some AMD build optimizers, like r.js, check for specific condition patterns
+	// like the following:
+	<span class="missing-if-branch" title="if path not taken"" >I</span>if (
+		typeof define == 'function' &&
+<span class="branch-1 cbranch-no" title="branch not covered" >		typeof define.amd == 'object' </span>&&
+<span class="branch-2 cbranch-no" title="branch not covered" >		define.amd</span>
+	) {
+<span class="cstat-no" title="statement not covered" >		define(<span class="fstat-no" title="function not covered" >function() {</span></span>
+<span class="cstat-no" title="statement not covered" >			return utf8;</span>
+		});
+	}	else <span class="missing-if-branch" title="else path not taken"" >E</span>if (freeExports && !freeExports.nodeType) {
+		<span class="missing-if-branch" title="else path not taken"" >E</span>if (freeModule) { // in Node.js or RingoJS v0.8.0+
+			freeModule.exports = utf8;
+		} else { // in Narwhal or RingoJS v0.7.0-
+<span class="cstat-no" title="statement not covered" >			var object = {};</span>
+<span class="cstat-no" title="statement not covered" >			var hasOwnProperty = object.hasOwnProperty;</span>
+<span class="cstat-no" title="statement not covered" >			for (var key in utf8) {</span>
+<span class="cstat-no" title="statement not covered" >				hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);</span>
+			}
+		}
+	} else { // in Rhino or a web browser
+<span class="cstat-no" title="statement not covered" >		root.utf8 = utf8;</span>
+	}
+ 
+}(this));
+ </pre></td></tr>
+</table></pre>
+
+</div>
+<div class="footer">
+    <div class="meta">Generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Thu Jun 20 2013 14:00:05 GMT+0200 (CEST)</div>
+</div>
+
+<script src="../prettify.js"></script>
+
+<script src="http://yui.yahooapis.com/3.6.0/build/yui/yui-min.js"></script>
+<script>
+
+    YUI().use('datatable', function (Y) {
+
+        var formatters = {
+          pct: function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              try {
+                  return o.value.toFixed(2) + '%';
+              } catch (ex) { return o.value + '%'; }
+          },
+          html: function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              return o.record.get(o.column.key + '_html');
+          }
+        },
+          defaultFormatter = function (o) {
+              o.className += o.record.get('classes')[o.column.key];
+              return o.value;
+          };
+
+        function getColumns(theadNode) {
+            var colNodes = theadNode.all('tr th'),
+                cols = [],
+                col;
+            colNodes.each(function (colNode) {
+                col = {
+                    key: colNode.getAttribute('data-col'),
+                    label: colNode.get('innerHTML') || ' ',
+                    sortable: !colNode.getAttribute('data-nosort'),
+                    className: colNode.getAttribute('class'),
+                    type: colNode.getAttribute('data-type'),
+                    allowHTML: colNode.getAttribute('data-html') === 'true' || colNode.getAttribute('data-fmt') === 'html'
+                };
+                col.formatter = formatters[colNode.getAttribute('data-fmt')] || defaultFormatter;
+                cols.push(col);
+            });
+            return cols;
+        }
+
+        function getRowData(trNode, cols) {
+            var tdNodes = trNode.all('td'),
+                    i,
+                    row = { classes: {} },
+                    node,
+                    name;
+            for (i = 0; i < cols.length; i += 1) {
+                name = cols[i].key;
+                node = tdNodes.item(i);
+                row[name] = node.getAttribute('data-value') || node.get('innerHTML');
+                row[name + '_html'] = node.get('innerHTML');
+                row.classes[name] = node.getAttribute('class');
+                //Y.log('Name: ' + name + '; Value: ' + row[name]);
+                if (cols[i].type === 'number') { row[name] = row[name] * 1; }
+            }
+            //Y.log(row);
+            return row;
+        }
+
+        function getData(tbodyNode, cols) {
+            var data = [];
+            tbodyNode.all('tr').each(function (trNode) {
+                data.push(getRowData(trNode, cols));
+            });
+            return data;
+        }
+
+        function replaceTable(node) {
+            if (!node) { return; }
+            var cols = getColumns(node.one('thead')),
+                data = getData(node.one('tbody'), cols),
+                table,
+                parent = node.get('parentNode');
+
+            table = new Y.DataTable({
+                columns: cols,
+                data: data,
+                sortBy: 'file'
+            });
+            parent.set('innerHTML', '');
+            table.render(parent);
+        }
+
+        Y.on('domready', function () {
+            replaceTable(Y.one('div.coverage-summary table'));
+            if (typeof prettyPrint === 'function') {
+                prettyPrint();
+            }
+        });
+    });
+</script>
+</body>
+</html>
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..308e66e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,49 @@
+{
+	"name": "utf8",
+	"version": "2.0.0",
+	"description": "A well-tested UTF-8 encoder/decoder written in JavaScript.",
+	"homepage": "http://mths.be/utf8js",
+	"main": "utf8.js",
+	"keywords": [
+		"charset",
+		"encoding",
+		"unicode",
+		"utf8"
+	],
+	"licenses": [
+		{
+			"type": "MIT",
+			"url": "http://mths.be/mit"
+		},
+		{
+			"type": "GPL",
+			"url": "http://mths.be/gpl"
+		}
+	],
+	"author": {
+		"name": "Mathias Bynens",
+		"url": "http://mathiasbynens.be/"
+	},
+	"repository": {
+		"type": "git",
+		"url": "https://github.com/mathiasbynens/utf8.js.git"
+	},
+	"bugs": {
+		"url": "https://github.com/mathiasbynens/utf8.js/issues"
+	},
+	"directories": {
+		"test": "tests"
+	},
+	"scripts": {
+		"test": "node tests/tests.js"
+	},
+	"dependencies": {},
+	"devDependencies": {
+		"grunt": "~0.4.1",
+		"grunt-shell": "~0.2.2",
+		"istanbul": "~0.1.36",
+		"qunit-clib": "~1.3.0",
+		"qunitjs": "~1.11.0",
+		"requirejs": "~2.1.6"
+	}
+}
diff --git a/tests/generate-test-data.py b/tests/generate-test-data.py
new file mode 100755
index 0000000..096b012
--- /dev/null
+++ b/tests/generate-test-data.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+import re
+import json
+
+# http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+# http://stackoverflow.com/a/13436167/96656
+def unisymbol(codePoint):
+	if codePoint >= 0x0000 and codePoint <= 0xFFFF:
+		return unichr(codePoint)
+	elif codePoint >= 0x010000 and codePoint <= 0x10FFFF:
+		highSurrogate = int((codePoint - 0x10000) / 0x400) + 0xD800
+		lowSurrogate = int((codePoint - 0x10000) % 0x400) + 0xDC00
+		return unichr(highSurrogate) + unichr(lowSurrogate)
+	else:
+		return 'Error'
+
+def hexify(codePoint):
+	return 'U+' + hex(codePoint)[2:].upper().zfill(6)
+
+def writeFile(filename, contents):
+	print filename
+	with open(filename, 'w') as f:
+		f.write(contents.strip() + '\n')
+
+data = []
+for codePoint in range(0x000000, 0x10FFFF + 1):
+	symbol = unisymbol(codePoint)
+	# http://stackoverflow.com/a/17199950/96656
+	bytes = symbol.encode('utf8').decode('latin1')
+	data.append({
+		'codePoint': codePoint,
+		'decoded': symbol,
+		'encoded': bytes
+	});
+
+jsonData = json.dumps(data, sort_keys=False, indent=2, separators=(',', ': '))
+# Use tabs instead of double spaces for indentation
+jsonData = jsonData.replace('  ', '\t')
+# Escape hexadecimal digits in escape sequences
+jsonData = re.sub(
+	r'\\u([a-fA-F0-9]{4})',
+	lambda match: r'\u{}'.format(match.group(1).upper()),
+	jsonData
+)
+
+writeFile('data.json', jsonData)
diff --git a/tests/index.html b/tests/index.html
new file mode 100644
index 0000000..13cc04d
--- /dev/null
+++ b/tests/index.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="utf-8">
+		<title>utf8.js test suite</title>
+		<link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
+	</head>
+	<body>
+		<div id="qunit"></div>
+		<script src="../node_modules/qunitjs/qunit/qunit.js"></script>
+		<script src="../utf8.js"></script>
+		<script>
+			// populate `QUnit.urlParams`
+			QUnit.urlParams.norequire = /[?&]norequire=true(?:&|$)/.test(location.search);
+
+			// load tests.js if not using require.js
+			document.write(QUnit.urlParams.norequire
+				? '<script src="tests.js"><\/script>'
+				: '<script src="../node_modules/requirejs/require.js"><\/script>'
+			);
+		</script>
+		<script>
+			window.require && require({
+				'baseUrl': '../node_modules/requirejs/',
+				'urlArgs': 't=' + (+new Date),
+				'paths': {
+					'utf8': '../../utf8'
+				}
+			},
+			['utf8'], function(utf8) {
+				require(['tests.js']);
+			});
+		</script>
+	</body>
+</html>
diff --git a/tests/tests.js b/tests/tests.js
new file mode 100644
index 0000000..cb01e10
--- /dev/null
+++ b/tests/tests.js
@@ -0,0 +1,242 @@
+;(function(root) {
+	'use strict';
+
+	/** Use a single `load` function */
+	var load = typeof require == 'function' ? require : root.load;
+
+	/** The unit testing framework */
+	var QUnit = (function() {
+		var noop = Function.prototype;
+		return root.QUnit || (
+			root.addEventListener || (root.addEventListener = noop),
+			root.setTimeout || (root.setTimeout = noop),
+			root.QUnit = load('../node_modules/qunitjs/qunit/qunit.js') || root.QUnit,
+			(load('../node_modules/qunit-clib/qunit-clib.js') || { 'runInContext': noop }).runInContext(root),
+			addEventListener === noop && delete root.addEventListener,
+			root.QUnit
+		);
+	}());
+
+	/** The `utf8` object to test */
+	var utf8 = root.utf8 || (root.utf8 = (
+		utf8 = load('../utf8.js') || root.utf8,
+		utf8 = utf8.utf8 || utf8
+	));
+
+	/*--------------------------------------------------------------------------*/
+
+	function forEach(array, fn) {
+		var index = -1;
+		var length = array.length;
+		while (++index < length) {
+			fn(array[index]);
+		}
+	}
+
+	// Quick and dirty test to see if we’re in Node & need extended tests
+	var runExtendedTests = (function() {
+		try {
+			return process.argv[0] == 'node' && process.argv[2] == '--extended';
+		} catch(error) { }
+	}());
+
+	var data = [
+		// 1-byte
+		{
+			'codePoint': 0x0000,
+			'decoded': '\0',
+			'encoded': '\0'
+		},
+		{
+			'codePoint': 0x005C,
+			'decoded': '\x5C',
+			'encoded': '\x5C'
+		},
+		{
+			'codePoint': 0x007F,
+			'decoded': '\x7F',
+			'encoded': '\x7F'
+		},
+
+		// 2-byte
+		{
+			'codePoint': 0x0080,
+			'decoded': '\x80',
+			'encoded': '\xC2\x80'
+		},
+		{
+			'codePoint': 0x05CA,
+			'decoded': '\u05CA',
+			'encoded': '\xD7\x8A'
+		},
+		{
+			'codePoint': 0x07FF,
+			'decoded': '\u07FF',
+			'encoded': '\xDF\xBF',
+		},
+
+		// 3-byte
+		{
+			'codePoint': 0x0800,
+			'decoded': '\u0800',
+			'encoded': '\xE0\xA0\x80',
+		},
+		{
+			'codePoint': 0x2C3C,
+			'decoded': '\u2C3C',
+			'encoded': '\xE2\xB0\xBC'
+		},
+		{
+			'codePoint': 0xFFFF,
+			'decoded': '\uFFFF',
+			'encoded': '\xEF\xBF\xBF'
+		},
+		// unmatched surrogate halves
+		// high surrogates: 0xD800 to 0xDBFF
+		{
+			'codePoint': 0xD800,
+			'decoded': '\uD800',
+			'encoded': '\xED\xA0\x80'
+		},
+		{
+			'description': 'High surrogate followed by another high surrogate',
+			'decoded': '\uD800\uD800',
+			'encoded': '\xED\xA0\x80\xED\xA0\x80'
+		},
+		{
+			'description': 'High surrogate followed by a symbol that is not a surrogate',
+			'decoded': '\uD800A',
+			'encoded': '\xED\xA0\x80A'
+		},
+		{
+			'description': 'Unmatched high surrogate, followed by a surrogate pair, followed by an unmatched high surrogate',
+			'decoded': '\uD800\uD834\uDF06\uD800',
+			'encoded': '\xED\xA0\x80\xF0\x9D\x8C\x86\xED\xA0\x80'
+		},
+		{
+			'codePoint': 0xD9AF,
+			'decoded': '\uD9AF',
+			'encoded': '\xED\xA6\xAF'
+		},
+		{
+			'codePoint': 0xDBFF,
+			'decoded': '\uDBFF',
+			'encoded': '\xED\xAF\xBF'
+		},
+		// low surrogates: 0xDC00 to 0xDFFF
+		{
+			'codePoint': 0xDC00,
+			'decoded': '\uDC00',
+			'encoded': '\xED\xB0\x80'
+		},
+		{
+			'description': 'Low surrogate followed by another low surrogate',
+			'decoded': '\uDC00\uDC00',
+			'encoded': '\xED\xB0\x80\xED\xB0\x80'
+		},
+		{
+			'description': 'Low surrogate followed by a symbol that is not a surrogate',
+			'decoded': '\uDC00A',
+			'encoded': '\xED\xB0\x80A'
+		},
+		{
+			'description': 'Unmatched low surrogate, followed by a surrogate pair, followed by an unmatched low surrogate',
+			'decoded': '\uDC00\uD834\uDF06\uDC00',
+			'encoded': '\xED\xB0\x80\xF0\x9D\x8C\x86\xED\xB0\x80'
+		},
+		{
+			'codePoint': 0xDEEE,
+			'decoded': '\uDEEE',
+			'encoded': '\xED\xBB\xAE'
+		},
+		{
+			'codePoint': 0xDFFF,
+			'decoded': '\uDFFF',
+			'encoded': '\xED\xBF\xBF'
+		},
+
+		// 4-byte
+		{
+			'codePoint': 0x010000,
+			'decoded': '\uD800\uDC00',
+			'encoded': '\xF0\x90\x80\x80'
+		},
+		{
+			'codePoint': 0x01D306,
+			'decoded': '\uD834\uDF06',
+			'encoded': '\xF0\x9D\x8C\x86'
+		},
+		{
+			'codePoint': 0x10FFF,
+			'decoded': '\uDBFF\uDFFF',
+			'encoded': '\xF4\x8F\xBF\xBF'
+		}
+	];
+
+	if (runExtendedTests) {
+		data = data.concat(require('./data.json'));
+	}
+
+	// `throws` is a reserved word in ES3; alias it to avoid errors
+	var raises = QUnit.assert['throws'];
+
+	// explicitly call `QUnit.module()` instead of `module()`
+	// in case we are in a CLI environment
+	QUnit.module('utf8.js');
+
+	test('encode/decode', function() {
+		forEach(data, function(object) {
+			var description = object.description || 'U+' + object.codePoint.toString(16).toUpperCase();
+			;
+			equal(
+				object.encoded,
+				utf8.encode(object.decoded),
+				'Encoding: ' + description
+			);
+			equal(
+				object.decoded,
+				utf8.decode(object.encoded),
+				'Decoding: ' + description
+			);
+		});
+
+		// Error handling
+		raises(
+			function() {
+				utf8.decode('\uFFFF');
+			},
+			Error,
+			'Error: invalid UTF-8 detected'
+		);
+		raises(
+			function() {
+				utf8.decode('\xE9\x00\x00');
+			},
+			Error,
+			'Error: invalid continuation byte (4-byte sequence expected)'
+		);
+		raises(
+			function() {
+				utf8.decode('\xC2\uFFFF');
+			},
+			Error,
+			'Error: invalid continuation byte'
+		);
+		raises(
+			function() {
+				utf8.decode('\xF0\x9D');
+			},
+			Error,
+			'Error: invalid byte index'
+		);
+	});
+
+	/*--------------------------------------------------------------------------*/
+
+	// configure QUnit and call `QUnit.start()` for
+	// Narwhal, Node.js, PhantomJS, Rhino, and RingoJS
+	if (!root.document || root.phantom) {
+		QUnit.config.noglobals = true;
+		QUnit.start();
+	}
+}(typeof global == 'object' && global || this));
diff --git a/utf8.js b/utf8.js
new file mode 100644
index 0000000..8f99bb1
--- /dev/null
+++ b/utf8.js
@@ -0,0 +1,239 @@
+/*! http://mths.be/utf8js v2.0.0 by @mathias */
+;(function(root) {
+
+	// Detect free variables `exports`
+	var freeExports = typeof exports == 'object' && exports;
+
+	// Detect free variable `module`
+	var freeModule = typeof module == 'object' && module &&
+		module.exports == freeExports && module;
+
+	// Detect free variable `global`, from Node.js or Browserified code,
+	// and use it as `root`
+	var freeGlobal = typeof global == 'object' && global;
+	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+		root = freeGlobal;
+	}
+
+	/*--------------------------------------------------------------------------*/
+
+	var stringFromCharCode = String.fromCharCode;
+
+	// Taken from http://mths.be/punycode
+	function ucs2decode(string) {
+		var output = [];
+		var counter = 0;
+		var length = string.length;
+		var value;
+		var extra;
+		while (counter < length) {
+			value = string.charCodeAt(counter++);
+			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+				// high surrogate, and there is a next character
+				extra = string.charCodeAt(counter++);
+				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+				} else {
+					// unmatched surrogate; only append this code unit, in case the next
+					// code unit is the high surrogate of a surrogate pair
+					output.push(value);
+					counter--;
+				}
+			} else {
+				output.push(value);
+			}
+		}
+		return output;
+	}
+
+	// Taken from http://mths.be/punycode
+	function ucs2encode(array) {
+		var length = array.length;
+		var index = -1;
+		var value;
+		var output = '';
+		while (++index < length) {
+			value = array[index];
+			if (value > 0xFFFF) {
+				value -= 0x10000;
+				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+				value = 0xDC00 | value & 0x3FF;
+			}
+			output += stringFromCharCode(value);
+		}
+		return output;
+	}
+
+	/*--------------------------------------------------------------------------*/
+
+	function createByte(codePoint, shift) {
+		return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
+	}
+
+	function encodeCodePoint(codePoint) {
+		if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
+			return stringFromCharCode(codePoint);
+		}
+		var symbol = '';
+		if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
+			symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
+		}
+		else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
+			symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
+			symbol += createByte(codePoint, 6);
+		}
+		else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
+			symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
+			symbol += createByte(codePoint, 12);
+			symbol += createByte(codePoint, 6);
+		}
+		symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
+		return symbol;
+	}
+
+	function utf8encode(string) {
+		var codePoints = ucs2decode(string);
+
+		// console.log(JSON.stringify(codePoints.map(function(x) {
+		// 	return 'U+' + x.toString(16).toUpperCase();
+		// })));
+
+		var length = codePoints.length;
+		var index = -1;
+		var codePoint;
+		var byteString = '';
+		while (++index < length) {
+			codePoint = codePoints[index];
+			byteString += encodeCodePoint(codePoint);
+		}
+		return byteString;
+	}
+
+	/*--------------------------------------------------------------------------*/
+
+	function readContinuationByte() {
+		if (byteIndex >= byteCount) {
+			throw Error('Invalid byte index');
+		}
+
+		var continuationByte = byteArray[byteIndex] & 0xFF;
+		byteIndex++;
+
+		if ((continuationByte & 0xC0) == 0x80) {
+			return continuationByte & 0x3F;
+		}
+
+		// If we end up here, it’s not a continuation byte
+		throw Error('Invalid continuation byte');
+	}
+
+	function decodeSymbol() {
+		var byte1;
+		var byte2;
+		var byte3;
+		var byte4;
+		var codePoint;
+
+		if (byteIndex > byteCount) {
+			throw Error('Invalid byte index');
+		}
+
+		if (byteIndex == byteCount) {
+			return false;
+		}
+
+		// Read first byte
+		byte1 = byteArray[byteIndex] & 0xFF;
+		byteIndex++;
+
+		// 1-byte sequence (no continuation bytes)
+		if ((byte1 & 0x80) == 0) {
+			return byte1;
+		}
+
+		// 2-byte sequence
+		if ((byte1 & 0xE0) == 0xC0) {
+			var byte2 = readContinuationByte();
+			codePoint = ((byte1 & 0x1F) << 6) | byte2;
+			if (codePoint >= 0x80) {
+				return codePoint;
+			} else {
+				throw Error('Invalid continuation byte');
+			}
+		}
+
+		// 3-byte sequence (may include unpaired surrogates)
+		if ((byte1 & 0xF0) == 0xE0) {
+			byte2 = readContinuationByte();
+			byte3 = readContinuationByte();
+			codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
+			if (codePoint >= 0x0800) {
+				return codePoint;
+			} else {
+				throw Error('Invalid continuation byte');
+			}
+		}
+
+		// 4-byte sequence
+		if ((byte1 & 0xF8) == 0xF0) {
+			byte2 = readContinuationByte();
+			byte3 = readContinuationByte();
+			byte4 = readContinuationByte();
+			codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) |
+				(byte3 << 0x06) | byte4;
+			if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+				return codePoint;
+			}
+		}
+
+		throw Error('Invalid UTF-8 detected');
+	}
+
+	var byteArray;
+	var byteCount;
+	var byteIndex;
+	function utf8decode(byteString) {
+		byteArray = ucs2decode(byteString);
+		byteCount = byteArray.length;
+		byteIndex = 0;
+		var codePoints = [];
+		var tmp;
+		while ((tmp = decodeSymbol()) !== false) {
+			codePoints.push(tmp);
+		}
+		return ucs2encode(codePoints);
+	}
+
+	/*--------------------------------------------------------------------------*/
+
+	var utf8 = {
+		'version': '2.0.0',
+		'encode': utf8encode,
+		'decode': utf8decode
+	};
+
+	// Some AMD build optimizers, like r.js, check for specific condition patterns
+	// like the following:
+	if (
+		typeof define == 'function' &&
+		typeof define.amd == 'object' &&
+		define.amd
+	) {
+		define(function() {
+			return utf8;
+		});
+	}	else if (freeExports && !freeExports.nodeType) {
+		if (freeModule) { // in Node.js or RingoJS v0.8.0+
+			freeModule.exports = utf8;
+		} else { // in Narwhal or RingoJS v0.7.0-
+			var object = {};
+			var hasOwnProperty = object.hasOwnProperty;
+			for (var key in utf8) {
+				hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
+			}
+		}
+	} else { // in Rhino or a web browser
+		root.utf8 = utf8;
+	}
+
+}(this));

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-utf8.git



More information about the Pkg-javascript-commits mailing list