[Reproducible-commits] [ckbuilder] 01/03: upstream

Boy Ska boyska-guest at moszumanska.debian.org
Wed Mar 30 04:07:40 UTC 2016


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

boyska-guest pushed a commit to branch pu/reproducible_builds
in repository ckbuilder.

commit bb58685ba1149f89b593420fb128d88981c5169c
Author: boyska <piuttosto at logorroici.org>
Date:   Wed Mar 30 00:02:06 2016 -0400

    upstream
---
 .gitignore                |   6 +
 .gitmodules               |   3 +
 LICENSE.md                | 101 +++++++
 README.md                 |  79 +++++
 dev/build/build.xml       | 114 +++++++
 dev/build/build_jar.bat   |  21 ++
 dev/build/build_jar.sh    |   9 +
 dev/jshint/.jshintrc      |  72 +++++
 dev/jshint/jshint.bat     |  18 ++
 dev/jshint/jshint.sh      |   8 +
 dev/scripts/build.bat     |  25 ++
 dev/scripts/build.sh      |  28 ++
 docs/build.bat            |   8 +
 docs/build.sh             |   8 +
 docs/jsduck.json          |   9 +
 lib/apache/LICENSE.txt    | 202 +++++++++++++
 lib/closure/COPYING       | 202 +++++++++++++
 lib/closure/README.md     | 507 +++++++++++++++++++++++++++++++
 lib/javatar/LICENSE.txt   |  15 +
 lib/rhino/LICENSE.txt     | 375 +++++++++++++++++++++++
 src/assets/help-build.txt |  57 ++++
 src/assets/help-extra.txt |  92 ++++++
 src/assets/help.txt       |  75 +++++
 src/ckbuilder.js          | 113 +++++++
 src/lib/builder.js        | 753 ++++++++++++++++++++++++++++++++++++++++++++++
 src/lib/config.js         | 115 +++++++
 src/lib/controller.js     | 239 +++++++++++++++
 src/lib/css.js            | 239 +++++++++++++++
 src/lib/cssmin.js         | 333 ++++++++++++++++++++
 src/lib/image.js          | 300 ++++++++++++++++++
 src/lib/io.js             | 590 ++++++++++++++++++++++++++++++++++++
 src/lib/javascript.js     | 171 +++++++++++
 src/lib/lang.js           | 144 +++++++++
 src/lib/plugin.js         | 405 +++++++++++++++++++++++++
 src/lib/samples.js        | 233 ++++++++++++++
 src/lib/skin.js           | 275 +++++++++++++++++
 src/lib/tools.js          | 394 ++++++++++++++++++++++++
 src/lib/utils.js          | 224 ++++++++++++++
 test/test.bat             |  11 +
 test/test.js              | 670 +++++++++++++++++++++++++++++++++++++++++
 test/test.sh              |  10 +
 41 files changed, 7253 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c0a0e35
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+
+/.idea
+/docs/output
+/test/tmp
+/dev/scripts/release
+/bin
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..2206060
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "dev/scripts/ckeditor"]
+	path = dev/scripts/ckeditor
+	url = git at github.com:ckeditor/ckeditor-dev.git
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..f9d70f2
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,101 @@
+Software License Agreement
+==========================
+
+CKBuilder - release builder for CKEditor.
+Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+
+Licensed under the terms of the [MIT License](http://en.wikipedia.org/wiki/MIT_License) (see Appendix A).
+
+Sources of Intellectual Property Included in CKBuilder
+-----------------------------------------------------
+
+Where not otherwise indicated, all CKBuilder content is authored by
+CKSource engineers and consists of CKSource-owned intellectual
+property. In some specific instances, CKBuilder will incorporate work
+done by developers outside of CKSource with their express permission.
+
+### Commons CLI ###
+
+The Apache Commons CLI library provides an API for parsing command line options passed to programs.
+
+Location: `lib/apache`
+
+License: Apache License, Version 2.0
+
+### Google Closure Compiler ###
+
+The Closure Compiler performs checking, instrumentation, and optimizations on JavaScript code.
+
+Location: `lib/closure`
+
+License: Apache License, Version 2.0
+
+### JSON ###
+
+Location: `lib/json`
+
+License: Public Domain
+
+### Rhino ###
+
+Rhino is an open source JavaScript engine.
+
+Location: `lib/rhino`
+
+License: MPL 2.0
+
+### JTar ###
+
+JTar is a simple Java Tar library, that provides an easy way to create and read tar files using IO streams.<br>
+
+Location: `lib/javatar`<br>
+
+License: Public Domain
+
+### Java Tar Package (TarTool) ###
+
+Location: `lib/tartool`<br>
+
+License: Public Domain
+
+### cssmin.js / YUI Compressor ###
+
+A JavaScript port of the CSS minification tool.<br>
+
+Location: `src/lib/cssmin.js`<br>
+
+License: YUI Compressor Copyright License Agreement (revised BSD License)
+
+Trademarks
+----------
+
+CKBuilder and CKEditor are trademarks of CKSource - Frederico Knabben. All other
+brand and product names are trademarks, registered trademarks or service
+marks of their respective holders.
+
+---
+
+Appendix A: The MIT License
+---------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2012-2014, CKSource - Frederico Knabben
+
+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..711b493
--- /dev/null
+++ b/README.md
@@ -0,0 +1,79 @@
+CKBuilder
+=========
+
+This repository contains the source files of CKBuilder, **a command line builder** for [CKEditor](https://github.com/ckeditor/ckeditor-dev).
+
+CKBuilder generates release packages of CKEditor out of its source code. 
+
+### Compiling CKBuilder
+
+You can compile CKBuilder into a single .jar file by running `build_jar.sh` located in the `dev\build` folder. The compiled file will be generated in the `bin` folder.
+[Apache Ant](http://ant.apache.org) is required to run it.
+
+### Using CKBuilder source files
+
+You can generate a CKEditor release version using CKBuilder source files by running `build.sh` available in the `dev\scripts` folder. The release version of CKEditor will be generated in the `release` folder.
+Make sure to download the CKEditor submodule first:
+
+	> git submodule update --init
+
+### Using the default ckbuilder.jar
+
+If you did not compile your own version of `ckbuilder.jar` and all you want to do is to build CKEditor, then there is a simpler way to do this:
+
+ 1. Clone the [CKEditor](https://github.com/ckeditor/ckeditor-dev) repository (hint: there is a "Download ZIP" button on the right side of the page if you don't know how to use git).
+ 2. Inside ckeditor-dev run:
+
+    ```
+    > ./dev/builder/build.sh
+    ```
+
+ 3. That's it - CKBuilder will be downloaded automatically and a "release" version of CKEditor will be built in the new `dev/builder/release/` folder. 
+
+**Note:** CKBuilder which is run by calling ```build.sh``` script will use default ```build-config.js``` which define skin, files to be ignored and plugins. For more information about build-config run builder with ```--build-help``` command.
+ 
+**Note2:** The shell script is designed to run on Mac/Linux. If you are a Windows user, install [Git for Windows](http://msysgit.github.io/), make sure "Git Bash" is checked during the installation process and then run this script using "Git Bash".
+
+### Using a custom ckbuilder.jar
+
+To get the list of all available commands and options, run:
+
+	> java -jar ckbuilder.jar --help
+
+#### Available commands
+
+This is just an overview of available commands. For more details, check the built-in help options.
+
+**--help | --build-help | --full-help**
+
+Display various help information.
+
+**--build**
+
+Build CKEditor, definitely the most frequently used command.
+
+**--build-skin**
+
+Creates a release version of a skin (icons are merged into a single strip image, CSS files are merged and minified, JavaScript files are minified). 
+
+Note: if you want to share your skin with others, do **not** upload the release version of a skin to the [CKEditor addons repository](http://ckeditor.com/addons/skins/all), upload the source version instead.
+
+**--verify-plugin | --verify-skin**
+
+Used by the online builder to verify if a plugin or skin is valid. If you have problems with uploading a skin or a plugin, it might be because this command returned errors.
+
+**--preprocess-core | --preprocess-plugin | --preprocess-skin**
+
+Used by the [online builder](http://ckeditor.com/builder), unless you intend to do a similar service, you don't need it.
+
+**--generate-build-config**
+
+Creates a fresh `build-config.js`.
+
+### Build config
+
+
+
+### License
+
+Licensed under the terms of the MIT License. For full details about license, please check LICENSE.md file.
diff --git a/dev/build/build.xml b/dev/build/build.xml
new file mode 100644
index 0000000..0fa5c12
--- /dev/null
+++ b/dev/build/build.xml
@@ -0,0 +1,114 @@
+<project name="ckbuilder" default="jar" basedir="../../">
+	<property name="bin.dir" location="bin" />
+	<property name="tmp.dir" location="tmp" />
+	<property name="build.dir" location="tmp/build" />
+	<property name="source.dir" location="src" />
+	<property name="json.dir" location="lib/json" />
+	<property name="rhino.jar" location="lib/rhino/js.jar" />
+	<property name="closure.jar" location="lib/closure/compiler.jar" />
+	<property name="commons-cli.jar" location="lib/apache/commons-cli.jar" />
+	<property name="tar.jar" location="lib/javatar/tar.jar" />
+
+	<target name="init">
+		<tstamp />
+		<delete dir="${build.dir}" />
+		<mkdir dir="${build.dir}/ckbuilder/lib" />
+		<mkdir dir="${build.dir}/tools/json" />
+	</target>
+
+	<target name="compile" depends="init" description="compile js">
+		<!-- ckbuilder -->
+		<java fork="yes" classname="org.mozilla.javascript.tools.jsc.Main" failonerror="true">
+			<arg value="-debug" />
+			<arg value="-package" />
+			<arg value="ckbuilder" />
+			<arg value="${source.dir}/ckbuilder.js" />
+			<classpath>
+				<pathelement location="${rhino.jar}"/>
+			</classpath>
+		</java>
+		<java fork="yes" classname="org.mozilla.javascript.tools.jsc.Main" failonerror="true">
+			<arg value="-debug" />
+			<arg value="-package" />
+			<arg value="ckbuilder.lib" />
+			<arg value="${source.dir}/lib/builder.js" />
+			<arg value="${source.dir}/lib/config.js" />
+			<arg value="${source.dir}/lib/controller.js" />
+			<arg value="${source.dir}/lib/css.js" />
+			<arg value="${source.dir}/lib/cssmin.js" />
+			<arg value="${source.dir}/lib/image.js" />
+			<arg value="${source.dir}/lib/io.js" />
+			<arg value="${source.dir}/lib/javascript.js" />
+			<arg value="${source.dir}/lib/lang.js" />
+			<arg value="${source.dir}/lib/plugin.js" />
+			<arg value="${source.dir}/lib/samples.js" />
+			<arg value="${source.dir}/lib/skin.js" />
+			<arg value="${source.dir}/lib/tools.js" />
+			<arg value="${source.dir}/lib/utils.js" />
+			<classpath>
+				<pathelement location="${rhino.jar}"/>
+			</classpath>
+		</java>
+		<!-- json -->
+		<java fork="yes" classname="org.mozilla.javascript.tools.jsc.Main" failonerror="true">
+			<arg value="-debug" />
+			<arg value="-package" />
+			<arg value="tools.json" />
+			<arg value="${json.dir}/json2.js" />
+			<classpath>
+				<pathelement location="${rhino.jar}"/>
+			</classpath>
+		</java>
+	</target>
+
+	<!--
+	During compilation if -package option is used, file is located automatically in a subdirectory based on the package name,
+	that's why for example we have anther tools/json subdirectory inside ot tools/json directory.
+	 -->
+	<target name="copy" depends="compile" description="copy files">
+		<!-- ckbuilder -->
+		<copy file="${source.dir}/ckbuilder/ckbuilder.class" tofile="${build.dir}/ckbuilder/ckbuilder.class" overwrite="true" />
+		<copy todir="${build.dir}/ckbuilder">
+			<fileset dir="${source.dir}/lib/ckbuilder" />
+		</copy>
+		<!-- json -->
+		<copy file="${json.dir}/tools/json/json2.class" tofile="${build.dir}/tools/json/json2.class" overwrite="true" />
+		<!-- Rhino jar file that will be updated -->
+		<copy file="${rhino.jar}" tofile="${tmp.dir}/ckbuilder.jar" overwrite="true" />
+	</target>
+
+	<target name="unpack" depends="copy" description="merge all jar files into one">
+		<unjar src="${tar.jar}" dest="${build.dir}" />
+		<unjar src="${closure.jar}" dest="${build.dir}" />
+		<unjar src="${commons-cli.jar}" dest="${build.dir}" />
+		<!-- Leave mailcap.default and mimetypes.default from META-INF attached to activation.jar -->
+		<delete includeemptydirs="true">
+			<fileset dir="${build.dir}/META-INF" excludes="**/*.default" />
+		</delete>
+	</target>
+
+	<target name="jar" depends="unpack" description="update the jar">
+		<!-- Put everything in ${build.dir} into a jar file -->
+		<jar jarfile="${tmp.dir}/ckbuilder.jar" update="true">
+			<fileset dir="${build.dir}">
+				<include name="**/*" />
+			</fileset>
+			<fileset file="${source.dir}/assets/help.txt" />
+			<fileset file="${source.dir}/assets/help-extra.txt" />
+			<fileset file="${source.dir}/assets/help-build.txt" />
+			<manifest>
+				<attribute name="Main-Class" value="ckbuilder.ckbuilder" />
+			</manifest>
+		</jar>
+	</target>
+
+	<target name="clean" description="clean up">
+		<move file="${tmp.dir}/ckbuilder.jar" tofile="${bin.dir}/ckbuilder.jar" overwrite="true" />
+		<!-- delete all folders created during javascript to java compilation -->
+		<delete dir="${source.dir}/ckbuilder" />
+		<delete dir="${source.dir}/lib/ckbuilder" />
+		<delete dir="${json.dir}/tools" />
+		<delete dir="${tmp.dir}" />
+	</target>
+
+</project>
diff --git a/dev/build/build_jar.bat b/dev/build/build_jar.bat
new file mode 100644
index 0000000..52fc205
--- /dev/null
+++ b/dev/build/build_jar.bat
@@ -0,0 +1,21 @@
+:: Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+:: For licensing, see LICENSE.md
+
+:: Creates CKBuilder jar file (in the "bin" directory).
+
+ at echo off
+
+if "%ANT_HOME%"=="" goto noAntHome
+if "%JAVA_HOME%"=="" goto noJavaHome
+call "%ANT_HOME%\bin\ant.bat" jar
+call "%ANT_HOME%\bin\ant.bat" clean
+goto end
+
+:noAntHome
+echo ANT_HOME environment variable is not set
+goto end
+
+:noJavaHome
+echo JAVA_HOME environment variable is not set
+
+:end
\ No newline at end of file
diff --git a/dev/build/build_jar.sh b/dev/build/build_jar.sh
new file mode 100755
index 0000000..6c5e5c8
--- /dev/null
+++ b/dev/build/build_jar.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+# For licensing, see LICENSE.md
+
+# Creates CKBuilder jar file (in the "bin" directory).
+
+ant jar
+ant clean
\ No newline at end of file
diff --git a/dev/jshint/.jshintrc b/dev/jshint/.jshintrc
new file mode 100644
index 0000000..b036770
--- /dev/null
+++ b/dev/jshint/.jshintrc
@@ -0,0 +1,72 @@
+{
+	"bitwise" : false,
+	// "forin" : true,
+	"immed" : true,
+	"latedef" : true,
+	"nonew" : true,
+	"smarttabs" : true,
+	"trailing" : true,
+	"undef" : true,
+	"unused": true,
+	"rhino" : true,
+	// "curly": true,
+	
+	"eqnull": true,
+	"eqeqeq": true,
+	"noarg": true,
+	// "onevar": true,,
+	"globals": {
+		"CKBuilder": false,
+		"CKBuilderTest": false,
+		"Integer": false,
+		"ImageIO": false,
+		"System": false,
+		"print": false,
+
+		"YAHOO": false,
+
+		"javax": false,
+		"com": false,
+		"org": false,
+		"BufferedImage": false,
+		"BufferedInputStream": false,
+		"BufferedOutputStream": false,
+		"BufferedReader": false,
+		"BufferedWriter": false,
+		"File": false,
+		"FileInputStream": false,
+		"FileOutputStream": false,
+		"InputStreamReader": false,
+		"OutputStreamWriter": false,
+		"Packages": false,
+		"Pattern": false,
+		"StringBuffer": false,
+		"ZipEntry": false,
+		"ZipFile": false,
+		"ZipOutputStream": false,
+
+		// Rhino
+		"importClass": false,
+		"importPackage": false,
+		"Context": false,
+		"CompilerEnvirons": false,
+
+		// Closure Compiler
+		"Compiler": false,
+		"CompilerOptions": false,
+		"CompilationLevel": false,
+		"SourceFile": false,
+
+		// Apache Commons CLI
+		"HelpFormatter": false,
+		"Options": false,
+		"OptionBuilder": false,
+
+		// com.ice.tar
+		"TarEntry": false,
+		"TarGzOutputStream": false,
+
+		// Apache Commons CLI
+		"PosixParser": false
+	}
+}
diff --git a/dev/jshint/jshint.bat b/dev/jshint/jshint.bat
new file mode 100644
index 0000000..f146b3e
--- /dev/null
+++ b/dev/jshint/jshint.bat
@@ -0,0 +1,18 @@
+:: Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+:: For licensing, see LICENSE.md
+
+ at echo Off
+
+:: http://www.jshint.com/platforms/
+:: To run the script install node and jshint:
+:: npm install jshint -g
+
+:: Find files in "src" folder
+SETLOCAL EnableDelayedExpansion
+SET Files=
+FOR /f %%a IN ('dir /b/s ..\src\lib\*.js') do (
+	SET Files=!Files! %%a
+)
+SET Files=!Files! ..\test\test.js
+
+jshint %Files% --show-non-errors
diff --git a/dev/jshint/jshint.sh b/dev/jshint/jshint.sh
new file mode 100755
index 0000000..f02ccfa
--- /dev/null
+++ b/dev/jshint/jshint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+# For licensing, see LICENSE.md
+
+# Runs jshint on source files.
+
+jshint -c .jshintrc --show-non-errors ../../src/*.js ../../src/*/*.js
\ No newline at end of file
diff --git a/dev/scripts/build.bat b/dev/scripts/build.bat
new file mode 100644
index 0000000..f6299f0
--- /dev/null
+++ b/dev/scripts/build.bat
@@ -0,0 +1,25 @@
+:: Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+:: For licensing, see LICENSE.md
+
+:: Builds CKEditor release using the source version of CKBuilder (useful for debugging issues in CKBuilder).
+
+echo ""
+echo "Starting CKBuilder..."
+
+set SCRIPTDIR=%CD%
+
+cd %SCRIPTDIR%\ckeditor
+
+for /f "delims=" %%a in ('git rev-parse --verify --short HEAD') do @set rev=%%a
+
+:: Move to the CKBuilder root folder.
+cd ../../..
+
+java -cp lib/apache/commons-cli.jar;lib/rhino/js.jar;lib/javatar/tar.jar;lib/closure/compiler.jar ^
+org.mozilla.javascript.tools.shell.Main -opt -1 src/ckbuilder.js ^
+--build %SCRIPTDIR%/ckeditor %SCRIPTDIR%/release --build-config %SCRIPTDIR%/ckeditor/dev/builder/build-config.js --overwrite --version=DEV --revision=%rev% %*
+
+cd %SCRIPTDIR%
+
+echo ""
+echo "Release created in the \"release\" directory."
diff --git a/dev/scripts/build.sh b/dev/scripts/build.sh
new file mode 100755
index 0000000..588301e
--- /dev/null
+++ b/dev/scripts/build.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+# For licensing, see LICENSE.md
+
+# Builds CKEditor release using the source version of CKBuilder (useful for debugging issues in CKBuilder).
+
+echo ""
+echo "Starting CKBuilder..."
+
+SCRIPTDIR=$(dirname $0)
+
+# Move to the script directory.
+cd $SCRIPTDIR/ckeditor
+
+rev=`git rev-parse --verify --short HEAD`
+
+# Move to the CKBuilder root folder.
+cd ../../..
+
+java -cp lib/apache/commons-cli.jar:lib/rhino/js.jar:lib/javatar/tar.jar:lib/closure/compiler.jar \
+org.mozilla.javascript.tools.shell.Main -opt -1 src/ckbuilder.js \
+--build dev/scripts/ckeditor dev/scripts/release --build-config dev/scripts/ckeditor/dev/builder/build-config.js --overwrite --version=DEV --revision=$rev $@
+
+cd $SCRIPTDIR
+
+echo ""
+echo "Release created in the \"release\" directory."
diff --git a/docs/build.bat b/docs/build.bat
new file mode 100644
index 0000000..0429a25
--- /dev/null
+++ b/docs/build.bat
@@ -0,0 +1,8 @@
+:: Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+:: For licensing, see LICENSE.md
+
+:: Create internal API documentation
+
+#!/bin/sh
+
+jsduck
\ No newline at end of file
diff --git a/docs/build.sh b/docs/build.sh
new file mode 100755
index 0000000..aab15ab
--- /dev/null
+++ b/docs/build.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+# For licensing, see LICENSE.md
+
+# Create internal API documentation
+
+jsduck
\ No newline at end of file
diff --git a/docs/jsduck.json b/docs/jsduck.json
new file mode 100644
index 0000000..cec01d4
--- /dev/null
+++ b/docs/jsduck.json
@@ -0,0 +1,9 @@
+{
+	"--title": "CKBuilder Source Code Documentation",
+	"--footer": "Copyright © 2012-2014, <a href=\"http://cksource.com\" style=\"color:#085585\">CKSource</a> - Frederico Knabben. All rights reserved. | Generated with <a href=\"https://github.com/senchalabs/jsduck\">JSDuck</a>.",
+	"--warnings": "-nodoc",
+	"--output": "./output",
+	"--": [
+		"../src"
+	]
+}
\ No newline at end of file
diff --git a/lib/apache/LICENSE.txt b/lib/apache/LICENSE.txt
new file mode 100644
index 0000000..57bc88a
--- /dev/null
+++ b/lib/apache/LICENSE.txt
@@ -0,0 +1,202 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/lib/closure/COPYING b/lib/closure/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/lib/closure/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/lib/closure/README.md b/lib/closure/README.md
new file mode 100644
index 0000000..30fd7c0
--- /dev/null
+++ b/lib/closure/README.md
@@ -0,0 +1,507 @@
+# [Google Closure Compiler](https://developers.google.com/closure/compiler/)
+
+[![Build Status](https://travis-ci.org/google/closure-compiler.svg?branch=master)](https://travis-ci.org/google/closure-compiler)
+
+The [Closure Compiler](https://developers.google.com/closure/compiler/) is a tool for making JavaScript download and run faster. It is a true compiler for JavaScript. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls.
+
+## Getting Started
+ * [Download the latest version](http://dl.google.com/closure-compiler/compiler-latest.zip) ([Release details here](https://github.com/google/closure-compiler/wiki/Releases))
+ * [Download a specific version](https://github.com/google/closure-compiler/wiki/Binary-Downloads). Also available via:
+   - [Maven](https://github.com/google/closure-compiler/wiki/Maven)
+   - [NPM](https://www.npmjs.com/package/google-closure-compiler)
+ * See the [Google Developers Site](https://developers.google.com/closure/compiler/docs/gettingstarted_app) for documentation including instructions for running the compiler from the command line.
+
+## Options for Getting Help
+1. Post in the [Closure Compiler Discuss Group](https://groups.google.com/forum/#!forum/closure-compiler-discuss)
+2. Ask a question on [Stack Overflow](http://stackoverflow.com/questions/tagged/google-closure-compiler)
+3. Consult the [FAQ](https://github.com/google/closure-compiler/wiki/FAQ)
+
+## Building it Yourself
+
+Note: The Closure Compiler requires [Java 7 or higher](http://www.java.com/).
+
+### Using [Ant](http://ant.apache.org/)
+
+1. Download the [Ant build tool](http://ant.apache.org/bindownload.cgi).
+
+2. At the root of the source tree, there is an Ant file named ```build.xml```.
+   To use it, navigate to the same directory and type the command
+
+    ```
+    ant jar
+    ```
+
+    This will produce a jar file called ```build/compiler.jar```.
+
+### Using [Eclipse](http://www.eclipse.org/)
+
+1. Download and open the [Eclipse IDE](http://www.eclipse.org/).
+2. Navigate to ```File > New > Project ...``` and create a Java Project. Give
+   the project a name.
+3. Select ```Create project from existing source``` and choose the root of the
+   checked-out source tree as the existing directory.
+3. Navigate to the ```build.xml``` file. You will see all the build rules in
+   the Outline pane. Run the ```jar``` rule to build the compiler in
+   ```build/compiler.jar```.
+
+## Running
+
+On the command line, at the root of this project, type
+
+```
+java -jar build/compiler.jar
+```
+
+This starts the compiler in interactive mode. Type
+
+```javascript
+var x = 17 + 25;
+```
+
+then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux)
+and "Enter" again. The Compiler will respond:
+
+```javascript
+var x=42;
+```
+
+The Closure Compiler has many options for reading input from a file, writing
+output to a file, checking your code, and running optimizations. To learn more,
+type
+
+```
+java -jar compiler.jar --help
+```
+
+More detailed information about running the Closure Compiler is available in the
+[documentation](http://code.google.com/closure/compiler/docs/gettingstarted_app.html).
+
+## Compiling Multiple Scripts
+
+If you have multiple scripts, you should compile them all together with one
+compile command.
+
+```bash
+java -jar compiler.jar --js_output_file=out.js in1.js in2.js in3.js ...
+```
+
+You can also use minimatch-style globs.
+
+```bash
+# Recursively include all js files in subdirs
+java -jar compiler.jar --js_output_file=out.js 'src/**.js'
+
+# Recursively include all js files in subdirs, exclusing test files.
+# Use single-quotes, so that bash doesn't try to expand the '!'
+java -jar compiler.jar --js_output_file=out.js 'src/**.js' '!**_test.js'
+```
+
+The Closure Compiler will concatenate the files in the order they're passed at
+the command line.
+
+If you're using globs or many files, you may start to run into
+problems with managing dependencies between scripts. In this case, you should
+use the [Closure Library](https://developers.google.com/closure/library/). It
+contains functions for enforcing dependencies between scripts, and Closure Compiler
+will re-order the inputs automatically.
+
+## How to Contribute
+### Reporting a bug
+1. First make sure that it is really a bug and not simply the way that Closure Compiler works (especially true for ADVANCED_OPTIMIZATIONS).
+ * Check the [official documentation](https://developers.google.com/closure/compiler/)
+ * Consult the [FAQ](https://github.com/google/closure-compiler/wiki/FAQ)
+ * Search on [Stack Overflow](http://stackoverflow.com/questions/tagged/google-closure-compiler) and in the [Closure Compiler Discuss Group](https://groups.google.com/forum/#!forum/closure-compiler-discuss)
+2. If you still think you have found a bug, make sure someone hasn't already reported it. See the list of [known issues](https://github.com/google/closure-compiler/issues).
+3. If it hasn't been reported yet, post a new issue. Make sure to add enough detail so that the bug can be recreated. The smaller the reproduction code, the better.
+
+### Suggesting a Feature
+1. Consult the [FAQ](https://github.com/google/closure-compiler/wiki/FAQ) to make sure that the behaviour you would like isn't specifically excluded (such as string inlining).
+2. Make sure someone hasn't requested the same thing. See the list of [known issues](https://github.com/google/closure-compiler/issues).
+3. Read up on [what type of feature requests are accepted](https://github.com/google/closure-compiler/wiki/FAQ#how-do-i-submit-a-feature-request-for-a-new-type-of-optimization).
+4. Submit your reqest as an issue.
+
+### Submitting patches
+1. All contributors must sign a contributor license agreement (CLA).
+   A CLA basically says that you own the rights to any code you contribute,
+   and that you give us permission to use that code in Closure Compiler.
+   You maintain the copyright on that code.
+   If you own all the rights to your code, you can fill out an
+   [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
+   If your employer has any rights to your code, then they also need to fill out
+   a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
+   If you don't know if your employer has any rights to your code, you should
+   ask before signing anything.
+   By default, anyone with an @google.com email address already has a CLA
+   signed for them.
+2. To make sure your changes are of the type that will be accepted, ask about your patch on the [Closure Compiler Discuss Group](https://groups.google.com/forum/#!forum/closure-compiler-discuss)
+3. Fork the repository.
+4. Make your changes.
+5. Submit a pull request for your changes. A project developer will review your work and then merge your request into the project.
+
+## Closure Compiler License
+
+Copyright 2009 The Closure Compiler Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+## Dependency Licenses
+
+### Rhino
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td>
+      <code>src/com/google/javascript/rhino</code>, <code>test/com/google/javascript/rhino</code>
+    </td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>http://www.mozilla.org/rhino</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>1.5R3, with heavy modifications</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Netscape Public License and MPL / GPL dual license</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>A partial copy of Mozilla Rhino. Mozilla Rhino is an
+implementation of JavaScript for the JVM.  The JavaScript
+parse tree data structures were extracted and modified
+significantly for use by Google's JavaScript compiler.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>The packages have been renamespaced. All code not
+relevant to the parse tree has been removed. A JsDoc parser and static typing
+system have been added.</td>
+  </tr>
+</table>
+
+### Args4j
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/args4j.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>https://args4j.dev.java.net/</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>2.0.26</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>MIT</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>args4j is a small Java class library that makes it easy to parse command line
+options/arguments in your CUI application.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### Guava Libraries
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/guava.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>https://github.com/google/guava</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>18.0</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Apache License 2.0</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>Google's core Java libraries.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### JSR 305
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/jsr305.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>http://code.google.com/p/jsr-305/</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>svn revision 47</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>BSD License</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>Annotations for software defect detection.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### JUnit
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/junit.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>http://sourceforge.net/projects/junit/</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>4.11</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Common Public License 1.0</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>A framework for writing and running automated tests in Java.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### Protocol Buffers
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/protobuf-java.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>http://code.google.com/p/protobuf/</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>2.5.0</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>New BSD License</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>Supporting libraries for protocol buffers,
+an encoding of structured data.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### Truth
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/truth.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>https://github.com/google/truth</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>0.24</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Apache License 2.0</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>Assertion/Proposition framework for Java unit tests</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### Ant
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td>
+      <code>lib/ant.jar</code>, <code>lib/ant-launcher.jar</code>
+    </td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>http://ant.apache.org/bindownload.cgi</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>1.8.1</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Apache License 2.0</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>Ant is a Java based build tool. In theory it is kind of like "make"
+without make's wrinkles and with the full portability of pure java code.</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### GSON
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>lib/gson.jar</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>https://code.google.com/p/google-gson/</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>2.2.4</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Apache license 2.0</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>A Java library to convert JSON to Java objects and vice-versa</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>None</td>
+  </tr>
+</table>
+
+### Node.js Closure Compiler Externs
+
+<table>
+  <tr>
+    <td>Code Path</td>
+    <td><code>contrib/nodejs</code></td>
+  </tr>
+
+  <tr>
+    <td>URL</td>
+    <td>https://github.com/dcodeIO/node.js-closure-compiler-externs</td>
+  </tr>
+
+  <tr>
+    <td>Version</td>
+    <td>e891b4fbcf5f466cc4307b0fa842a7d8163a073a</td>
+  </tr>
+
+  <tr>
+    <td>License</td>
+    <td>Apache 2.0 license</td>
+  </tr>
+
+  <tr>
+    <td>Description</td>
+    <td>Type contracts for NodeJS APIs</td>
+  </tr>
+
+  <tr>
+    <td>Local Modifications</td>
+    <td>Substantial changes to make them compatible with NpmCommandLineRunner.</td>
+  </tr>
+</table>
diff --git a/lib/javatar/LICENSE.txt b/lib/javatar/LICENSE.txt
new file mode 100644
index 0000000..95fabaf
--- /dev/null
+++ b/lib/javatar/LICENSE.txt
@@ -0,0 +1,15 @@
+
+    ----  Public Domain  ----
+
+This work was autored by Timothy Gerard Endres, time at gjt.org.
+
+This work has been placed into the public domain.
+
+You are free to use this work in any way you wish.
+
+DISCLAIMER
+
+THIS SOFTWARE IS PROVIDED AS-IS, WITH ABSOLUTELY NO WARRANTY.
+YOU ASSUME ALL RESPONSIBILITY FOR ANY AND ALL CONSEQUENCES
+THAT MAY RESULT FROM THE USE OF THIS SOFTWARE!
+
diff --git a/lib/rhino/LICENSE.txt b/lib/rhino/LICENSE.txt
new file mode 100644
index 0000000..c0e7c21
--- /dev/null
+++ b/lib/rhino/LICENSE.txt
@@ -0,0 +1,375 @@
+The majority of Rhino is licensed under the MPL 2.0:
+
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/src/assets/help-build.txt b/src/assets/help-build.txt
new file mode 100644
index 0000000..ae25f3e
--- /dev/null
+++ b/src/assets/help-build.txt
@@ -0,0 +1,57 @@
+The build configuration is a critical file required to build the release.
+
+A sample build configuration (build-config.js) looks as follows:
+
+
+var CKBUILDER_CONFIG = {
+  skin : 'kama',
+  ignore : [
+      '_dev',
+      '.gitignore',
+      '.gitattributes'
+  ],
+  plugins :
+  {
+      a11yhelp : 1,
+      about : 1,
+     // more plugins
+  },
+  js : [
+     '/path/to/file1.js,start'
+     '/path/to/file2.js,aftercore',
+     '/path/to/file3.js,end',
+     '/path/to/file4.js',
+  ]
+}
+
+
+[PROPERTIES]
+    skin         The name of the default skin.
+    ignore       The set of files/folders to ignore.
+    plugins      The list of plugins to include in the release.
+    js           An optional array of javascript files to append at the end
+                 of ckeditor.js.
+                 It is possible to specify precisely, where the file should
+                 be added to ckeditor.js. Add a colon and one of the
+                 properties: start, aftercore, end.
+                    start       prepend javascript files at the beginning of
+                                ckeditor.js
+                    aftercore   add javascript files in the middle of
+                                ckeditor.js, before plugin files
+                    end         add javascript files at the end (default).
+
+
+In oder to generate the base build configuration file (build-config.js), run the
+following command:
+
+    java -jar ckbuilder.jar --generate-build-config ckeditor-dev
+
+("ckeditor-dev" is the name of a folder with source files).
+
+After commenting out plugins that are not needed, to build the release run:
+
+    java -jar ckbuilder.jar --build ckeditor-dev release --version 4.0
+
+Need more help? Run:
+
+    java -jar ckbuilder.jar --help
diff --git a/src/assets/help-extra.txt b/src/assets/help-extra.txt
new file mode 100644
index 0000000..6127dfb
--- /dev/null
+++ b/src/assets/help-extra.txt
@@ -0,0 +1,92 @@
+** ADVANCED COMMANDS **
+If you're unsure what these commands are for, just ignore them.
+
+[TASK: preprocess core files]
+SYNOPSIS:
+  --preprocess-core SRC DST (options...)
+      [OPTIONS]
+      SRC  source folder
+      DST  destination folder
+      [OPTIONS]
+      --build-config <FILE>     path to the file
+      --version <NUMBER>        version number
+      --revision <NUMBER>       revision number
+      --leave-js-unminified     leave javascript files as is:
+                                merge, but do not minify.
+      --no-ie-checks            turn off warnings about syntax errors on
+                                Internet Explorer, like trailing commas
+
+DESCRIPTION:
+  Preprocess core files. Creates ckeditor.js with no plugins enabled.
+  Converts language files into "intermediate" format.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --preprocess-core ckeditor-dev output
+
+[TASK: preprocess plugin]
+SYNOPSIS:
+  --preprocess-plugin SRC DST (options...)
+      [OPTIONS]
+      SRC  source folder
+      DST  destination folder
+      [OPTIONS]
+      --build-config <FILE>     path to the file
+      --leave-js-unminified     leave javascript files as is
+      --no-ie-checks            turn off warnings about syntax errors on
+                                Internet Explorer, like trailing commas
+
+DESCRIPTION:
+  Preprocess plugin folder. Minifies JavaScript files.
+  Fixes missing entries and converts language files into "intermediate" format.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --preprocess-plugin devtools output
+
+[TASK: preprocess skin]
+SYNOPSIS:
+  --preprocess-skin SRC DST (options...)
+      [OPTIONS]
+      SRC  source folder
+      DST  destination folder
+      [OPTIONS]
+      --build-config <FILE>     path to the file
+      --leave-js-unminified     leave javascript files as is
+      --no-ie-checks            turn off warnings about syntax errors on
+                                Internet Explorer, like trailing commas
+
+DESCRIPTION:
+  Preprocess skin folder. Minifies JavaScript files.
+  Merges CSS files.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --preprocess-skin kama output
+
+[TASK: verify plugin]
+SYNOPSIS:
+  --verify-plugin SRC
+      [OPTIONS]
+      SRC  source folder (or zip file)
+      [OPTIONS]
+      --name         the name of the plugin to be found in plugin.js
+
+DESCRIPTION:
+  Verifies the plugin folder / zip file with a plugin.
+  Checks for plugin.js and tries to parse JS files looking for syntax errors.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --verify-plugin devtools
+
+[TASK: verify skin]
+SYNOPSIS:
+  --verify-skin SRC
+      [OPTIONS]
+      SRC  source folder (or zip file)
+      [OPTIONS]
+      --name         the name of the skin to be found in skin.js
+
+DESCRIPTION:
+  Verifies the skin folder / zip file with a skin.
+  Checks for skin.js and tries to parse JS files looking for syntax errors.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --verify-skin kama
diff --git a/src/assets/help.txt b/src/assets/help.txt
new file mode 100644
index 0000000..dc8c21b
--- /dev/null
+++ b/src/assets/help.txt
@@ -0,0 +1,75 @@
+CKBuilder 2.3.0
+
+USAGE: java -jar ckbuilder.jar
+
+[TASK: build release]
+SYNOPSIS:
+  --build SRC DST (options...)
+      SRC  source folder
+      DST  destination folder
+      [OPTIONS]
+      --build-config <FILE>     path to the file
+      --version <NUMBER>        version number
+      --revision <NUMBER>       revision number
+      --overwrite               overwrite target folder if exists
+      -s,--skip-omitted-in-build-config
+                                exclude from release all plugins/skins
+                                that are not specified in build-config
+      --leave-js-unminified     leave javascript files as is:
+                                merge, but do not minify.
+      --leave-css-unminified    leave CSS files as is:
+                                merge, but do not minify.
+      --no-ie-checks            turn off warnings about syntax errors on
+                                Internet Explorer, like trailing commas
+      --core                    create only the core file (ckeditor.js)
+      --no-zip                  do not create zip file
+      --no-tar                  do not create tar.gz file
+      --commercial              builds a package with commercial license
+
+DESCRIPTION:
+  Creates CKEditor build in DST folder using source files from SRC folder.
+  The build configuration file (build-config.js), which is required in order to
+  create the build package, contains the list of plugins to include.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --build ckeditor-dev release --version 4.0
+
+[TASK: build skin]
+SYNOPSIS:
+  --build-skin SRC DST (options...)
+      SRC  source folder
+      DST  destination folder
+      [OPTIONS]
+      --overwrite               overwrite target folder if exists
+      --leave-js-unminified     leave javascript files as is:
+                                merge, but do not minify.
+      --leave-css-unminified    leave CSS files as is:
+                                merge, but do not minify.
+      --no-ie-checks            turn off warnings about syntax errors on
+                                Internet Explorer, like trailing commas
+
+DESCRIPTION:
+  Creates a release version of a skin.
+
+EXAMPLE:
+  java -jar ckbuilder.jar --build-skin skins/myskin target_dir
+
+[TASK: create build configuration file]
+SYNOPSIS:
+  --generate-build-config SRC (options...)
+      SRC  source folder
+      [OPTIONS]
+      --build-config <FILE>     path to the new file
+
+DESCRIPTION:
+  Creates build configuration file (default: build-config.js).
+
+EXAMPLE:
+  java -jar ckbuilder.jar --generate-build-config ckeditor-dev
+
+[OTHER OPTIONS]
+
+  -d,--debug-level <LEVEL> debug level (0, 1, 2).
+  --help                   prints help information
+  --build-help             prints help information about build configuration
+  --full-help              prints help information about all advanced commands
diff --git a/src/ckbuilder.js b/src/ckbuilder.js
new file mode 100644
index 0000000..8093a98
--- /dev/null
+++ b/src/ckbuilder.js
@@ -0,0 +1,113 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importPackage( org.mozilla.javascript );
+importClass( java.lang.System );
+importClass( java.lang.Integer );
+
+/**
+ * @class java.io.File
+ *
+ * Check out [official java.io.File documentation][1]  for more.
+ *
+ * [1]: http://docs.oracle.com/javase/7/docs/api/java/io/File.html
+ */
+
+/**
+ * @class java.io.OutputStream
+ *
+ * Check out [official java.io.File documentation][1]  for more.
+ *
+ * [1]: http://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html
+ */
+
+/**
+ * @class
+ *
+ * Main file which is run from Rhino. Responsible for load scripts and run controller.
+ */
+this.CKBuilder = ( function() {
+	var isMinified = true;
+	try {
+		java.lang.Class.forName( "ckbuilder.ckbuilder" );
+	} catch ( e ) {
+		isMinified = false;
+	}
+	var now = new Date();
+	var timestamp = Integer.toString( now.getUTCFullYear() % 1000, 36 ) + Integer.toString( now.getUTCMonth(), 36 ) + Integer.toString( now.getUTCDate(), 36 ) + Integer.toString( now.getUTCHours(), 36 );
+	timestamp = timestamp.toUpperCase();
+
+	return {
+		isMinified : isMinified,
+		options : {
+			debug : 0,
+			all : true,
+			overwrite : false,
+			version : 'DEV',
+			revision : 0,
+			timestamp : timestamp
+		},
+		error : function( msg ) {
+			print( 'ERROR:' );
+			print( msg );
+			print( '' );
+			// quit() does not work when compiled.
+			System.exit( 1000 );
+		},
+		load : function( className ) {
+			if ( isMinified )
+				loadClass( className );
+			else
+			{
+				var path = className;
+
+				if ( path.indexOf( "ckbuilder." ) === 0 )
+					path = path.replace( /^ckbuilder\./, "src/" );
+				else
+					path = path.replace( /^tools\./, 'lib/' );
+
+				path = path.replace( /\./g, '/' ) + '.js';
+				load( path );
+			}
+		}
+	};
+} )();
+
+/* jshint ignore:start */
+function print( arg ) {
+	if ( arg === undefined )
+		arg = 'undefined';
+
+	if ( arg === null)
+		arg = 'null';
+
+	System.out.println( arg );
+}
+/* jshint ignore:end */
+
+CKBuilder.DEFAULT_SKIN = 'moono';
+CKBuilder.DEFAULT_LANGUAGE = 'en';
+
+CKBuilder.load( 'tools.json.json2' );
+
+CKBuilder.load( 'ckbuilder.lib.controller' );
+CKBuilder.load( 'ckbuilder.lib.io' );
+CKBuilder.load( 'ckbuilder.lib.css' );
+CKBuilder.load( 'ckbuilder.lib.cssmin' );
+CKBuilder.load( 'ckbuilder.lib.image' );
+CKBuilder.load( 'ckbuilder.lib.lang' );
+CKBuilder.load( 'ckbuilder.lib.javascript' );
+CKBuilder.load( 'ckbuilder.lib.config' );
+CKBuilder.load( 'ckbuilder.lib.samples' );
+CKBuilder.load( 'ckbuilder.lib.plugin' );
+CKBuilder.load( 'ckbuilder.lib.skin' );
+CKBuilder.load( 'ckbuilder.lib.utils' );
+CKBuilder.load( 'ckbuilder.lib.tools' );
+CKBuilder.load( 'ckbuilder.lib.builder' );
+
+if ( typeof CKBuilderTest === 'undefined' ) {
+	var controller = new CKBuilder.Controller();
+	controller.run( arguments );
+}
diff --git a/src/lib/builder.js b/src/lib/builder.js
new file mode 100644
index 0000000..568176c
--- /dev/null
+++ b/src/lib/builder.js
@@ -0,0 +1,753 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+/**
+ * Responsible for preprocess core, generate build and generate core.
+ *
+ * @class
+ * @param {String} srcDir
+ * @param {String} dstDir
+ */
+CKBuilder.builder = function( srcDir, dstDir ) {
+	/**
+	 * Build configuration.
+	 *
+	 * @property {Object} config
+	 */
+	var config = {};
+
+	/**
+	 * The main target skin file.
+	 *
+	 * @type {java.io.File}
+	 */
+	var targetSkinFile;
+
+	/**
+	 * The main source skin file.
+	 *
+	 * @type {java.io.File}
+	 */
+	var sourceSkinFile;
+
+	/**
+	 * The main language file.
+	 *
+	 * @type {java.io.File}
+	 */
+	var languageFile;
+
+	/**
+	 * The list of "core" scripts.
+	 * "Helper" variable used to mark script as loaded in "coreScriptsSorted".
+	 *
+	 * @type {Object}
+	 */
+	var coreScripts = {};
+
+	/**
+	 * The list of "core" scripts, sorted by the loading order.
+	 *
+	 * @type {Array}
+	 */
+	var coreScriptsSorted = [];
+
+	/**
+	 * The hash map with the list of plugins to include in ckeditor.js.
+	 * The key is the name of the plugin.
+	 * The value indicates whether the plugin is included in ckeditor.js (true).
+	 *
+	 * @type {Object}
+	 */
+	var pluginNames = {};
+
+	/**
+	 * The list of plugin files to include in ckeditor.js, sorted by the loading order.
+	 *
+	 * @type {Object}
+	 */
+	var sourcePluginFilesSorted = [];
+
+	/**
+	 * The list of plugin files to include in ckeditor.js, sorted by the loading order.
+	 *
+	 * @type {Object}
+	 */
+	var targetPluginFilesSorted = [];
+
+	/**
+	 * Paths to extra files to be included in ckeditor.js, defined by the "js" property.
+	 *
+	 * @type {Object}
+	 */
+	var extraCoreJavaScriptFiles = null;
+
+	/**
+	 * The extra code to be included in ckeditor.js, defined by the "js" property,
+	 *
+	 * @type {Object}
+	 */
+	var extraCoreJavaScriptCode = {};
+
+	/**
+	 * The list of plugin names to include in ckeditor.js, sorted by the loading order.
+	 *
+	 * @type {Array}
+	 */
+	var pluginNamesSorted = [];
+
+	/**
+	 * The "scripts" definition in the loader file.
+	 *
+	 * @type {Array}
+	 */
+	var loaderScripts;
+
+	/**
+	 * Source location with CKEditor source files.
+	 *
+	 * @type {java.io.File}
+	 */
+	var sourceLocation = new File( srcDir );
+
+	/**
+	 * Target location where the release will be built.
+	 *
+	 * @type {java.io.File}
+	 */
+	var targetLocation = new File( dstDir, 'ckeditor' );
+
+	/**
+	 * Checks for some required files/folders and throws an error in case of missing items.
+	 */
+	function validateSourceFolder() {
+		if ( !sourceLocation.exists() )
+			CKBuilder.error( 'Source folder does not exist: ' + srcDir );
+		if ( !sourceLocation.isDirectory() )
+			CKBuilder.error( 'Source folder is not a directory: ' + srcDir );
+		var requiredFiles = [
+			'lang/' + ( config.language || CKBuilder.DEFAULT_LANGUAGE ) + '.js',
+			'core/loader.js',
+			'ckeditor.js',
+			'lang',
+			'plugins'
+		];
+		if ( config.skin )
+			requiredFiles.push( 'skins/' + config.skin + '/skin.js' );
+
+		for ( var i = 0; i < requiredFiles.length; i++ ) {
+			var file = new File( sourceLocation, requiredFiles[ i ] );
+			if ( !file.exists() )
+				throw( 'The source directory is not invalid. The following file is missing: ' + file.getAbsolutePath() );
+		}
+	}
+
+	/**
+	 * Initializes all variables required during the build process.
+	 */
+	function init() {
+		if ( config.skin ) {
+			sourceSkinFile = new File( sourceLocation, 'skins/' + ( config.skin ) + '/skin.js' );
+			targetSkinFile = new File( targetLocation, 'skins/' + ( config.skin ) + '/skin.js' );
+		}
+		languageFile = new File( targetLocation, 'lang/' + ( config.language || CKBuilder.DEFAULT_LANGUAGE ) + '.js' );
+		var loaderFile = new File( sourceLocation, 'core/loader.js' );
+
+		/*
+		 * Execute script loader.js in core directory and read
+		 * CKEDITOR.loader.scripts property
+		 */
+		loaderScripts = ( function() {
+			var code = 'var CKEDITOR = { basePath : \'/ckeditor/\' }; ' + CKBuilder.io.readFile( loaderFile ),
+				cx = Context.enter(),
+				scope = cx.initStandardObjects();
+
+			try {
+				cx.evaluateString( scope, code, loaderFile.getName(), 1, null );
+				return scope.CKEDITOR.loader.scripts;
+			} catch ( e ) {
+				throw( 'Invalid JavaScript file: ' + loaderFile.getAbsolutePath() + '.\nError: ' + e.message );
+			}
+		}() );
+
+		if ( !loaderScripts )
+			throw( 'Unable to get required scripts from loader: ' + loaderFile.getAbsolutePath() );
+
+		if ( CKBuilder.options.debug )
+			print( 'Reading core files from loader' );
+
+		getCoreScripts( 'ckeditor' );
+		getCoreScripts( '_bootstrap' );
+
+		if ( CKBuilder.options.debug )
+			print( 'Checking plugins dependency' );
+
+		findAllRequiredPlugins( getPluginsFromBuildConfig() );
+	}
+
+	/**
+	 * Generates arrays with the list of core files to include.
+	 *
+	 * @param {String} scriptName
+	 */
+	function getCoreScripts( scriptName ) {
+		// Check if the script has already been loaded.
+		if ( scriptName === 'ckeditor_base' || scriptName in coreScripts )
+			return;
+
+		// Get the script dependencies list.
+		var dependencies = loaderScripts[ scriptName ];
+		if ( !dependencies )
+			throw( 'The script name"' + scriptName + '" is not defined.' );
+
+		// Mark as loaded
+		coreScripts[ scriptName ] = true;
+
+		// Load all dependencies first.
+		for ( var i = 0; i < dependencies.length; i++ )
+			getCoreScripts( dependencies[ i ] );
+
+		if ( CKBuilder.options.debug > 1 )
+			print( 'Found core script to load: core/' + scriptName + '.js' );
+
+		var file = new File( sourceLocation, 'core/' + scriptName + '.js' );
+		coreScriptsSorted.push( file );
+	}
+
+	/**
+	 * Returns an array with plugins enabled in the builder configuration file.
+	 *
+	 * @returns {Array}
+	 */
+	function getPluginsFromBuildConfig() {
+		var plugins = [];
+
+		for ( var plugin in config.plugins ) {
+			if ( config.plugins[ plugin ] )
+				plugins.push( plugin );
+		}
+
+		return plugins;
+	}
+
+	/**
+	 * Generates arrays with the list of all plugins to include.
+	 *
+	 * @param {Array} plugins
+	 */
+	function findAllRequiredPlugins( plugins ) {
+		var pluginFile;
+
+		for ( var i = 0; i < plugins.length; i++ ) {
+			if ( plugins[ i ] in pluginNames )
+				continue;
+
+			pluginFile = new File( sourceLocation, 'plugins/' + plugins[ i ] + '/plugin.js' );
+			if ( !pluginFile.exists() )
+				throw( 'Plugin does not exist: ' + plugins[ i ] + '. Unable to open: ' + pluginFile.getPath() );
+			else {
+				var required = CKBuilder.plugin.getRequiredPlugins( pluginFile );
+				if ( required.length ) {
+					pluginNames[ plugins[ i ] ] = false;
+					findAllRequiredPlugins( required );
+				}
+
+				// Previous call to findAllRequiredPlugins() could have added our plugin to the array.
+				if ( !( plugins[ i ] in pluginNames ) || !pluginNames[ plugins[ i ] ] ) {
+					pluginNames[ plugins[ i ] ] = true;
+					sourcePluginFilesSorted.push( File( sourceLocation, 'plugins/' + plugins[ i ] + '/plugin.js' ) );
+					targetPluginFilesSorted.push( File( targetLocation, 'plugins/' + plugins[ i ] + '/plugin.js' ) );
+					pluginNamesSorted.push( plugins[ i ] );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Delete unused files in the destination folder.
+	 */
+	function deleteUnusedFiles() {
+		CKBuilder.io.deleteDirectory( new File( targetLocation, 'core' ) );
+
+		for ( var i = 0; i < targetPluginFilesSorted.length; i++ ) {
+			var empty = true,
+				parentDir = targetPluginFilesSorted[ i ].getParentFile(),
+				dirList = parentDir.list();
+
+			for ( var j = 0; j < dirList.length; j++ ) {
+				if ( String( dirList[ j ] ) === 'icons' )
+					CKBuilder.io.deleteDirectory( new File( parentDir, dirList[ j ] ) ); else if ( String( dirList[ j ] ) === 'lang' )
+					CKBuilder.io.deleteDirectory( new File( parentDir, dirList[ j ] ) ); else if ( String( dirList[ j ] ) === 'plugin.js' )
+					CKBuilder.io.deleteFile( new File( parentDir, dirList[ j ] ) ); else
+					empty = false;
+			}
+
+			if ( empty )
+				CKBuilder.io.deleteDirectory( parentDir );
+		}
+	}
+
+	/**
+	 * Remove unused plugins (not included in the build configuration file) from the plugins folder.
+	 * Executed only when skip-omitted-in-build-config is enabled.
+	 */
+	function filterPluginFolders() {
+		var pluginsFolder = new File( targetLocation, 'plugins' );
+		if ( !pluginsFolder.exists() )
+			return;
+		var dirList = pluginsFolder.list();
+		for ( var i = 0; i < dirList.length; i++ ) {
+			if ( !pluginNames[ dirList[ i ] ] ) {
+				if ( CKBuilder.options.debug > 1 )
+					print( 'Removing unused plugin: ' + dirList[ i ] );
+				CKBuilder.io.deleteDirectory( File( pluginsFolder, dirList[ i ] ) );
+			}
+		}
+	}
+
+	/**
+	 * Remove unused skins (not included in the build configuation file) from the skins folder.
+	 * Executed only when skip-omitted-in-build-config is enabled.
+	 * @param {String} selectedSkin
+	 */
+	function filterSkinsFolders( selectedSkin ) {
+		var skinsFolder = new File( targetLocation, 'skins' );
+		if ( !skinsFolder.exists() )
+			return;
+
+		var dirList = skinsFolder.list();
+		for ( var i = 0; i < dirList.length; i++ ) {
+			if ( String( dirList[ i ] ) !== selectedSkin ) {
+				if ( CKBuilder.options.debug > 1 )
+					print( 'Removing unused skin: ' + dirList[ i ] );
+				CKBuilder.io.deleteDirectory( File( skinsFolder, dirList[ i ] ) );
+			}
+		}
+	}
+
+	/**
+	 * Build skins in the skins folder.
+	 * @private
+	 */
+	function buildSkins() {
+		var skinsLocation = new File( targetLocation, 'skins' ),
+			pluginsLocation = new File( sourceLocation, 'plugins' );
+
+		if ( !skinsLocation.exists() )
+			return;
+
+		var dirList = skinsLocation.list();
+		for ( var i = 0; i < dirList.length; i++ ) {
+			var skinLocation = new File( skinsLocation, dirList[ i ] );
+			if ( skinLocation.isDirectory() ) {
+				if ( CKBuilder.options.debug > 1 )
+					print( 'Building skin: ' + dirList[ i ] );
+
+				var outputFile = new File( skinLocation, 'icons.png' ),
+					outputCssFile = new File( skinLocation, 'editor.css' );
+				CKBuilder.image.createFullSprite( pluginsLocation, skinLocation, outputFile, outputCssFile, pluginNamesSorted );
+
+				outputFile = new File( skinLocation, 'icons_hidpi.png' );
+				CKBuilder.image.createFullSprite( pluginsLocation, skinLocation, outputFile, outputCssFile, pluginNamesSorted, true );
+
+				CKBuilder.css.mergeCssFiles( skinLocation );
+				var iconsDir = new File( skinLocation, 'icons' );
+				if ( iconsDir.exists )
+					CKBuilder.io.deleteDirectory( File( skinLocation, 'icons' ) );
+			}
+		}
+	}
+
+	/**
+	 * Copies files form source to the target location.
+	 * The following actions are additionally executed:
+	 *  - line endings are fixed
+	 *  - directives are processed
+	 *  - JS files are minified
+	 *
+	 * @private
+	 */
+	function copyFiles( context ) {
+		var flags = {},
+			coreLocation = new File( sourceLocation, 'core' );
+
+		CKBuilder.io.copy( sourceLocation, targetLocation, function( sourceLocation, targetLocation ) {
+				if ( CKBuilder.config.isIgnoredPath( sourceLocation, config.ignore ) )
+					return -1;
+
+				if ( extraCoreJavaScriptFiles && extraCoreJavaScriptFiles[ sourceLocation.getAbsolutePath() ] )
+					return -1;
+
+				if ( sourceLocation.isFile() ) {
+					if ( context === 'build' && 'languages' in config ) {
+						try {
+							// Find the "lang" folder inside plugins' folders and ignore language files that are not selected
+							if ( String( sourceLocation.getParentFile().getName() ) === 'lang' && String( sourceLocation.getParentFile().getParentFile().getParentFile().getName() ) === 'plugins' && File( sourceLocation.getParentFile().getParentFile(), 'plugin.js' ).exists() ) {
+								var fileName = String( sourceLocation.getName() ),
+									langFile = fileName.match( /^([a-z]{2}(?:-[a-z]+)?)\.js$/ );
+
+								if ( langFile ) {
+									var langCode = langFile[ 1 ];
+									if ( !config.languages[ langCode ] )
+										return -1;
+								}
+							}
+						} catch ( e ) {
+						}
+					}
+					var copied = CKBuilder.tools.fixLineEndings( sourceLocation, targetLocation );
+					if ( copied ) {
+						if ( CKBuilder.options.commercial )
+							CKBuilder.tools.updateCopyrights( targetLocation );
+
+						var flag = CKBuilder.tools.processDirectives( targetLocation );
+						if ( flag.LEAVE_UNMINIFIED )
+							flags[ targetLocation.getAbsolutePath() ] = flag;
+
+						return 1;
+					}
+				} else {
+					if ( coreLocation.getAbsolutePath().equals( sourceLocation.getAbsolutePath() ) )
+						return -1;
+
+					// No plugins specified, special case to be able to build core only
+					if ( !pluginNamesSorted.length && String( sourceLocation.getName() ) === "plugins" )
+						return -1;
+
+					// No skins specified, special case to be able to build core only
+					if ( typeof config.skin !== 'undefined' && !config.skin && String( sourceLocation.getName() ) === "skins" )
+						return -1;
+
+				}
+				return 0;
+			}, function( targetLocation ) {
+				if ( CKBuilder.options.leaveJsUnminified )
+					return;
+
+				if ( CKBuilder.io.getExtension( targetLocation.getName() ) === 'js' ) {
+					var targetPath = targetLocation.getAbsolutePath();
+					if ( flags[ targetPath ] && flags[ targetPath ].LEAVE_UNMINIFIED ) {
+						if ( CKBuilder.options.debug > 1 )
+							print( "Leaving unminified: " + targetLocation.getPath() );
+
+						CKBuilder.io.saveFile( targetLocation, CKBuilder.tools.removeLicenseInstruction( CKBuilder.io.readFile( targetLocation ) ), true );
+						return;
+					}
+
+					if ( context === 'build' && 'languages' in config && String( targetLocation.getName() ) === 'plugin.js' ) {
+						try {
+							if ( String( targetLocation.getParentFile().getParentFile().getName() ) === 'plugins' && File( targetLocation.getParentFile(), "lang" ).exists() ) {
+								var result = CKBuilder.plugin.updateLangProperty( targetLocation, config.languages );
+								// Something went wrong...
+								if ( result === false )
+									print( "WARNING: it was impossible to update the lang property in " + targetLocation.getAbsolutePath() );
+							}
+						} catch ( e ) {
+						}
+					}
+
+					if ( CKBuilder.options.debug )
+						print( "Minifying: " + targetLocation.getPath() );
+
+					CKBuilder.javascript.minify( targetLocation );
+				}
+			} );
+	}
+
+	/**
+	 * Creates sprite image from icons provided by plugins.
+	 *
+	 * @returns {String} Returns JavaScript code that registers created icons.
+	 * @private
+	 */
+	function createPluginsSpriteImage() {
+		var iconsCode = "";
+		if ( !pluginNamesSorted.length )
+			return "";
+
+		print( "Generating plugins sprite image" );
+		var sourcePluginsLocation = new File( sourceLocation, "plugins" ),
+			targetPluginsLocation = new File( targetLocation, "plugins" );
+		if ( !targetPluginsLocation.exists() )
+			targetPluginsLocation.mkdirs();
+
+		var outputFile = new File( targetPluginsLocation, "icons.png" ),
+			outputFileHidpi = new File( targetPluginsLocation, "icons_hidpi.png" ),
+			iconsOffset = CKBuilder.image.createFullSprite( sourcePluginsLocation, null, outputFile, null, pluginNamesSorted ),
+			iconsOffsetHidpi = CKBuilder.image.createFullSprite( sourcePluginsLocation, null, outputFileHidpi, null, pluginNamesSorted, true );
+
+		if ( iconsOffset )
+			iconsCode = "(function() {" + "var setIcons = function(icons, strip) {" + "var path = CKEDITOR.getUrl( 'plugins/' + strip );" + "icons = icons.split( ',' );" + "for ( var i = 0; i < icons.length; i++ )" + "CKEDITOR.skin.icons[ icons[ i ] ] = { path: path, offset: -icons[ ++i ], bgsize : icons[ ++i ] };" + "};" + "if (CKEDITOR.env.hidpi) " + "setIcons('" + iconsOffsetHidpi + "','icons_hidpi.png');" + "else " + "setIcons('" + iconsOffset + "','icons.png');" + "})();";
+
+		return iconsCode;
+	}
+
+	/**
+	 * Creates ckeditor.js.
+	 *
+	 * @param {Object} config
+	 * @param {String} extraCode JavaScript code to include in ckeditor.js
+	 * @param {Boolean} apply7588 Whether to include patch for #7588
+	 * @param {String} context (build|preprocess) In build,
+	 * @private
+	 */
+	function createCore( config, extraCode, apply7588, context ) {
+		var ckeditorjs = "",
+			patch7588 = 'if(window.CKEDITOR&&window.CKEDITOR.dom)return;';
+
+		if ( extraCoreJavaScriptCode && extraCoreJavaScriptCode.start )
+			ckeditorjs += extraCoreJavaScriptCode.start.join( "\n" );
+
+		ckeditorjs += CKBuilder.io.readFile( File( sourceLocation, "core/ckeditor_base.js" ) ) + "\n";
+		ckeditorjs += CKBuilder.io.readFiles( coreScriptsSorted, "\n" );
+
+		if ( extraCoreJavaScriptCode && extraCoreJavaScriptCode.aftercore )
+			ckeditorjs += extraCoreJavaScriptCode.aftercore.join( "\n" );
+
+		if ( sourceSkinFile )
+			ckeditorjs += CKBuilder.io.readFile( sourceSkinFile ) + "\n";
+
+		if ( pluginNamesSorted.length > 0 ) {
+			var configEntry = "CKEDITOR.config.plugins='" + pluginNamesSorted.join( "," ) + "';";
+			ckeditorjs += CKBuilder.io.readFiles( sourcePluginFilesSorted, "\n" ) + "\n" + configEntry;
+		}
+		// When the core is created for the preprocessed version of CKEditor, then it makes no sense to
+		// specify an empty "config.plugins", because config.plugins will be later set by the online builder.
+		else if ( 'build' === context )
+			ckeditorjs += "CKEDITOR.config.plugins='';";
+
+		if ( config.language )
+			ckeditorjs += CKBuilder.io.readFile( languageFile ) + "\n" ;
+
+		ckeditorjs = CKBuilder.tools.processDirectivesInString( ckeditorjs );
+		ckeditorjs = CKBuilder.tools.processCoreDirectivesInString( ckeditorjs );
+		ckeditorjs = CKBuilder.tools.removeLicenseInstruction( ckeditorjs );
+
+		if ( extraCode )
+			ckeditorjs += extraCode + "\n";
+
+		if ( 'build' === context && config.languages ) {
+			var langs = [];
+			for ( var lang in config.languages ) {
+				if ( config.languages[ lang ] )
+					langs.push( '"' + lang + '":1' );
+			}
+
+			if ( langs.length )
+				ckeditorjs += "CKEDITOR.lang.languages={" + langs.join( ',' ) + "};";
+		}
+
+		// http://dev.ckeditor.com/ticket/7588
+		if ( apply7588 )
+			ckeditorjs = CKBuilder.utils.wrapInFunction( patch7588 + ckeditorjs );
+
+		if ( extraCoreJavaScriptCode && extraCoreJavaScriptCode.end )
+			ckeditorjs += extraCoreJavaScriptCode.end.join( "" );
+
+		var targetFile = File( targetLocation, "ckeditor.js" );
+		CKBuilder.io.saveFile( targetFile, ckeditorjs, true );
+
+		if ( !CKBuilder.options.leaveJsUnminified ) {
+			print( "Minifying ckeditor.js" );
+			CKBuilder.javascript.minify( targetFile );
+		}
+		CKBuilder.io.saveFile( targetFile, CKBuilder.utils.copyright( CKBuilder.options.leaveJsUnminified ? "\r\n" : "\n" ) + CKBuilder.io.readFile( targetFile ), true );
+		print( "Created ckeditor.js (" + parseInt( targetFile.length() / 1024, 10 ) + "KB)" );
+	}
+
+	/**
+	 * Reads configuration file and returns configuration object.
+	 *
+	 * @returns {Object}
+	 * @private
+	 */
+	function readConfig() {
+		var configPath = CKBuilder.options.buildConfig || 'build-config.js',
+			configFile = new File( configPath );
+
+		if ( !configFile.exists() )
+			CKBuilder.error( 'The build configuration file was not found: ' + configPath + "\nRun:\n    java -jar ckbuilder.jar SRC --generate-build-config" );
+		config = CKBuilder.config.read( configFile );
+
+		if ( config.js ) {
+			extraCoreJavaScriptFiles = {};
+			extraCoreJavaScriptCode = { start: [], aftercore: [], end: [] };
+
+			var instruction,
+				regexInstruction = Pattern.compile( '^(.*),(aftercore|end|start)$', Pattern.DOTALL );
+
+			for ( var i = 0; i < config.js.length; i++ ) {
+				var matcher = regexInstruction.matcher( config.js[ i ] ),
+					file,
+					filePath;
+
+				if ( matcher.find() ) {
+					filePath = matcher.group( 1 );
+					instruction = matcher.group( 2 );
+				} else {
+					filePath = config.js[ i ];
+					instruction = 'end';
+				}
+				file = new File( filePath );
+				if ( !file.exists() )
+					CKBuilder.error( "File not found: " + file.getAbsolutePath() + "\nCheck the build configuration file." );
+
+				extraCoreJavaScriptFiles[ file.getAbsolutePath() ] = true;
+
+				if ( CKBuilder.options.debug )
+					print( 'Adding extra file [' + instruction + ']: ' + filePath );
+
+				extraCoreJavaScriptCode[ instruction ].push( CKBuilder.io.readFile( file ) );
+			}
+		}
+
+		return config;
+	}
+
+	return {
+		/**
+		 * Preprocess CKEditor core.
+		 *
+		 * @static
+		 */
+		preprocess: function() {
+			var time = new Date(),
+				config = readConfig();
+
+			config.plugins = {};
+			config.skin = '';
+			config.language = false;
+
+			validateSourceFolder();
+			CKBuilder.tools.prepareTargetFolder( File( dstDir ) );
+			init();
+			print( "Copying files (relax, this may take a while)" );
+			copyFiles( 'preprocess' );
+			time = CKBuilder.utils.printUsedTime( time );
+
+			print( "Merging language files" );
+			var langFolder = new File( targetLocation, 'lang' );
+			CKBuilder.lang.mergeAll( sourceLocation, langFolder, {}, config.languages );
+			time = CKBuilder.utils.printUsedTime( time );
+
+			print( "Processing lang folder" );
+			var children = langFolder.list();
+			for ( var i = 0; i < children.length; i++ ) {
+				if ( children[ i ].match( /^([a-z]{2}(?:-[a-z]+)?)\.js$/ ) ) {
+					var langFile = new File( langFolder, children[ i ] ),
+						translation = CKBuilder.lang.loadLanguageFile( langFile ).translation,
+						pseudoObject = JSON.stringify( translation ).replace( /^\{(.*)\}$/, '$1' );
+
+					CKBuilder.io.saveFile( langFile, pseudoObject, true );
+				}
+			}
+
+			print( "Building ckeditor.js" );
+			createCore( config, "", false, 'preprocess' );
+
+			print( "Cleaning up target folder" );
+			deleteUnusedFiles();
+			CKBuilder.utils.printUsedTime( time );
+		},
+
+		/**
+		 * Creates ckeditor.js and icons.png in the target folder.
+		 *
+		 * @static
+		 */
+		generateCore: function() {
+			var time = new Date(),
+				config = readConfig();
+
+			validateSourceFolder();
+			init();
+
+			config.language = false;
+			var iconsCode = createPluginsSpriteImage();
+			print( "Building ckeditor.js" );
+			var extraCode = '';
+			if ( config.skin )
+				extraCode = "CKEDITOR.config.skin='" + config.skin + "';";
+			createCore( config, extraCode + iconsCode, true, 'build' );
+			CKBuilder.utils.printUsedTime( time );
+		},
+
+		/**
+		 * Creates CKEditor build in the specified folder.
+		 *
+		 * @static
+		 */
+		generateBuild: function() {
+			var time = new Date(),
+				startTime = time,
+				config = readConfig();
+
+			validateSourceFolder();
+			CKBuilder.tools.prepareTargetFolder( File( dstDir ) );
+			init();
+			print( "Copying files (relax, this may take a while)" );
+			copyFiles( 'build' );
+			if ( !CKBuilder.options.all ) {
+				filterPluginFolders();
+				if ( config.skin )
+					filterSkinsFolders( config.skin );
+			}
+			time = CKBuilder.utils.printUsedTime( time );
+
+			print( "Merging language files" );
+			CKBuilder.lang.mergeAll( sourceLocation, File( targetLocation, 'lang' ), pluginNames, config.languages );
+			time = CKBuilder.utils.printUsedTime( time );
+
+			var iconsCode = createPluginsSpriteImage();
+			print( "Building ckeditor.js" );
+			var extraCode = '';
+			if ( config.skin )
+				extraCode = "CKEDITOR.config.skin='" + config.skin + "';";
+			createCore( config, extraCode + iconsCode, true, 'build' );
+			time = CKBuilder.utils.printUsedTime( time );
+
+			print( "Building skins" );
+			buildSkins();
+			if ( targetSkinFile )
+				CKBuilder.io.deleteFile( targetSkinFile );
+			time = CKBuilder.utils.printUsedTime( time );
+
+			CKBuilder.samples.mergeSamples( targetLocation );
+
+			print( "Cleaning up target folder" );
+			deleteUnusedFiles();
+			time = CKBuilder.utils.printUsedTime( time );
+
+			// get information about release directory
+			var info = CKBuilder.io.getDirectoryInfo( targetLocation );
+
+			if ( !CKBuilder.options.noZip || !CKBuilder.options.noTar )
+				print( "\nCreating compressed files...\n" );
+
+			var normalize = function( version ) {
+				return String( version ).toLowerCase().replace( / /g, "_" ).replace( /\(\)/g, "" );
+			};
+
+			if ( !CKBuilder.options.noZip ) {
+				var zipFile = new File( targetLocation.getParentFile(), "ckeditor_" + normalize( CKBuilder.options.version ) + ".zip" );
+				CKBuilder.io.zipDirectory( targetLocation, targetLocation, zipFile, "ckeditor" );
+				print( "    Created " + zipFile.getName() + "...: " + zipFile.length() + " bytes (" + Math.round( zipFile.length() / info.size * 100 ) + "% of original)" );
+			}
+			if ( !CKBuilder.options.noTar ) {
+				var tarFile = new File( targetLocation.getParentFile(), "ckeditor_" + normalize( CKBuilder.options.version ) + ".tar.gz" );
+				CKBuilder.io.targzDirectory( targetLocation, targetLocation, tarFile, "ckeditor" );
+				print( "    Created " + tarFile.getName() + ": " + tarFile.length() + " bytes (" + Math.round( tarFile.length() / info.size * 100 ) + "% of original)" );
+			}
+			CKBuilder.utils.printUsedTime( time );
+
+			print( "\n==========================" );
+			print( "Release process completed:\n" );
+			print( "    Number of files: " + info.files );
+			print( "    Total size.....: " + info.size + " bytes" );
+			CKBuilder.utils.printUsedTime( startTime );
+			print( "" );
+		}
+	};
+};
diff --git a/src/lib/config.js b/src/lib/config.js
new file mode 100644
index 0000000..967aec3
--- /dev/null
+++ b/src/lib/config.js
@@ -0,0 +1,115 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importClass( java.io.BufferedWriter );
+importClass( java.io.FileWriter );
+importClass( java.io.FileOutputStream );
+importClass( java.io.FileInputStream );
+
+( function() {
+	/**
+	 * Looking through directory and search subdirectories.
+	 * When second parameter provided also filter subdirectories which contains file provided in parameter.
+	 *
+	 * @param {java.io.File} rootDir
+	 * @param {String=} requiredFile If parameter provided also check whether directory contains file
+	 * @returns {Object} Hash map of 1
+	 * @member CKBuilder.config
+	 */
+	function getSubfolders( rootDir, requiredFile ) {
+		if ( !rootDir.exists() || !rootDir.isDirectory() )
+			return {};
+
+		var children = rootDir.list(), // get directory children
+			result = {};
+
+		children.sort();
+		for ( var i = 0; i < children.length; i++ ) {
+			var childDir = new File( rootDir, children[ i ] );
+
+			if ( !requiredFile || File( childDir, requiredFile ).exists() )
+				result[ children[ i ] ] = 1;
+		}
+
+		return result;
+	}
+
+	/**
+	 * Responsible for creating CKBuilder config based on source directory
+	 *
+	 * @class
+	 */
+	CKBuilder.config = {
+		/**
+		 * Creates a configuration file (build-config.js) with all plugins and skins listed.
+		 * Config file structure is based on `plugins` and `skins` catalogue content.
+		 *
+		 * @param {String} sourceDir Path to the folder with source files
+		 * @static
+		 */
+		create: function( sourceDir ) {
+			var sourceLocation = new File( sourceDir );
+
+			if ( !sourceLocation.exists() )
+				CKBuilder.error( "Source folder does not exist: " + sourceDir );
+			if ( !sourceLocation.isDirectory() )
+				CKBuilder.error( "Source folder is not a directory: " + sourceDir );
+
+			var plugins = getSubfolders( File( sourceLocation, "plugins" ), "plugin.js" ),
+				skins = getSubfolders( File( sourceLocation, "skins" ), "skin.js" ),
+				config = {
+					skins: skins,
+					plugins: plugins
+				};
+
+			CKBuilder.io.saveFile( CKBuilder.options.buildConfig || 'build-config.js', "var CKBUILDER_CONFIG = {\n" + CKBuilder.utils.prettyPrintObject( config, "	" ) + "\n};" );
+		},
+
+		/**
+		 * Reads a configuration file and returns the configuration object.
+		 *
+		 * @param {java.io.File} configFile Path to the configuration file
+		 * @static
+		 */
+		read: function( configFile ) {
+			var file = new File( configFile ),
+				code = CKBuilder.io.readFile( file ),
+				cx = Context.enter(),
+				scope = cx.initStandardObjects();
+
+			try {
+				cx.evaluateString( scope, code, file.getName(), 1, null );
+				return scope.CKBUILDER_CONFIG;
+			} catch ( e ) {
+				throw( "Configuration file is invalid: " + file.getAbsolutePath() + ".\nError: " + e.message );
+			}
+		},
+
+		/**
+		 * Returns true if the file/folder is set to be ignored.
+		 *
+		 * @param {java.io.File} sourceLocation
+		 * @param {Array} ignoredPaths An array with ignored paths
+		 * @returns {Boolean}
+		 * @static
+		 */
+		isIgnoredPath: function( sourceLocation, ignoredPaths ) {
+			if ( !ignoredPaths )
+				return false;
+
+			for ( var i = 0; i < ignoredPaths.length; i++ ) {
+				var rule = ignoredPaths[ i ];
+
+				if ( rule.indexOf( '/' ) === -1 ) {
+					if ( String( sourceLocation.getName() ) === rule )
+						return true;
+				} else if ( sourceLocation.getAbsolutePath().replace( "\\", "/" ).endsWith( rule ) )
+					return true;
+			}
+
+			return false;
+		}
+	};
+}() );
diff --git a/src/lib/controller.js b/src/lib/controller.js
new file mode 100644
index 0000000..37525b9
--- /dev/null
+++ b/src/lib/controller.js
@@ -0,0 +1,239 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importPackage( org.apache.commons.cli );
+importClass( java.lang.System );
+/**
+ * The main controller, parses the command line options and calls the right methods.
+ *
+ * @class
+ * @constructor
+ */
+CKBuilder.Controller = function() {
+	/**
+	 * Command definitions along with descriptions.
+	 *
+	 * @type {Array}
+	 * @private
+	 */
+	var options = [
+		// Commands
+		[ null, "build", false, "build release" ],
+		[ null, "generate-build-config", false, "generate build configuration file" ],
+		[ null, "help", false, "print help" ],
+		[ null, "full-help", false, "print help for all advanced commands" ],
+		[ null, "build-help", false, "print help about build configuration" ],
+		[ null, "preprocess-core", false, "preprocess CKEditor core" ],
+		[ null, "preprocess-plugin", false, "preprocess plugin" ],
+		[ null, "preprocess-skin", false, "preprocess skin" ],
+		[ null, "build-skin", false, "build skin" ],
+		[ null, "verify-plugin", false, "verify plugin" ],
+		[ null, "verify-skin", false, "verify skin" ],
+		// Options
+		[ null, "build-config", [ "FILE", "path to the file" ], "path to the build configuration" ],
+		[ null, "leave-js-unminified", false, "leave javascript files as is, do not minify them" ],
+		[ null, "leave-css-unminified", false, "leave CSS files as is, do not minify them" ],
+		[ "s", "skip-omitted-in-build-config", false, "exclude from release all plugins/skins that are not specified in build-config" ],
+		[ null, "revision", [ "NUMBER", "revision number" ], "revision number" ],
+		[ null, "version", [ "NUMBER", "version number" ], "version number" ],
+		[ null, "overwrite", false, "overwrite target folder if exists" ],
+		[ null, "no-ie-checks", false, "turn off warnings about syntax errors on Internet Explorer, like trailing commas" ],
+		[ null, "no-zip", false, "do not create zip file" ],
+		[ null, "no-tar", false, "do not create tar.gz file" ],
+		[ null, "core", false, "build only the core file (ckeditor.js)" ],
+		[ null, "commercial", false, "builds a package with commercial license" ],
+		[ null, "name", [ "NAME", "expected name" ], "the expected name of the skin/plugin, used for verification" ],
+		[ "d", "debug-level", [ "LEVEL", "debug level (0, 1, 2)." ], "sets the debug level" ]
+	];
+
+	/**
+	 * Object with functions which are called for appropriate commands.
+	 * Key is command name and value is function which is called with `CKBuilder.Controller` context
+	 * and two arguments:
+	 * first - Array of strings which are command line arguments
+	 * second - Command line instance {org.apache.commons.cli.CommandLine}
+	 *
+	 * @type {Object}
+	 */
+	this.commandsHandlers = {
+		'help': function() {
+			this.printHelp( [ 'help.txt' ] );
+		},
+		'full-help': function() {
+			this.printHelp( [ 'help.txt', 'help-extra.txt' ] );
+		},
+		'build-help': function() {
+			this.printHelp( [ 'help-build.txt' ] );
+		},
+		'build': function( args ) {
+			if ( args.length < 2 )
+				CKBuilder.error( "The build command requires two arguments." );
+
+			var builder = CKBuilder.builder( args[ 0 ], args[ 1 ] );
+			if ( CKBuilder.options.core )
+				builder.generateCore();
+			else
+				builder.generateBuild();
+		},
+		'generate-build-config': function( args ) {
+			if ( args.length < 1 )
+				CKBuilder.error( "The generate-build-config command requires an argument." );
+
+			CKBuilder.config.create( args[ 0 ] );
+		},
+		'preprocess-core': function( args ) {
+			if ( args.length < 2 )
+				CKBuilder.error( "The preprocess-core command requires two arguments." );
+
+			var builder = CKBuilder.builder( args[ 0 ], args[ 1 ] );
+			builder.preprocess();
+		},
+		'preprocess-plugin': function( args ) {
+			if ( args.length < 2 )
+				CKBuilder.error( "The preprocess-plugin command requires two arguments." );
+
+			CKBuilder.plugin.preprocess( args[ 0 ], args[ 1 ] );
+			print( "Plugin preprocessed successfully" );
+		},
+		'preprocess-skin': function( args ) {
+			if ( args.length < 2 )
+				CKBuilder.error( "The preprocess-skin command requires two arguments." );
+
+			CKBuilder.skin.preprocess( args[ 0 ], args[ 1 ] );
+			print( "Skin preprocessed successfully" );
+		},
+		'build-skin': function( args ) {
+			if ( args.length < 2 )
+				CKBuilder.error( "The build-skin command requires two arguments." );
+
+			CKBuilder.skin.build( args[ 0 ], args[ 1 ] );
+		},
+		'verify-plugin': function( args, line ) {
+			var options = {};
+
+			if ( line.hasOption( "name" ) )
+				options.pluginName = String( line.getOptionValue( "name" ) );
+
+			if ( args.length < 1 )
+				CKBuilder.error( "The verify-plugin command requires an argument." );
+
+			print( CKBuilder.plugin.verify( args[ 0 ], options ) );
+		},
+		'verify-skin': function( args, line ) {
+			var options = {};
+			if ( line.hasOption( "name" ) )
+				options.skinName = String( line.getOptionValue( "name" ) );
+
+			if ( args.length < 1 )
+				CKBuilder.error( "The verify-skin command requires an argument." );
+
+			print( CKBuilder.skin.verify( args[ 0 ], options ) );
+		}
+	};
+
+	/**
+	 * @type {org.apache.commons.cli.PosixParser}
+	 */
+	this.parser = new PosixParser();
+
+	/**
+	 * @type {org.apache.commons.cli.Options}
+	 */
+	this.options = new Options();
+
+	for ( var i = 0; i < options.length; i++ ) {
+		if ( !options[ i ][ 2 ] )
+			this.options.addOption.apply( this.options, options[ i ] );
+		else {
+			var option = OptionBuilder.withLongOpt( options[ i ][ 1 ] ).withDescription( options[ i ][ 2 ][ 1 ] ).hasArg().withArgName( options[ i ][ 2 ][ 0 ] );
+
+			this.options.addOption( option.create( options[ i ][ 0 ] || null ) );
+		}
+	}
+};
+
+CKBuilder.Controller.prototype = {
+	/**
+	 * Prints all available options.
+	 *
+	 * @param {Array} types
+	 */
+	printHelp: function( types ) {
+		var i, date = new Date();
+
+		if ( CKBuilder.isMinified ) {
+			for ( i = 0; i < types.length; i++ )
+				print( "\n" + CKBuilder.io.readFileFromJar( types[ i ] ) );
+		} else {
+			for ( i = 0; i < types.length; i++ )
+				print( "\n" + CKBuilder.io.readFile( new File( "src/assets/" + types[ i ] ) ) );
+		}
+		print( "Copyright (c) 2003-" + date.getFullYear() + ", CKSource - Frederico Knabben" );
+	},
+
+	/**
+	 * Executes commands based on passed arguments.
+	 *
+	 * @param {Array} _arguments An array containing the strings of all the arguments given at the command line when the shell was invoked
+	 */
+	run: function( _arguments ) {
+		// parse the command line arguments
+		var line = this.parser.parse( this.options, _arguments );
+
+		// Options
+		if ( line.hasOption( "debug-level" ) )
+			CKBuilder.options.debug = line.getOptionValue( "debug-level" );
+
+		if ( line.hasOption( "overwrite" ) )
+			CKBuilder.options.overwrite = true;
+
+		if ( line.hasOption( "build-config" ) )
+			CKBuilder.options.buildConfig = line.getOptionValue( "build-config" );
+
+		if ( line.hasOption( "skip-omitted-in-build-config" ) )
+			CKBuilder.options.all = false;
+
+		if ( line.hasOption( "version" ) )
+			CKBuilder.options.version = line.getOptionValue( "version" );
+
+		if ( line.hasOption( "core" ) )
+			CKBuilder.options.core = true;
+
+		if ( line.hasOption( "commercial" ) )
+			CKBuilder.options.commercial = true;
+
+		if ( line.hasOption( "revision" ) )
+			CKBuilder.options.revision = line.getOptionValue( "revision" );
+
+		if ( line.hasOption( "leave-js-unminified" ) )
+			CKBuilder.options.leaveJsUnminified = true;
+
+		if ( line.hasOption( "leave-css-unminified" ) )
+			CKBuilder.options.leaveCssUnminified = true;
+
+		if ( line.hasOption( "no-zip" ) )
+			CKBuilder.options.noZip = true;
+
+		if ( line.hasOption( "no-ie-checks" ) )
+			CKBuilder.options.noIeChecks = true;
+
+		if ( line.hasOption( "no-tar" ) )
+			CKBuilder.options.noTar = true;
+
+		var foundCommandName = null;
+		for ( var commandName in this.commandsHandlers ) {
+			if ( line.hasOption( commandName ) ) {
+				foundCommandName = commandName;
+				break;
+			}
+		}
+
+		foundCommandName = foundCommandName || 'help';
+
+		this.commandsHandlers[ foundCommandName ].call(this, line.getArgs(), line );
+
+		System.exit( 0 );
+	}
+};
\ No newline at end of file
diff --git a/src/lib/css.js b/src/lib/css.js
new file mode 100644
index 0000000..19c9556
--- /dev/null
+++ b/src/lib/css.js
@@ -0,0 +1,239 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importClass( java.io.BufferedWriter );
+importClass( java.io.FileWriter );
+importClass( java.io.FileOutputStream );
+importClass( java.io.FileInputStream );
+importPackage( com.yahoo.platform.yui.compressor );
+
+( function() {
+	var importedFiles = {};
+
+	/**
+	 * Removes comments from specified text.
+	 *
+	 * @param {String} text
+	 * @returns {String}
+	 * @member CKBuilder.css
+	 * @private
+	 */
+	function removeComments( text ) {
+		var endIndex,
+			startIndex = 0,
+			/**
+			 * Indicating comment to hide rules from IE Mac.
+			 *
+			 * @property {Boolean} iemac
+			 * @private
+			 * @member CKBuilder.css
+			 */
+			iemac = false,
+			preserve = false,
+			sb = new StringBuffer( text );
+
+		while ( ( startIndex = sb.indexOf( "/*", startIndex ) ) >= 0 ) {
+			preserve = sb.length() > startIndex + 2 && sb.charAt( startIndex + 2 ) === '!';
+			endIndex = sb.indexOf( "*/", startIndex + 2 );
+			if ( endIndex < 0 ) {
+				if ( !preserve )
+					sb[ "delete" ]( startIndex, sb.length() );
+			} else if ( endIndex >= startIndex + 2 ) {
+				if ( sb.charAt( endIndex - 1 ) === '\\' ) {
+					/*
+					 * Looks like a comment to hide rules from IE Mac.
+					 * Leave this comment, and the following one, alone...
+					 */
+					startIndex = endIndex + 2;
+					iemac = true;
+				} else if ( iemac ) {
+					startIndex = endIndex + 2;
+					iemac = false;
+				} else if ( !preserve ) {
+					try {
+						/* Remove new line character if there is nothing else after a comment */
+						if ( sb.charAt( endIndex + 2 ) === 13 && sb.charAt( endIndex + 3 ) === 10 )
+							endIndex += 2;
+						else if ( sb.charAt( endIndex + 2 ) === 10 && sb.charAt( endIndex + 3 ) === 13 )
+							endIndex += 2;
+						else if ( sb.charAt( endIndex + 2 ) === 13 && sb.charAt( endIndex + 3 ) === 13 )
+							endIndex += 1;
+						else if ( sb.charAt( endIndex + 2 ) === 10 && sb.charAt( endIndex + 3 ) === 10 )
+							endIndex += 1;
+					} catch ( e ) {
+						/* catch StringIndexOutOfBoundsException if comment is at the end of file */
+					}
+
+					sb[ "delete" ]( startIndex, endIndex + 2 );
+				} else
+					startIndex = endIndex + 2;
+
+			}
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * Returns content of source file and all CSS files included in import statements.
+	 *
+	 * @param {java.io.File} sourceLocation The location of CSS file
+	 * @param {java.io.File=} parentLocation The location of parent CSS file, if source file was imported
+	 * @returns {String}
+	 * @member CKBuilder.css
+	 * @private
+	 */
+	function processCssFile( sourceLocation, parentLocation ) {
+		var out = [],
+			isImported = false,
+			parentPath,
+			path = sourceLocation.getCanonicalPath(),
+			lines = CKBuilder.io.readFile( new File( path ) ).split( /\r\n|\n|\r/ );
+
+		if ( !parentLocation ) {
+			parentLocation = sourceLocation;
+			parentPath = sourceLocation.getCanonicalPath();
+		} else {
+			isImported = true;
+			parentPath = parentLocation.getCanonicalPath();
+			if ( path === parentPath )
+				throw( "Invalid @import statements, file including itself: " + path );
+
+			if ( importedFiles[ parentPath ][ path ] )
+				throw( "Invalid @import statement in " + parentPath + ", file " + path + " was already imported." );
+
+			importedFiles[ parentPath ][ path ] = true;
+		}
+
+		for ( var i = 0, length = lines.length; i < length; i++ ) {
+			if ( lines[ i ].indexOf( "@import" ) === -1 )
+				out.push( lines[ i ] );
+			else {
+				var matches = lines[ i ].match( /^\s*@import\s+url\(["'](.*?)["']\)/ );
+
+				if ( matches[ 1 ] ) {
+					var file = new File( sourceLocation.getParent(), matches[ 1 ] );
+					if ( !file.exists() )
+						throw( "Importing of CSS file failed, file does not exist (" + file.getPath() + ")" );
+					else {
+						if ( !importedFiles[ parentPath ] )
+							importedFiles[ parentPath ] = {};
+
+						out.push( processCssFile( file, parentLocation ) );
+					}
+				} else
+					out.push( lines[ i ] );
+			}
+		}
+
+		if ( isImported )
+			return removeComments( out.join( "\r\n" ) );
+		else
+			return out.join( "\r\n" ).replace( /(\r\n){2,}/g, "\r\n" );
+	}
+
+	/**
+	 * Copies files from source to the target folder and calls the CSS processor on each css file.
+	 *
+	 * @param {java.io.File} targetLocation Target folder
+	 * @member CKBuilder.css
+	 * @private
+	 */
+	function processCssFiles( targetLocation ) {
+		var children = targetLocation.list();
+
+		for ( var i = 0; i < children.length; i++ ) {
+			var f = new File( targetLocation, children[ i ] );
+			if ( f.isDirectory() )
+				processCssFiles( f );
+			else if ( f.getName().toLowerCase().endsWith( ".css" ) ) {
+				CKBuilder.io.saveFile( f, processCssFile( f ) );
+				if ( CKBuilder.options.debug )
+					print( "    Saved CSS file: " + f.getAbsolutePath() );
+			}
+		}
+	}
+
+	/**
+	 * Compress all CSS files in given directory.
+	 *
+	 * @param {java.io.File} targetLocation
+	 * @member CKBuilder.css
+	 * @private
+	 */
+	function compressCssFiles( targetLocation ) {
+		var children = targetLocation.list();
+
+		for ( var i = 0; i < children.length; i++ ) {
+			var f = new File( targetLocation, children[ i ] );
+			if ( f.isDirectory() )
+				compressCssFiles( f );
+			else if ( f.getName().toLowerCase().endsWith( ".css" ) ) {
+				if ( CKBuilder.options.debug )
+					print( "Compressing " + f.getAbsolutePath() );
+
+				var cssContent = CKBuilder.io.readFile( f ),
+					copyright = CKBuilder.tools.getCopyrightFromText( cssContent );
+
+				cssContent = YAHOO.compressor.cssmin( cssContent, -1 );
+				CKBuilder.io.saveFile( f, copyright + cssContent );
+			}
+		}
+	}
+
+	/**
+	 * Removes imported CSS files.
+	 *
+	 * @param {Object} importedFiles
+	 * @member CKBuilder.css
+	 * @private
+	 */
+	function deleteImportedFiles( importedFiles ) {
+		for ( var parentPath in importedFiles ) {
+			for ( var path in importedFiles[ parentPath ] ) {
+				if ( !importedFiles[ path ] ) {
+					var file = new File( path ),
+						fileName = String( file.getName() );
+
+					if ( fileName === "dialog.css" || fileName === "editor.css" )
+						continue;
+
+					if ( CKBuilder.options.debug > 1 )
+						print( "    CSS file was imported, removing: " + path );
+
+					CKBuilder.io.deleteFile( path );
+				} else {
+					if ( CKBuilder.options.debug > 1 )
+						print( "    CSS file was imported, but is also a root CSS file for another file: " + path );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Handle css files - merge then, and determine dependencies.
+	 *
+	 * @class
+	 */
+	CKBuilder.css = {
+		/**
+		 * Performs optimization of CSS files in given location.
+		 * Join @import files into root CSS file.
+		 *
+		 * @param {java.io.File} targetLocation The folder where to optimize CSS files.
+		 * @static
+		 */
+		mergeCssFiles: function( targetLocation ) {
+			if ( !targetLocation.isDirectory() )
+				throw( "CSS compression failed. The target location is not a directory: " + targetLocation.getAbsolutePath() );
+
+			importedFiles = {};
+			processCssFiles( targetLocation );
+			deleteImportedFiles( importedFiles );
+			if ( !CKBuilder.options.leaveCssUnminified )
+				compressCssFiles( targetLocation );
+		}
+	};
+}() );
diff --git a/src/lib/cssmin.js b/src/lib/cssmin.js
new file mode 100644
index 0000000..8c1fc93
--- /dev/null
+++ b/src/lib/cssmin.js
@@ -0,0 +1,333 @@
+/*
+ * cssmin.js
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * This is a JavaScript port of the CSS minification tool
+ * distributed with YUICompressor, itself a port
+ * of the cssmin utility by Isaac Schlueter - http://foohack.com/
+ * Permission is hereby granted to use the JavaScript version under the same
+ * conditions as the YUICompressor (original YUICompressor note below).
+ */
+
+/* jshint eqeqeq: false */
+
+/**
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ *
+ * @class YAHOO.compressor
+ */
+this.YAHOO = this.YAHOO || {};
+YAHOO.compressor = YAHOO.compressor || {};
+
+/**
+ * Utility method to replace all data urls with tokens before we start
+ * compressing, to avoid performance issues running some of the subsequent
+ * regexes against large strings chunks.
+ *
+ * @private
+ * @method _extractDataUrls
+ * @param {String} css The input css
+ * @param {Array} preservedTokens The global array of tokens to preserve
+ * @returns String The processed css
+ */
+YAHOO.compressor._extractDataUrls = function( css, preservedTokens ) {
+
+	// Leave data urls alone to increase parse performance.
+	var maxIndex = css.length - 1, appendIndex = 0, startIndex, endIndex, terminator, foundTerminator, sb = [], m, preserver, token, pattern = /url\(\s*(["']?)data\:/g;
+
+	// Since we need to account for non-base64 data urls, we need to handle
+	// ' and ) being part of the data string. Hence switching to indexOf,
+	// to determine whether or not we have matching string terminators and
+	// handling sb appends directly, instead of using matcher.append* methods.
+
+	while ( ( m = pattern.exec( css ) ) !== null ) {
+
+		startIndex = m.index + 4;  // "url(".length()
+		terminator = m[ 1 ];         // ', " or empty (not quoted)
+
+		if ( terminator.length === 0 )
+			terminator = ")";
+
+		foundTerminator = false;
+
+		endIndex = pattern.lastIndex - 1;
+
+		while ( foundTerminator === false && endIndex + 1 <= maxIndex ) {
+			endIndex = css.indexOf( terminator, endIndex + 1 );
+
+			// endIndex == 0 doesn't really apply here
+			if ( ( endIndex > 0 ) && ( css.charAt( endIndex - 1 ) !== '\\') ) {
+				foundTerminator = true;
+				if ( ")" != terminator )
+					endIndex = css.indexOf( ")", endIndex );
+
+			}
+		}
+
+		// Enough searching, start moving stuff over to the buffer
+		sb.push( css.substring( appendIndex, m.index ) );
+
+		if ( foundTerminator ) {
+			token = css.substring( startIndex, endIndex );
+			token = token.replace( /\s+/g, "" );
+			preservedTokens.push( token );
+
+			preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + ( preservedTokens.length - 1 ) + "___)";
+			sb.push( preserver );
+
+			appendIndex = endIndex + 1;
+		} else {
+			// No end terminator found, re-add the whole match. Should we throw/warn here?
+			sb.push( css.substring( m.index, pattern.lastIndex ) );
+			appendIndex = pattern.lastIndex;
+		}
+	}
+
+	sb.push( css.substring( appendIndex ) );
+
+	return sb.join( "" );
+};
+
+/**
+ * Utility method to compress hex color values of the form #AABBCC to #ABC.
+ *
+ * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
+ * e.g. #AddressForm { ... }
+ *
+ * DOES NOT compress IE filters, which have hex color values (which would break things).
+ * e.g. filter: chroma(color="#FFFFFF");
+ *
+ * DOES NOT compress invalid hex values.
+ * e.g. background-color: #aabbccdd
+ *
+ * @private
+ * @method _compressHexColors
+ * @param {String} css The input css
+ * @returns String The processed css
+ */
+YAHOO.compressor._compressHexColors = function( css ) {
+
+	// Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
+	var pattern = /(\=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/gi, m, index = 0, isFilter, sb = [];
+
+	while ( ( m = pattern.exec( css ) ) !== null ) {
+
+		sb.push( css.substring( index, m.index ) );
+
+		isFilter = m[ 1 ];
+
+		if ( isFilter ) {
+			// Restore, maintain case, otherwise filter will break
+			sb.push( m[ 1 ] + "#" + ( m[ 2 ] + m[ 3 ] + m[ 4 ] + m[ 5 ] + m[ 6 ] + m[ 7 ] ) );
+		} else {
+			if ( m[ 2 ].toLowerCase() == m[ 3 ].toLowerCase() && m[ 4 ].toLowerCase() == m[ 5 ].toLowerCase() && m[ 6 ].toLowerCase() == m[ 7 ].toLowerCase() ) {
+
+				// Compress.
+				sb.push( "#" + ( m[ 3 ] + m[ 5 ] + m[ 7 ] ).toLowerCase() );
+			} else {
+				// Non compressible color, restore but lower case.
+				sb.push( "#" + ( m[ 2 ] + m[ 3 ] + m[ 4 ] + m[ 5 ] + m[ 6 ] + m[ 7 ] ).toLowerCase() );
+			}
+		}
+
+		index = pattern.lastIndex = pattern.lastIndex - m[ 8 ].length;
+	}
+
+	sb.push( css.substring( index ) );
+
+	return sb.join( "" );
+};
+
+YAHOO.compressor.cssmin = function( css, linebreakpos ) {
+
+	var startIndex = 0, endIndex = 0, i = 0, max = 0, preservedTokens = [], comments = [], token = '', totallen = css.length, placeholder = '';
+
+	css = this._extractDataUrls( css, preservedTokens );
+
+	// collect all comment blocks...
+	while ( ( startIndex = css.indexOf( "/*", startIndex ) ) >= 0 ) {
+		endIndex = css.indexOf( "*/", startIndex + 2 );
+		if ( endIndex < 0 )
+			endIndex = totallen;
+
+		token = css.slice( startIndex + 2, endIndex );
+		comments.push( token );
+		css = css.slice( 0, startIndex + 2 ) + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + ( comments.length - 1 ) + "___" + css.slice( endIndex );
+		startIndex += 2;
+	}
+
+	// preserve strings so their content doesn't get accidentally minified
+	css = css.replace( /("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g, function( match ) {
+		var i, max, quote = match.substring( 0, 1 );
+
+		match = match.slice( 1, -1 );
+
+		// maybe the string contains a comment-like substring?
+		// one, maybe more? put'em back then
+		if ( match.indexOf( "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" ) >= 0 ) {
+			for ( i = 0, max = comments.length; i < max; i = i + 1 ) {
+				match = match.replace( "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[ i ] );
+			}
+		}
+
+		// minify alpha opacity in filter strings
+		match = match.replace( /progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=" );
+
+		preservedTokens.push( match );
+		return quote + "___YUICSSMIN_PRESERVED_TOKEN_" + ( preservedTokens.length - 1 ) + "___" + quote;
+	} );
+
+	// strings are safe, now wrestle the comments
+	for ( i = 0, max = comments.length; i < max; i = i + 1 ) {
+
+		token = comments[ i ];
+		placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+		// ! in the first position of the comment means preserve
+		// so push to the preserved tokens keeping the !
+		if ( token.charAt( 0 ) === "!" ) {
+			preservedTokens.push( token );
+			css = css.replace( placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + ( preservedTokens.length - 1 ) + "___" );
+			continue;
+		}
+
+		// \ in the last position looks like hack for Mac/IE5
+		// shorten that to /*\*/ and the next one to /**/
+		if ( token.charAt( token.length - 1 ) === "\\" ) {
+			preservedTokens.push( "\\" );
+			css = css.replace( placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + ( preservedTokens.length - 1 ) + "___" );
+			i = i + 1; // attn: advancing the loop
+			preservedTokens.push( "" );
+			css = css.replace( "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + ( preservedTokens.length - 1 ) + "___" );
+			continue;
+		}
+
+		// keep empty comments after child selectors (IE7 hack)
+		// e.g. html >/**/ body
+		if ( token.length === 0 ) {
+			startIndex = css.indexOf( placeholder );
+			if ( startIndex > 2 ) {
+				if ( css.charAt( startIndex - 3 ) === '>' ) {
+					preservedTokens.push( "" );
+					css = css.replace( placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + ( preservedTokens.length - 1 ) + "___" );
+				}
+			}
+		}
+
+		// in all other cases kill the comment
+		css = css.replace( "/*" + placeholder + "*/", "" );
+	}
+
+
+	// Normalize all whitespace strings to single spaces. Easier to work with that way.
+	css = css.replace( /\s+/g, " " );
+
+	// Remove the spaces before the things that should not have spaces before them.
+	// But, be careful not to turn "p :link {...}" into "p:link{...}"
+	// Swap out any pseudo-class colons with the token, and then swap back.
+	css = css.replace( /(^|\})(([^\{:])+:)+([^\{]*\{)/g, function( m ) {
+		return m.replace( ":", "___YUICSSMIN_PSEUDOCLASSCOLON___" );
+	} );
+	css = css.replace( /\s+([!{};:>+\(\)\],])/g, '$1' );
+	css = css.replace( /___YUICSSMIN_PSEUDOCLASSCOLON___/g, ":" );
+
+	// retain space for special IE6 cases
+	css = css.replace( /:first-(line|letter)(\{|,)/g, ":first-$1 $2" );
+
+	// no space after the end of a preserved comment
+	css = css.replace( /\*\/ /g, '*/' );
+
+
+	// If there is a @charset, then only allow one, and push to the top of the file.
+	css = css.replace( /^(.*)(@charset "[^"]*";)/gi, '$2$1' );
+	css = css.replace( /^(\s*@charset [^;]+;\s*)+/gi, '$1' );
+
+	// Put the space back in some cases, to support stuff like
+	// @media screen and (-webkit-min-device-pixel-ratio:0){
+	css = css.replace( /\band\(/gi, "and (" );
+
+
+	// Remove the spaces after the things that should not have spaces after them.
+	css = css.replace( /([!{}:;>+\(\[,])\s+/g, '$1' );
+
+	// remove unnecessary semicolons
+	css = css.replace( /;+\}/g, "}" );
+
+	// Replace 0(px,em,%) with 0.
+	css = css.replace( /([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)/gi, "$1$2" );
+
+	// Replace 0 0 0 0; with 0.
+	css = css.replace( /:0 0 0 0(;|\})/g, ":0$1" );
+	css = css.replace( /:0 0 0(;|\})/g, ":0$1" );
+	css = css.replace( /:0 0(;|\})/g, ":0$1" );
+
+	// Replace background-position:0; with background-position:0 0;
+	// same for transform-origin
+	css = css.replace( /(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|\})/gi, function( all, prop, tail ) {
+		return prop.toLowerCase() + ":0 0" + tail;
+	} );
+
+	// Replace 0.6 to .6, but only when preceded by : or a white-space
+	css = css.replace( /(:|\s)0+\.(\d+)/g, "$1.$2" );
+
+	// Shorten colors from rgb(51,102,153) to #336699
+	// This makes it more likely that it'll get further compressed in the next step.
+	css = css.replace( /rgb\s*\(\s*([0-9,\s]+)\s*\)/gi, function() {
+		var i, rgbcolors = arguments[ 1 ].split( ',' );
+		for ( i = 0; i < rgbcolors.length; i = i + 1 ) {
+			rgbcolors[ i ] = parseInt( rgbcolors[ i ], 10 ).toString( 16 );
+			if ( rgbcolors[ i ].length === 1 )
+				rgbcolors[ i ] = '0' + rgbcolors[ i ];
+
+		}
+		return '#' + rgbcolors.join( '' );
+	} );
+
+	// Shorten colors from #AABBCC to #ABC.
+	css = this._compressHexColors( css );
+
+	// border: none -> border:0
+	css = css.replace( /(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\})/gi, function( all, prop, tail ) {
+		return prop.toLowerCase() + ":0" + tail;
+	} );
+
+	// shorter opacity IE filter
+	css = css.replace( /progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=" );
+
+	// Remove empty rules.
+	css = css.replace( /[^\};\{\/]+\{\}/g, "" );
+
+	if ( linebreakpos >= 0 ) {
+		// Some source control tools don't like it when files containing lines longer
+		// than, say 8000 characters, are checked in. The linebreak option is used in
+		// that case to split long lines after a specific column.
+		startIndex = 0;
+		i = 0;
+		while ( i < css.length ) {
+			i = i + 1;
+			if ( css[ i - 1 ] === '}' && i - startIndex > linebreakpos ) {
+				css = css.slice( 0, i ) + '\n' + css.slice( i );
+				startIndex = i;
+			}
+		}
+	}
+
+	// Replace multiple semi-colons in a row by a single one
+	// See SF bug #1980989
+	css = css.replace( /;;+/g, ";" );
+
+	// restore preserved comments and strings
+	for ( i = 0, max = preservedTokens.length; i < max; i = i + 1 ) {
+		css = css.replace( "___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens[ i ] );
+	}
+
+	// Trim the final string (for any leading or trailing white spaces)
+	css = css.replace( /^\s+|\s+$/g, "" );
+
+	return css;
+
+};
\ No newline at end of file
diff --git a/src/lib/image.js b/src/lib/image.js
new file mode 100644
index 0000000..d5601f4
--- /dev/null
+++ b/src/lib/image.js
@@ -0,0 +1,300 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importClass( java.io.File );
+importClass( javax.imageio.ImageIO );
+importClass( java.awt.image.BufferedImage );
+
+( function() {
+	/**
+	 * Iterating through files in directory and when file has one of the following
+	 * png, jpg, gif extension then file absolute path is set as a value and
+	 * file name is set as a key
+	 *
+	 * @param {java.io.File} directory
+	 * @param {Object=} paths
+	 * @private
+	 * @returns {Object}
+	 * @member CKBuilder.image
+	 */
+	function getAbsolutePathForImageFiles( directory, paths ) {
+		paths = paths || {};
+
+		var files = directory.list().sort();
+		for ( var i = 0; i < files.length; i++ ) {
+			var f = new File( directory, files[ i ] );
+
+			if ( f.isFile() ) {
+				var extension = CKBuilder.io.getExtension( files[ i ] );
+				if ( extension === "png" || extension === "jpg" || extension === "gif" ) {
+					var fileName = files[ i ].slice( 0, files[ i ].indexOf( "." ) );
+					paths[ String( fileName ) ] = String( f.getAbsolutePath() );
+				}
+			}
+		}
+
+		return paths;
+	}
+
+	/**
+	 * Responsible for generating sprite with icons.
+	 *
+	 * @class
+	 */
+	CKBuilder.image = {
+		/**
+		 * @param {java.io.File} sourceLocation
+		 * @param {Boolean} hidpi
+		 * @static
+		 */
+		findIcons: function( sourceLocation, hidpi ) {
+			if ( !sourceLocation || !sourceLocation.exists() )
+				return {};
+
+			var result = {},
+				children = sourceLocation.list().sort();
+
+			for ( var i = 0; i < children.length; i++ ) {
+				var child = new File( sourceLocation, children[ i ] );
+
+				// Handle only directories
+				if ( !child.isDirectory() )
+					continue;
+
+				if ( String( children[ i ] ) === "icons" ) {
+					getAbsolutePathForImageFiles( child, result );
+
+					// When the "hidpi" flag is set, overwrite 16px icons with hidpi versions.
+					// Searching above for 16px icons still makes sense, because a plugin may not
+					// provide hidpi icons at all.
+					if ( hidpi ) {
+						var hidpiFolder = new File( child, 'hidpi' );
+						if ( !hidpiFolder.isDirectory() )
+							continue;
+
+						getAbsolutePathForImageFiles( hidpiFolder, result );
+					}
+				// When directory name is not "icons", going deeper.
+				} else {
+					var icons = CKBuilder.image.findIcons( child, hidpi );
+					result = CKBuilder.utils.merge( result, icons );
+				}
+			}
+
+			return result;
+		},
+
+		/**
+		 * Creates a complete sprite, based on passed plugins and skin location.
+		 *
+		 * @param {java.io.File} pluginsLocation
+		 * @param {java.io.File} skinLocation
+		 * @param {java.io.File} outputFile
+		 * @param {java.io.File} outputCssFile
+		 * @param {String[]} pluginNamesSorted
+		 * @param {Boolean} [hidpi=false]
+		 * @returns {*}
+		 * @static
+		 */
+		createFullSprite: function( pluginsLocation, skinLocation, outputFile, outputCssFile, pluginNamesSorted, hidpi ) {
+			var pluginIcons = {};
+
+			// Include all available icons
+			if ( CKBuilder.options.all )
+				pluginIcons = CKBuilder.image.findIcons( pluginsLocation, hidpi );
+
+			// Include in strip image only icons provided by plugins included in core
+			else {
+				for ( var i = 0; i < pluginNamesSorted.length; i++ ) {
+					var pluginName = pluginNamesSorted[ i ],
+						pluginFolder = new File( pluginsLocation, pluginName );
+
+					if ( pluginFolder.exists() && pluginFolder.isDirectory() ) {
+						var result = CKBuilder.image.findIcons( pluginFolder, hidpi );
+						pluginIcons = CKBuilder.utils.merge( result, pluginIcons, true );
+					}
+				}
+			}
+			var skinIcons = CKBuilder.image.findIcons( skinLocation, hidpi ),
+				icons = CKBuilder.utils.merge( pluginIcons, skinIcons, false );
+
+			if ( CKBuilder.options.debug > 1 ) {
+				print( "Generating sprite image" );
+				print( "\n== Plugin names ==\n" );
+				print( pluginNamesSorted.join( "," ) );
+				print( "\n== Plugin icons ==\n" );
+				print( CKBuilder.utils.prettyPrintObject( pluginIcons ) );
+				print( "\n== Skin icons ==\n" );
+				print( CKBuilder.utils.prettyPrintObject( skinIcons ) );
+				print( "\n== Used icons ==\n" );
+				print( CKBuilder.utils.prettyPrintObject( icons ) );
+			}
+
+			var files = Object.keys( pluginIcons )// Map to paths array.
+				.map( function( buttonName ) {
+					return icons[ buttonName ];
+				} )// Sort in paths order, so icon-rtl.png will be before icon.png.
+				.sort()// Map to files array.
+				.map( function( iconPath ) {
+					return new File( iconPath );
+				} );
+
+			return this.createSprite( files, outputFile, outputCssFile, hidpi );
+		},
+
+		/**
+		 * Generate sprite file from given images.
+		 *
+		 * @param {Array} files An array with image files ({java.io.File})
+		 * @param {Boolean} outputFile Where to save sprite image
+		 * @param {java.io.File} outputCssFile Where to save CSS information about buttons
+		 * @param {Boolean} hidpi Whether to create hidpi strip image
+		 * @static
+		 */
+		createSprite: function( files, outputFile, outputCssFile, hidpi ) {
+			if ( !files.length ) {
+				if ( CKBuilder.options.debug )
+					print( "No images given, sprite file will not be created." );
+				return '';
+			}
+
+			var totalHeight = 0,
+				iconsOffset = [],
+				iconsHasRtl = {},
+				minimumIconSpace = hidpi ? 16 : 8,
+				cssRules = [];
+
+			if ( outputCssFile && outputCssFile.exists() )
+				cssRules.push( CKBuilder.io.readFile( outputCssFile ) || "" );
+
+			// Read images
+			var i,
+
+				// each image is an object with keys:
+				// {Boolean} isHidpi
+				// {java.awt.image.BufferedImage} bufferedImage
+				// {String} fileName
+				images = [],
+
+				// while iterating through images there is determined highest icon width and height
+				maxIconWidth = 0,
+				maxIconHeight = 0;
+
+			for ( i = 0; i < files.length; i++ ) {
+				images[ i ] = {
+					isHidpi: String( files[ i ].getAbsolutePath() ).replace( /\\/g, '/' ).indexOf( "/icons/hidpi/" ) !== -1,
+					bufferedImage: ImageIO.read( files[ i ] ),
+					fileName: files[ i ].getName()
+				};
+				images[ i ].width = images[ i ].bufferedImage.getWidth();
+				images[ i ].height = images[ i ].bufferedImage.getHeight();
+
+				// Humm huge images? That's probably not an icon, ignore that file.
+				if ( images[ i ].height > 100 || images[ i ].width > 100 ) {
+					print( "WARNING: cowardly refused to add an image to a sprite because it's too big: " + files[ i ].getAbsolutePath() );
+					images[ i ] = null;
+					continue;
+				}
+				maxIconHeight = Math.max( images[ i ].height, maxIconHeight );
+				maxIconWidth = Math.max( images[ i ].width, maxIconWidth );
+			}
+			// Get rid of images that turned out to be too big
+			images = images.filter( function( image ) {
+				return !!image;
+			} );
+
+			if ( maxIconWidth <= 0 )
+				throw( 'Error while generating sprite image: invalid width (' + maxIconWidth + ')' );
+
+			var cssHidpiPrefix = hidpi ? ".cke_hidpi" : "",
+				iconsStrip = hidpi ? "icons_hidpi.png" : "icons.png";
+
+			for ( i = 0; i < images.length; i++ ) {
+				var buttonName = images[ i ].fileName.match( /.*?(?=\.|-rtl)/ ),
+					buttonSelector = ".cke_button__" + buttonName + '_icon',
+					ypos,
+					backgroundSize,
+					cssBackgroundSize;
+
+				if ( hidpi ) {
+					if ( images[ i ].isHidpi ) {
+						backgroundSize = Math.round( maxIconWidth / 2 ) + "px";
+						cssBackgroundSize = "background-size: " + backgroundSize + " !important;";
+
+						// This is the default value in CKEditor, so it does not make sense to specify it again
+						if ( backgroundSize === '16px' )
+							backgroundSize = "";
+						ypos = totalHeight / 2;
+					} else {
+						backgroundSize = "auto";
+						cssBackgroundSize = "";
+						ypos = totalHeight;
+					}
+				} else {
+					// The icons folder in 3rd party plugins may contain surprises
+					// As a result, the strip image may have unpredictable width
+					// https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/6
+					// Here, with wsc plugin, the strip image had 108px width, so default background-size:16px was invalid
+					// We need to always reset it to auto
+					backgroundSize = "auto";
+					cssBackgroundSize = "";
+					ypos = totalHeight;
+				}
+
+				if ( images[ i ].fileName.indexOf( "-rtl" ) !== -1 ) {
+					iconsHasRtl[ buttonName ] = 1;
+					cssRules.push( ".cke_rtl" + cssHidpiPrefix + " " + buttonSelector + "," + // The "cke_mixed_dir_content" env class is to increase the specificity,
+							// with RTL button in LTR editor.
+							( cssHidpiPrefix ? " " : "" ) + cssHidpiPrefix + " .cke_mixed_dir_content .cke_rtl " + buttonSelector + " {background: url(" + iconsStrip + ") no-repeat 0 -" + ypos + "px !important;" + cssBackgroundSize + "}" );
+					iconsOffset.push( buttonName + '-rtl' );
+					iconsOffset.push( ypos );
+					iconsOffset.push( backgroundSize );
+				} else {
+					var envSelector = ( buttonName in iconsHasRtl ? ".cke_ltr" : "" ) + cssHidpiPrefix;
+					if ( envSelector )
+						envSelector = envSelector + " ";
+					if ( hidpi && buttonName in iconsHasRtl )
+						cssRules.push( ".cke_hidpi .cke_ltr " + buttonSelector + "," );
+
+					cssRules.push( envSelector + buttonSelector + " {background: url(" + iconsStrip + ") no-repeat 0 -" + ypos + "px !important;" + cssBackgroundSize + "}" );
+					iconsOffset.push( buttonName );
+					iconsOffset.push( ypos );
+					iconsOffset.push( backgroundSize );
+				}
+				totalHeight = totalHeight + maxIconHeight + minimumIconSpace;
+			}
+
+			if ( totalHeight <= 0 )
+				throw( 'Error while generating sprite image: invalid height (' + totalHeight + ')' );
+
+			if ( CKBuilder.options.debug )
+				System.out.format( "Sprites generator: %s images. Total height: %spx, width: %spx%n", images.length, totalHeight, maxIconWidth );
+
+			// Create the actual sprite
+			var sprite = new BufferedImage( maxIconWidth, totalHeight, BufferedImage.TYPE_INT_ARGB ),
+				currentY = 0,
+				g = sprite.getGraphics();
+
+			for ( i = 0; i < images.length; i++ ) {
+				// image = BufferedImage object
+				g.drawImage( images[ i ].bufferedImage, 0, currentY, null );
+				currentY = currentY + maxIconHeight + minimumIconSpace;
+			}
+
+			if ( CKBuilder.options.debug )
+				print( "Saving sprite: ", outputFile.getAbsolutePath() );
+
+			ImageIO.write( sprite, "png", outputFile );
+			if ( outputCssFile ) {
+				if ( CKBuilder.options.debug )
+					print( "Saving CSS rules to " + outputCssFile.getAbsolutePath() );
+
+				CKBuilder.io.saveFile( outputCssFile, cssRules.join( CKBuilder.options.leaveCssUnminified ? "\r\n" : "" ) );
+			}
+			return iconsOffset.join( ',' );
+		}
+	};
+
+}() );
diff --git a/src/lib/io.js b/src/lib/io.js
new file mode 100644
index 0000000..b64e465
--- /dev/null
+++ b/src/lib/io.js
@@ -0,0 +1,590 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importPackage( com.ice.tar );
+importPackage( java.util.zip );
+
+importClass( java.io.BufferedReader );
+importClass( java.io.BufferedWriter );
+importClass( java.io.File );
+importClass( java.io.FileWriter );
+importClass( java.io.FileOutputStream );
+importClass( java.io.FileInputStream );
+importClass( java.io.BufferedInputStream );
+importClass( java.io.BufferedOutputStream );
+importClass( java.lang.StringBuffer );
+importClass( java.io.InputStreamReader );
+importClass( java.io.FileOutputStream );
+importClass( java.io.OutputStreamWriter );
+importClass( java.util.zip.ZipOutputStream );
+importClass( java.util.zip.ZipEntry );
+importClass( java.util.zip.GZIPInputStream );
+
+( function() {
+
+	/**
+	 * Creates an archive from specified path.
+	 *
+	 * @param {String} sourceLocation Path Source path
+	 * @param {String} startLocation Path Source path
+	 * @param {java.io.OutputStream} outStream Output stream to which the archive is created
+	 * @param {String} compressMethod The type of the archive (tar.gz|zip)
+	 * @param {String} rootDir The root folder of the archive.
+	 * @member CKBuilder.io
+	 */
+	function compressDirectory( sourceLocation, startLocation, outStream, compressMethod, rootDir ) {
+		if ( CKBuilder.options.debug )
+			print( "    " + compressMethod + ": " + sourceLocation.getAbsolutePath() );
+
+		if ( !rootDir )
+			rootDir = "";
+
+		try {
+			var dirList = sourceLocation.list(),
+				readBuffer = new Packages.java.lang.reflect.Array.newInstance( java.lang.Byte.TYPE, 2056 ),
+				bytesIn = 0,
+				anEntry,
+				fis;
+
+			for ( var i = 0; i < dirList.length; i++ ) {
+				var f = new File( sourceLocation, dirList[ i ] );
+
+				if ( f.isDirectory() ) {
+					compressDirectory( f, startLocation, outStream, compressMethod, rootDir );
+					continue;
+				}
+
+				fis = new FileInputStream( f );
+
+				switch ( compressMethod ) {
+					case 'tar.gz' :
+						anEntry = new TarEntry( f.getCanonicalPath().replace( startLocation.getCanonicalPath(), rootDir ).replace( "\\", "/" ) );
+						break;
+					case 'zip' :
+						anEntry = new ZipEntry( f.getCanonicalPath().replace( startLocation.getCanonicalPath(), rootDir ).replace( "\\", "/" ) );
+						break;
+					default:
+						throw "Unknown compression method: " + compressMethod;
+				}
+
+				outStream.putNextEntry( anEntry );
+
+				while ( ( bytesIn = fis.read( readBuffer ) ) !== -1 ) {
+					outStream.write( readBuffer, 0, bytesIn );
+				}
+				outStream.closeEntry();
+
+				fis.close();
+			}
+		} catch ( e ) {
+			throw "An error occurred during (" + compressMethod + ") compression of " + sourceLocation.getAbsolutePath() + ": " + e;
+		}
+	}
+
+	/**
+	 * Copy file from source to target location.
+	 *
+	 * @param {String} sourceLocation Source folder
+	 * @param {String} targetLocation Target folder
+	 * @member CKBuilder.io
+	 */
+	function copyFile( sourceLocation, targetLocation ) {
+		try {
+			var inStream = new FileInputStream( sourceLocation ),
+				outStream = new FileOutputStream( targetLocation );
+
+			if ( CKBuilder.options.debug > 1 )
+				print( "Copying file: " + sourceLocation.getCanonicalPath() );
+
+			var len,
+				buf = new Packages.java.lang.reflect.Array.newInstance( java.lang.Byte.TYPE, 1024 );
+
+			while ( ( len = inStream.read( buf ) ) !== -1 )
+				outStream.write( buf, 0, len );
+
+			inStream.close();
+			outStream.close();
+
+			if ( CKBuilder.options.debug > 1 )
+				print( "File copied: " + targetLocation.getCanonicalPath() );
+		} catch ( e ) {
+			throw "Cannot copy file:\n Source: " + sourceLocation.getCanonicalPath() + "\n Destination : " + targetLocation.getCanonicalPath() + "\n" + e.message;
+		}
+	}
+
+	/**
+	 * Input output actions. Copy, delete files and directories. Save them, show directory info.
+	 *
+	 * @class
+	 */
+	CKBuilder.io = {
+		/**
+		 * This method is preventable depending on callback value.
+		 * When callback returns false value then nothing changes.
+		 *
+		 * @static
+		 * @param {java.io.File} sourceLocation
+		 * @param {java.io.File} targetLocation
+		 * @param {function(java.io.File, java.io.File):Boolean} callback
+		 */
+		copyFile: function( sourceLocation, targetLocation, callback ) {
+			if ( callback ) {
+				if ( !callback.call( this, sourceLocation, targetLocation ) )
+					return;
+			}
+			copyFile( sourceLocation, targetLocation );
+		},
+
+		/**
+		 * Unzips a file recursively.
+		 *
+		 * @param {String} zipFile Path to the source file
+		 * @param {String|java.io.File} newPath Path to the destination folder
+		 * @static
+		 */
+		unzipFile: function( zipFile, newPath ) {
+			try {
+				var BUFFER = 2048,
+					file = new File( zipFile ),
+					zip = new ZipFile( file );
+
+				newPath = newPath || zipFile.substring( 0, zipFile.length() - 4 );
+				new File( newPath ).mkdir();
+				var zipFileEntries = zip.entries();
+
+				// Process each entry
+				while ( zipFileEntries.hasMoreElements() ) {
+					// grab a zip file entry
+					var entry = zipFileEntries.nextElement(),
+						currentEntry = entry.getName(),
+						destFile = new File( newPath, currentEntry ),
+						destinationParent = destFile.getParentFile();
+
+					// create the parent directory structure if needed
+					destinationParent.mkdirs();
+
+					if ( !entry.isDirectory() ) {
+						var is = new BufferedInputStream( zip.getInputStream( entry ) ),
+
+						// establish buffer for writing file
+							data = new Packages.java.lang.reflect.Array.newInstance( java.lang.Byte.TYPE, BUFFER ),
+
+						// write the current file to disk
+							fos = new FileOutputStream( destFile ),
+							dest = new BufferedOutputStream( fos, BUFFER );
+
+						// read and write until last byte is encountered
+						var currentByte;
+						while ( ( currentByte = is.read( data, 0, BUFFER ) ) !== -1 ) {
+							dest.write( data, 0, currentByte );
+						}
+						dest.flush();
+						dest.close();
+						is.close();
+					}
+
+					if ( currentEntry.endsWith( ".zip" ) ) {
+						// found a zip file, try to open
+						CKBuilder.io.unzipFile( destFile.getAbsolutePath() );
+					}
+				}
+			} catch ( e ) {
+				throw "Unable to extract archive file:\n Source: " + zipFile + "\n" + e.message;
+			}
+		},
+
+		/**
+		 * Deletes a directory.
+		 *
+		 * @param {java.io.File} path Directory to delete
+		 * @static
+		 */
+		deleteDirectory: function( path ) {
+			var dir = new File( path );
+
+			if ( !dir.exists() )
+				return true;
+
+			if ( dir.isDirectory() ) {
+				var children = dir.list();
+				for ( var i = 0; i < children.length; i++ ) {
+					if ( !this.deleteDirectory( new File( dir, children[ i ] ) ) )
+						return false;
+				}
+			}
+
+			return dir['delete']();
+		},
+
+		/**
+		 * Deletes a file.
+		 *
+		 * @param {java.io.File} path File to delete
+		 * @static
+		 */
+		deleteFile: function( path ) {
+			var f = new File( path );
+
+			if ( !f.exists() )
+				return true;
+
+			if ( !f.canWrite() )
+				throw "Cannot delete file: " + f.getAbsolutePath();
+
+			return f[ "delete" ]();
+		},
+
+		/**
+		 * Saves a file.
+		 *
+		 * @param {java.io.File} file Path to the file
+		 * @param {String} text Content of a file
+		 * @param {Boolean} [includeBom=false] includeBom Whether to include BOM character
+		 * @static
+		 */
+		saveFile: function( file, text, includeBom ) {
+			includeBom = ( includeBom === true );
+
+			try {
+				var stream = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file ), "UTF-8" ) );
+				if ( includeBom )
+					stream.write( 65279 );
+
+				stream.write( text );
+				stream.flush();
+				stream.close();
+			} catch ( e ) {
+				throw "Cannot save file:\n Path: " + file.getCanonicalPath() + "\n Exception details: " + e.message;
+			}
+		},
+
+		/**
+		 * Copies file/folder, with the possibility of ignoring specific paths.
+		 *
+		 * @param {java.io.File} sourceLocation Source location
+		 * @param {java.io.File} targetLocation Target location
+		 * @param {function(java.io.File, java.io.File): number} callbackBefore (Optional)
+		 *   The possible returned values are:
+		 *
+		 *   -1 Do not copy file, do not call callbackAfter.
+		 *
+		 *   0 Copy file, call callbackAfter.
+		 *
+		 *   1 File was already copied, call callbackAfter.
+		 * @param {Function} callbackAfter (Optional) Callback function executed after the file is copied.
+		 * @static
+		 */
+		copy: function( sourceLocation, targetLocation, callbackBefore, callbackAfter ) {
+			if ( callbackBefore ) {
+				var code = callbackBefore.call( this, sourceLocation, targetLocation );
+				if ( code === -1 )
+					return;
+				if ( callbackAfter )
+					callbackAfter.call( this, targetLocation );
+				if ( code === 1 )
+					return;
+			}
+
+			if ( sourceLocation.isDirectory() ) {
+				if ( !targetLocation.exists() )
+					targetLocation.mkdir();
+
+				var children = sourceLocation.list();
+				for ( var i = 0; i < children.length; i++ ) {
+					if ( String( children[ i ] ) === ".svn" || String( children[ i ] ) === "CVS" || String( children[ i ] ) === ".git" )
+						continue;
+
+					this.copy( new File( sourceLocation, children[ i ] ), new File( targetLocation, children[ i ] ), callbackBefore, callbackAfter );
+				}
+
+				if ( !targetLocation.list().length )
+					targetLocation[ 'delete' ]();
+			} else {
+				copyFile( sourceLocation, targetLocation );
+				if ( callbackAfter )
+					callbackAfter.call( this, targetLocation );
+			}
+		},
+
+		/**
+		 * Creates a zip archive from specified location.
+		 *
+		 * @param {java.io.File} sourceLocation The location of the folder to compress.
+		 * @param {java.io.File} startLocation Starting from this location the folder structure will be replicated.
+		 * The startLocation should be a subfolder of sourceLocation.
+		 * @param {java.io.File} targetFile The location of the target zip file.
+		 * @param {String} rootDir The name of root folder in which the rest of files will be placed
+		 * @static
+		 */
+		zipDirectory: function( sourceLocation, startLocation, targetFile, rootDir ) {
+			var outStream = new ZipOutputStream( new FileOutputStream( targetFile ) );
+			compressDirectory( sourceLocation, startLocation, outStream, 'zip', rootDir );
+			outStream.close();
+		},
+
+		/**
+		 * Creates a tar.gz archive from specified location.
+		 *
+		 * @param {java.io.File} sourceLocation The location of the folder to compress.
+		 * @param {java.io.File} startLocation Starting from this location the folder structure will be replicated.
+		 * The startLocation should be a subfolder of sourceLocation.
+		 * @param {java.io.File} targetFile The location of the target tar.gz file.
+		 * @param {String} rootDir The name of root folder in which the rest of files will be placed
+		 * @static
+		 */
+		targzDirectory: function( sourceLocation, startLocation, targetFile, rootDir ) {
+			var outStream = new TarGzOutputStream( new FileOutputStream( targetFile ) );
+			compressDirectory( sourceLocation, startLocation, outStream, 'tar.gz', rootDir );
+			outStream.close();
+		},
+
+		/**
+		 * Sets or removes the BOM character at the beginning of the file.
+		 *
+		 * @param {String} file Path to the file
+		 * @param {Boolean} includeUtf8Bom Boolean value indicating whether the BOM character should exist
+		 * @static
+		 */
+		setByteOrderMark: function( file, includeUtf8Bom ) {
+			var buffer = new StringBuffer(),
+				chars = new Packages.java.lang.reflect.Array.newInstance( java.lang.Character.TYPE, 32 ),
+				inStream;
+
+			try {
+				inStream = new InputStreamReader( new FileInputStream( file ), 'UTF-8' );
+			} catch ( e ) {
+				throw 'An I/O error occurred while opening the ' + file + ' file.';
+			}
+
+			try {
+				var count = inStream.read( chars, 0, 32 );
+
+				if ( count <= 0 )
+					return;
+
+				buffer.append( chars, 0, count );
+
+				/* BOM is at the beginning of file */
+				if ( buffer.length() && buffer.charAt( 0 ) === 65279 ) {
+					if ( !includeUtf8Bom ) {
+						if ( CKBuilder.options.debug )
+							print( 'Removing BOM from ' + file.getCanonicalPath() );
+						this.saveFile( file, this.readFile( file ) );
+					}
+				} else {
+					if ( includeUtf8Bom ) {
+						if ( CKBuilder.options.debug )
+							print( 'Adding BOM to ' + file.getCanonicalPath() );
+						this.saveFile( file, this.readFile( file ), true );
+					}
+				}
+			} catch ( e ) {
+				throw 'An I/O error occurred while reading the ' + file.getCanonicalPath() + ' file.';
+			} finally {
+				inStream.close();
+			}
+		},
+
+		/**
+		 * Reads files from given array and returns joined file contents.
+		 *
+		 * @param {java.io.File[]} files The list of files to read.
+		 * @returns {String}
+		 * @static
+		 */
+		readFiles: function( files, separator ) {
+			var i,
+				out = [];
+
+			for ( i = 0; i < files.length; i++ ) {
+				out.push( this.readFile( files[ i ] ) );
+			}
+
+			return out.join( separator ? separator : "" );
+		},
+
+		/**
+		 * Reads file and returns file contents without initial UTF-8 Byte Order.
+		 *
+		 * Mark
+		 * @param {java.io.File} file
+		 * @returns {String}
+		 * @static
+		 */
+		readFile: function( file ) {
+			var buffer = new StringBuffer(),
+				chars = new Packages.java.lang.reflect.Array.newInstance( java.lang.Character.TYPE, 8192 ),
+				count,
+				fis,
+				inStream;
+
+			if ( !file.exists() )
+				throw 'File ' + file + ' does not exist.';
+
+			try {
+				fis = new FileInputStream( file );
+				inStream = new InputStreamReader( fis, 'UTF-8' );
+			} catch ( e ) {
+				throw 'An I/O error occurred while opening the ' + file + ' file.';
+			}
+
+			try {
+				while ( ( count = inStream.read( chars, 0, 8192 ) ) !== -1 ) {
+					if ( count > 0 )
+						buffer.append( chars, 0, count );
+				}
+			} catch ( e ) {
+				throw 'An I/O error occurred while reading the ' + file.getCanonicalPath() + ' file.';
+			} finally {
+				fis.close();
+				inStream.close();
+			}
+
+			/* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 */
+			if ( buffer.length() && buffer.charAt( 0 ) === 65279 )
+				buffer.deleteCharAt( 0 );
+
+			return String( buffer.toString() );
+		},
+
+		/**
+		 * Reads file from within .jar file and returns file contents without initial UTF-8 Byte Order Mark.
+		 *
+		 * @param {String} path Path to the file
+		 * @returns {String}
+		 * @static
+		 */
+		readFileFromJar: function( path ) {
+			var buffer = new StringBuffer(),
+				chars = new Packages.java.lang.reflect.Array.newInstance( java.lang.Character.TYPE, 8192 ),
+				fis,
+				inStream;
+
+			try {
+				fis = java.lang.Class.forName( "ckbuilder.ckbuilder" ).getClassLoader().getResourceAsStream( path );
+				inStream = new InputStreamReader( fis, "UTF-8" );
+			} catch ( e ) {
+				throw 'An I/O error occurred while opening the ' + path + ' file.';
+			}
+
+			try {
+				var count;
+				while ( ( count = inStream.read( chars, 0, 8192 ) ) !== -1 ) {
+					if ( count > 0 )
+						buffer.append( chars, 0, count );
+				}
+			} catch ( e ) {
+				throw 'An I/O error occurred while reading the ' + path + ' file.';
+			} finally {
+				fis.close();
+				inStream.close();
+			}
+
+			/* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 */
+			if ( buffer.length() && buffer.charAt( 0 ) === 65279 )
+				buffer.deleteCharAt( 0 );
+
+			return String( buffer.toString() );
+		},
+
+		/**
+		 * Returns size and number of files in the specified directory.
+		 *
+		 * @param {String} path Path to the folder
+		 * @returns {{files: Number, size: Number}}
+		 * @static
+		 */
+		getDirectoryInfo: function( path ) {
+			var result = {
+					files: 0,
+					size: 0
+				};
+
+			if ( !path.exists() )
+				return result;
+
+			var files = path.listFiles();
+
+			if ( !files )
+				return result;
+
+			var path_iterator = ( java.util.Arrays.asList( files ) ).iterator();
+
+			while ( path_iterator.hasNext() ) {
+				var current_file = path_iterator.next();
+				if ( current_file.isFile() ) {
+					result.size += current_file.length();
+					result.files++;
+				} else {
+					var info = this.getDirectoryInfo( current_file );
+					result.size += info.size;
+					result.files += info.files;
+				}
+			}
+
+			return result;
+		},
+
+		/**
+		 * Returns the (lower-cased) extension of the file from the specified path (e.g. "txt").
+		 *
+		 * @param {String} fileName The file name
+		 * @returns {String}
+		 * @static
+		 */
+		getExtension: function( fileName ) {
+			var pos = fileName.lastIndexOf( "." );
+
+			if ( pos === -1 )
+				return "";
+			else
+				return String( fileName.substring( pos + 1 ).toLowerCase() );
+		},
+
+		/**
+		 * Check element wether its a zip file. If yes, then extract it into temporary directory.
+		 *
+		 * @param {java.io.File|String} element Directory or zip file.
+		 * @returns {java.io.File} Directory on which work will be done.
+		 */
+		prepareWorkingDirectoryIfNeeded: function( element ) {
+			var elementLocation = new File( element ),
+				tmpDir,
+				workingDir,
+				isTemporary = false;
+			if ( !elementLocation.isDirectory() ) { //is not a directory
+				if ( CKBuilder.io.getExtension( element ) !== "zip" ) // is not a zip
+					throw( "The element file is not a zip file: " + elementLocation.getCanonicalPath() );
+
+				// temporary directory
+				tmpDir = new File( System.getProperty( "java.io.tmpdir" ), ".tmp" + Math.floor( ( Math.random() * 1000000 ) + 1 ) );
+				// cleaning up dir
+				if ( tmpDir.exists() && !CKBuilder.io.deleteDirectory( tmpDir ) )
+					throw( "Unable to delete tmp dir: " + tmpDir.getCanonicalPath() );
+
+				try {
+					tmpDir.mkdirs(); // creating temporary directory
+				} catch ( e ) {
+					throw( "Unable to create temp directory: " + tmpDir.getAbsolutePath() + "\nError: " + e.getMessage() );
+				}
+
+				// unzip into temp directory
+				CKBuilder.io.unzipFile( element, tmpDir );
+				isTemporary = true;
+				workingDir = tmpDir;
+			} else
+				workingDir = elementLocation;
+
+			return {
+				directory: workingDir,
+				cleanUp: function () {
+					if ( isTemporary && workingDir.exists() )
+						CKBuilder.io.deleteDirectory( workingDir );
+				}
+			};
+		}
+
+	};
+}() );
diff --git a/src/lib/javascript.js b/src/lib/javascript.js
new file mode 100644
index 0000000..46c6b72
--- /dev/null
+++ b/src/lib/javascript.js
@@ -0,0 +1,171 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importClass( java.io.File );
+importClass( java.lang.System );
+importPackage( java.util.regex );
+importClass( java.util.regex.Pattern );
+importClass( java.util.regex.Matcher );
+
+importClass( com.google.javascript.jscomp.CompilationLevel );
+importClass( com.google.javascript.jscomp.Compiler );
+importClass( com.google.javascript.jscomp.CompilerOptions );
+importClass( com.google.javascript.jscomp.SourceFile );
+
+( function() {
+
+	/**
+	 * Compile JavaScript file.
+	 *
+	 * @param {java.io.File} file
+	 * http://closure-compiler.googlecode.com/svn/trunk/javadoc/index.html
+	 * @member CKBuilder.javascript
+	 * @private
+	 * @returns {String}
+	 */
+	function compileFile( file ) {
+		var compiler = new Compiler();
+		compiler.setLoggingLevel( java.util.logging.Level.WARNING );
+
+		// http://closure-compiler.googlecode.com/svn/trunk/javadoc/index.html
+		// http://closure-compiler.googlecode.com/svn/trunk/javadoc/com/google/javascript/jscomp/CompilerOptions.html
+		var options = new CompilerOptions();
+
+		// Otherwise strings in language files are escaped as \u1234 making them larger
+		options.outputCharset = 'UTF-8';
+
+		CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel( options );
+
+		// This is required in order to compile JS files with JSC_TRAILING_COMMA errors
+		options.setWarningLevel( com.google.javascript.jscomp.DiagnosticGroups.INTERNET_EXPLORER_CHECKS, CKBuilder.options.noIeChecks ? com.google.javascript.jscomp.CheckLevel.OFF : com.google.javascript.jscomp.CheckLevel.WARNING );
+
+		options.setWarningLevel(
+			com.google.javascript.jscomp.DiagnosticGroups.NON_STANDARD_JSDOC,
+			com.google.javascript.jscomp.CheckLevel.OFF
+		);
+
+		options.setWarningLevel(
+			com.google.javascript.jscomp.DiagnosticGroups.MISPLACED_TYPE_ANNOTATION,
+			com.google.javascript.jscomp.CheckLevel.OFF
+		);
+
+		// To get the complete set of externs, the logic in
+		// CompilerRunner.getDefaultExterns() should be used here.
+		var extern = SourceFile.fromCode( "externs.js", "function PACKAGER_RENAME() {}" ),
+
+			// The dummy input name "input.js" is used here so that any warnings or
+			// errors will cite line numbers in terms of input.js.
+			input = SourceFile.fromCode( file.getName(), CKBuilder.io.readFile( file ) ),
+
+			// compile() returns a Result, but it is not needed here.
+			result = compiler.compile( extern, input, options );
+
+		if ( result.success )
+			return compiler.toSource();
+		else
+			throw( "Unable to compile file: " + file.getAbsolutePath() );
+	}
+
+	/**
+	 * Handle javascript files. Minify them, remove white spaces and find errors.
+	 *
+	 * @class
+	 */
+	CKBuilder.javascript = {
+		/**
+		 * Finds errors in given code.
+		 *
+		 * @param {String} code JavaScript code
+		 * @param fileName The name of the file from which the code has been taken (used only to build error messages).
+		 * @returns {Array|null}
+		 * @static
+		 */
+		findErrors: function( code, fileName ) {
+			var compiler = new Compiler();
+			compiler.setLoggingLevel( java.util.logging.Level.OFF );
+
+			var options = new CompilerOptions();
+			options.outputCharset = 'UTF-8';
+			CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel( options );
+
+			// To get the complete set of externs, the logic in
+			// CompilerRunner.getDefaultExterns() should be used here.
+			var extern = SourceFile.fromCode( "externs.js", "function PACKAGER_RENAME() {}" ),
+
+				// The dummy input name "input.js" is used here so that any warnings or
+				// errors will cite line numbers in terms of input.js.
+				input = SourceFile.fromCode( fileName || "input.js", code );
+
+			// compile() returns a Result, but it is not needed here.
+			compiler.compile( extern, input, options );
+
+			var arr = [],
+				errors = compiler.getErrors();
+			for ( var i = 0; i < errors.length; i++ ) {
+				// There are simply too many errors of this kind in various libraries :(
+				/* jshint eqeqeq: false */
+				if ( 'JSC_TRAILING_COMMA' != errors[ i ].getType().key )
+					arr.push( errors[ i ].toString() );
+				/* jshint eqeqeq: true */
+			}
+
+			return arr.length ? arr : null;
+		},
+
+		/**
+		 * Removes white space characters from given code (removes comments and extra whitespace in the input JS).
+		 *
+		 * @param {String} code JavaScript code
+		 * @param {String} fileName The name of the file from which the code has been taken (used only to build error messages).
+		 * @returns {String}
+		 * @static
+		 */
+		removeWhiteSpace: function( code, fileName ) {
+			var compiler = new Compiler();
+			//compiler.setLoggingLevel(java.util.logging.Level.OFF);
+			compiler.setLoggingLevel( java.util.logging.Level.SEVERE );
+			// compiler.setLoggingLevel( java.util.logging.Level.WARNING );
+
+			var options = new CompilerOptions();
+
+			// This is required in order to compile JS files with JSC_TRAILING_COMMA errors
+			options.setWarningLevel(
+				com.google.javascript.jscomp.DiagnosticGroups.INTERNET_EXPLORER_CHECKS,
+				CKBuilder.options.noIeChecks ? com.google.javascript.jscomp.CheckLevel.OFF : com.google.javascript.jscomp.CheckLevel.WARNING
+			);
+
+			// Otherwise strings in language files are escaped as \u1234 making them larger
+			options.outputCharset = 'UTF-8';
+			CompilationLevel.WHITESPACE_ONLY.setOptionsForCompilationLevel( options );
+
+				// To get the complete set of externs, the logic in
+				// CompilerRunner.getDefaultExterns() should be used here.
+			var extern = SourceFile.fromCode( "externs.js", "function PACKAGER_RENAME() {}" ),
+
+				// The dummy input name "input.js" is used here so that any warnings or
+				// errors will cite line numbers in terms of input.js.
+				input = SourceFile.fromCode( fileName || "input.js", code ),
+				result = compiler.compile( extern, input, options );
+
+			if ( result.success )
+				return compiler.toSource(); else
+				throw( "Unable to compile file: " + fileName );
+		},
+
+		/**
+		 * Minify and save specified file.
+		 *
+		 * @param {java.io.File} file
+		 * @static
+		 */
+		minify: function( file ) {
+			if ( CKBuilder.io.getExtension( file.getName() ) !== "js" )
+				throw( "Not a JavaScript file: " + file.getAbsolutePath() );
+
+			CKBuilder.io.saveFile( file, compileFile( file ), true );
+		}
+	};
+
+}() );
diff --git a/src/lib/lang.js b/src/lib/lang.js
new file mode 100644
index 0000000..d20b31b
--- /dev/null
+++ b/src/lib/lang.js
@@ -0,0 +1,144 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+importClass( java.io.BufferedWriter );
+importClass( java.io.FileWriter );
+importClass( java.io.FileOutputStream );
+importClass( java.io.FileInputStream );
+
+( function() {
+	var translations = {};
+
+	/**
+	 * This method modifies translations property.
+	 *
+	 * @param {java.io.File} sourceLocation Source folder. Directory which represents plugin, so is located in `plugins` directory.
+	 * @member CKBuilder.lang
+	 */
+	function loadPluginLanguageFiles( sourceLocation ) {
+		var folder = new File( sourceLocation, 'lang' );
+
+		if ( !folder.exists() )
+			return;
+
+		var englishFile = new File( folder, 'en.js' ),
+			englishObj = CKBuilder.lang.loadLanguageFile( englishFile ).translation;
+
+		for ( var langCode in translations ) {
+			var langFile = new File( folder, langCode + '.js' ),
+				langObj;
+
+			if ( langFile.exists() )
+				langObj = CKBuilder.utils.merge( englishObj, CKBuilder.lang.loadLanguageFile( langFile ).translation );
+			else
+				langObj = englishObj;
+
+			translations[ langCode ] = CKBuilder.utils.merge( translations[ langCode ], langObj );
+		}
+	}
+
+	/**
+	 * @param {java.io.File} folder
+	 * @member CKBuilder.lang
+	 */
+	function loadCoreLanguageFiles( folder ) {
+		translations.en = CKBuilder.lang.loadLanguageFile( new File( folder, "en.js" ) ).translation;
+
+		var children = folder.list();
+		for ( var i = 0; i < children.length; i++ ) {
+			var langFile = children[ i ].match( /^([a-z]{2}(?:-[a-z]+)?)\.js$/ );
+			if ( langFile ) {
+				var langCode = langFile[ 1 ];
+				translations[ langCode ] = CKBuilder.utils.merge( translations.en, CKBuilder.lang.loadLanguageFile( new File( folder, children[ i ] ) ).translation );
+			}
+		}
+	}
+
+	/**
+	 * @param langCode
+	 * @returns {string}
+	 * @member CKBuilder.lang
+	 */
+	function printTranslation( langCode ) {
+		if ( CKBuilder.options.leaveJsUnminified )
+			return CKBuilder.utils.copyright( "\r\n" ) + "CKEDITOR.lang['" + langCode + "'] = {\n" + CKBuilder.utils.prettyPrintObject( translations[ langCode ], '    ' ) + " }; ";
+		else
+			return CKBuilder.utils.copyright( "\n" ) + "CKEDITOR.lang['" + langCode + "']=" + JSON.stringify( translations[ langCode ] ) + ";";
+	}
+
+	/**
+	 * Gather translations from CKEditor, merge them and sace into single file.
+	 *
+	 * @class
+	 */
+	CKBuilder.lang = {
+		/**
+		 * @param {String} sourceLocation Path to the folder with source files
+		 * @param {String} targetLocation The target folder where to save the resulting files
+		 * @param {Object} pluginNames Object with a set of plugins included in build
+		 * @param {Object} languages (Optional) Object with languages included in build (if empty, all languages are used)
+		 * @static
+		 */
+		mergeAll: function( sourceLocation, targetLocation, pluginNames, languages ) {
+			var langLocation = new File( sourceLocation, "lang" );
+			if ( !langLocation.exists() )
+				throw( "Language folder is missing: " + langLocation.getAbsolutePath() );
+
+			var pluginsLocation = new File( sourceLocation, "plugins" );
+			if ( !pluginsLocation.exists() )
+				throw( "Plugins folder is missing: " + pluginsLocation.getAbsolutePath() );
+
+			loadCoreLanguageFiles( langLocation );
+
+			// Load plugins language files
+			var children = pluginsLocation.list();
+			children.sort();
+			for ( var i = 0; i < children.length; i++ ) {
+				var folderName = String( children[ i ] );
+				if ( folderName === ".svn" || folderName === "CVS" || folderName === ".git" )
+					continue;
+				// Do not load language files from plugins that are not enabled.
+				if ( pluginNames[ folderName ] )
+					loadPluginLanguageFiles( new File( pluginsLocation, children[ i ] ) );
+			}
+
+			for ( var langCode in translations ) {
+				if ( !languages || languages[ langCode ] )
+					CKBuilder.io.saveFile( File( targetLocation, langCode + ".js" ), printTranslation( langCode ), true );
+				else
+					CKBuilder.io.deleteFile( File( targetLocation, langCode + ".js" ) );
+			}
+		},
+
+		/**
+		 * Load language file and return an object with the whole translation.
+		 *
+		 * @param {java.io.File} file Language file to load.
+		 * @returns {{languageCode: String, translation: Object }}
+		 * @static
+		 */
+		loadLanguageFile: function( file ) {
+			var translationCode = 'var CKEDITOR = { lang : {}, plugins : { setLang : function(plugin, langCode, obj) { if(!CKEDITOR.lang[langCode]) CKEDITOR.lang[langCode] = {};CKEDITOR.lang[langCode][plugin] = obj; } } }; ' + CKBuilder.io.readFile( file ),
+				cx = Context.enter(),
+				scope = cx.initStandardObjects();
+
+			try {
+				cx.evaluateString( scope, translationCode, file.getName(), 1, null );
+
+				/*
+				 * Return the first entry from scope.CKEDITOR.lang object
+				 */
+				for ( var languageCode in scope.CKEDITOR.lang ) {
+					return {
+						languageCode: languageCode,
+						translation: scope.CKEDITOR.lang[ languageCode ]
+					};
+				}
+			} catch ( e ) {
+				throw( "Language file is invalid: " + file.getAbsolutePath() + ".\nError: " + e.message );
+			}
+		}
+	};
+}() );
diff --git a/src/lib/plugin.js b/src/lib/plugin.js
new file mode 100644
index 0000000..02738e7
--- /dev/null
+++ b/src/lib/plugin.js
@@ -0,0 +1,405 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+( function() {
+	var regexLib = {
+		// requires : [ 'dialogui' ]
+		requiresArray: Pattern.compile( '^\\s*requires\\s*:\\s*\\[\\s*(.*?)\\s*\\]' ),
+		requiresString: Pattern.compile( '^\\s*requires\\s*:\\s*([\'"])\\s*((?:[a-z0-9-_]+|\\s*,\\s*)+?)\\1\\s*' ),
+		// lang : 'af,ar,bg'
+		langString: Pattern.compile( '^(\\s*lang\\s*:\\s*)([\'"])(\\s*(?:[a-z-_]+|\\s*,\\s*)+?)(\\2\\s*.*$)' ),
+		// matches both CKEDITOR.plugins.add( pluginName AND CKEDITOR.plugins.add( 'pluginName'
+		// can be used to detect where "CKEDITOR.plugins.add" is located in code
+		pluginsAdd: Pattern.compile( 'CKEDITOR.plugins.add\\s*\\(\\s*([\'"]?)([a-zA-Z0-9-_]+)\\1', Pattern.DOTALL ),
+		// matches CKEDITOR.plugins.liststyle =
+		pluginsDef: Pattern.compile( 'CKEDITOR.plugins.[a-z-_0-9]+\\s*=\\s*', Pattern.DOTALL ),
+		// matches only CKEDITOR.plugins.add( 'pluginName'
+		// can be used to find the real plugin name, because the name is not stored in a variable but in a string
+		pluginsAddWithStringName: Pattern.compile( 'CKEDITOR.plugins.add\\s*\\(\\s*([\'"])([a-zA-Z0-9-_]+)\\1', Pattern.DOTALL ),
+		pluginName: Pattern.compile( 'var\\s+pluginName\\s*=\\s*([\'"])([a-zA-Z0-9-_]+)\\1', Pattern.DOTALL ),
+		validPluginProps: Pattern.compile( '(^\\s*icons\\s*:\\s*|^\\s*requires\\s*:\\s*|^\\s*lang\\s*:\\s*|^\\s*$|^\\s*//)', Pattern.DOTALL ),
+		blockComments: Pattern.compile( "/\\*[^\\r\\n]*[\\r\\n]+(.*?)[\\r\\n]+[^\\r\\n]*\\*+/", Pattern.DOTALL )
+	};
+
+	/**
+	 * Finds the plugin name in given file (plugin.js).
+	 *
+	 * @param {java.io.File} file
+	 * @returns {String|null}
+	 * @member CKBuilder.plugin
+	 * @private
+	 */
+	function findPluginNameInPluginDefinition( file ) {
+		var pluginName = null,
+			code = CKBuilder.io.readFile( file );
+
+		code = CKBuilder.javascript.removeWhiteSpace( code, file.getParentFile().getName() + "/plugin.js" );
+		var matcher = regexLib.pluginsAddWithStringName.matcher( code );
+		if ( matcher.find() )
+			pluginName = matcher.group( 2 );
+		else {
+			matcher = regexLib.pluginName.matcher( code );
+			if ( matcher.find() )
+				pluginName = matcher.group( 2 );
+		}
+
+		return ( pluginName === null ? pluginName : String( pluginName ) );
+	}
+
+	/**
+	 * Finds the correct plugin.js in given directory.
+	 *
+	 * @param {java.io.File} dir
+	 * @returns {Boolean|String} Path to the right plugin.js file or false.
+	 * @member CKBuilder.plugin
+	*/
+	function findCorrectPluginFile( dir ) {
+		var pluginFiles = CKBuilder.utils.findFilesInDirectory( 'plugin.js', dir ),
+			result = false;
+
+		if ( pluginFiles.length === 1 )
+			result = pluginFiles[ 0 ];
+
+		// let's exclude plugin.js located in the _source or dev folders
+		else if ( pluginFiles.length > 1 ) {
+			var tmpArray = [];
+			for ( var i = 0; i < pluginFiles.length; i++ ) {
+				if ( !pluginFiles[ i ].match( /(\/|\\)(?:_source|dev)\1/i ) )
+					tmpArray.push( pluginFiles[ i ] );
+			}
+
+			if ( tmpArray.length === 1 )
+				result = tmpArray[ 0 ];
+		}
+
+		return result;
+	}
+
+	/**
+	 * Handle plugins. Validate them and preprocess.
+	 *
+	 * @class
+	 */
+	CKBuilder.plugin = {
+		/**
+		 * Returns an array with plugins required by this plugin.
+		 *
+		 * @param {java.io.File} file Plugin file
+		 * @returns {Array}
+		 * @static
+		 */
+		getRequiredPlugins: function( file ) {
+			if ( CKBuilder.options.debug > 1 )
+				print( "Getting required plugins from " + file.getPath() );
+
+			var text = String( CKBuilder.io.readFile( file ) ),
+				// Remove comments
+				matcher = regexLib.blockComments.matcher( text );
+
+			if ( matcher.find() )
+				text = matcher.replaceAll( '' );
+
+			var lines = text.split( "\n" ),
+				pluginsAddFound = false,
+				checkValidPluginProps = false,
+				invalidLinesCounter = 0;
+
+			for ( var i = 0; i < lines.length; i++ ) {
+				if ( !pluginsAddFound ) {
+					matcher = regexLib.pluginsAdd.matcher( lines[ i ] );
+					if ( matcher.find() )
+						pluginsAddFound = true;
+					else {
+						matcher = regexLib.pluginsDef.matcher( lines[ i ] );
+						if ( matcher.find() )
+							pluginsAddFound = true;
+					}
+					if ( pluginsAddFound )
+						invalidLinesCounter = 0;
+				}
+
+				var requires;
+				if ( pluginsAddFound ) {
+					matcher = regexLib.requiresArray.matcher( lines[ i ] );
+					if ( matcher.find() ) {
+						requires = String( matcher.group( 1 ) );
+						if ( CKBuilder.options.debug > 1 )
+							print( "Found: " + matcher.group( 1 ) );
+						return requires.replace( /['" ]/g, '' ).split( "," );
+					}
+
+					matcher = regexLib.requiresString.matcher( lines[ i ] );
+					if ( matcher.find() ) {
+						requires = String( matcher.group( 2 ) );
+						if ( CKBuilder.options.debug > 1 )
+							print( "Found: " + matcher.group( 2 ) );
+						return requires.replace( /['" ]/g, '' ).split( "," );
+					}
+
+					if ( checkValidPluginProps ) {
+						matcher = regexLib.validPluginProps.matcher( lines[ i ] );
+						if ( !matcher.find() )
+							invalidLinesCounter++;
+						if ( invalidLinesCounter > 5 ) {
+							pluginsAddFound = false;
+							checkValidPluginProps = false;
+						}
+					}
+					// we're in the same line where plugin definition has started, start checking from another line
+					else
+						checkValidPluginProps = true;
+				}
+			}
+			return [];
+		},
+
+		/**
+		 * Updates lang property in file.
+		 *
+		 * @param {java.io.File} sourceLocation
+		 * @param {Object} languages
+		 * @returns {Array|Boolean}
+		 * @static
+		 */
+		updateLangProperty: function( sourceLocation, languages ) {
+			var text = String( CKBuilder.io.readFile( sourceLocation ) ),
+				lines = text.split( "\n" ),
+				pluginsAddFound = false,
+				checkValidPluginProps = false,
+				langPropertyChanged = false,
+				invalidLinesCounter = 0,
+				validLanguages;
+
+			for ( var i = 0; i < lines.length; i++ ) {
+				var matcher;
+				if ( !pluginsAddFound ) {
+					matcher = regexLib.pluginsAdd.matcher( lines[ i ] );
+
+					if ( matcher.find() )
+						pluginsAddFound = true;
+					else {
+						matcher = regexLib.pluginsDef.matcher( lines[ i ] );
+						if ( matcher.find() )
+							pluginsAddFound = true;
+					}
+					if ( pluginsAddFound )
+						invalidLinesCounter = 0;
+				}
+
+				if ( pluginsAddFound ) {
+					matcher = regexLib.langString.matcher( lines[ i ] );
+					if ( matcher.find() ) {
+						var pluginLanguages = String( matcher.group( 3 ) ).replace( /['" ]/g, '' ).split( "," );
+
+						validLanguages = [];
+
+						for ( var langCode in languages ) {
+							if ( languages[ langCode ] && pluginLanguages.indexOf( langCode ) !== -1 )
+								validLanguages.push( langCode );
+
+						}
+						// better to change the lang property only if we're able to find some matching language files...
+						if ( validLanguages.length ) {
+							if ( validLanguages.length !== pluginLanguages.length ) {
+								lines[ i ] = matcher.group( 1 ) + matcher.group( 2 ) + validLanguages.join( ',' ) + matcher.group( 4 );
+								langPropertyChanged = true;
+							} else
+								return true;
+
+						}
+					}
+					if ( checkValidPluginProps ) {
+						matcher = regexLib.validPluginProps.matcher( lines[ i ] );
+						if ( !matcher.find() )
+							invalidLinesCounter++;
+
+						if ( invalidLinesCounter > 5 ) {
+							pluginsAddFound = false;
+							checkValidPluginProps = false;
+						}
+					}
+					// We're in the same line where plugin definition has started, start checking from another line.
+					else
+						checkValidPluginProps = true;
+				}
+			}
+			if ( langPropertyChanged ) {
+				if ( CKBuilder.options.debug > 1 )
+					print( "Updated lang property in " + sourceLocation.getPath() );
+
+				CKBuilder.io.saveFile( sourceLocation, lines.join( "\r\n" ), true );
+				return validLanguages;
+			}
+
+			return false;
+		},
+
+		/**
+		 * Checks specified plugin for errors.
+		 *
+		 * @param {java.io.File|String} plugin Path to the plugin (or the java.io.File object pointing to a plugin file).
+		 * @param {Object=} options
+		 * @param {Boolean=} options.exitOnError
+		 * @param {String=} options.pluginName
+		 * @returns {String}
+		 * @static
+		 */
+		verify: function( plugin, options ) {
+			var errors = "",
+				workingDirObj = CKBuilder.io.prepareWorkingDirectoryIfNeeded( plugin ),
+				workingDir = workingDirObj.directory;
+
+			if ( CKBuilder.options.debug > 1 )
+				print( "Validating JS files" );
+
+			errors += CKBuilder.tools.validateJavaScriptFiles( workingDir );
+			errors += CKBuilder.tools.validateJavaScriptFilesUsingCC( workingDir );
+
+			if ( !errors ) {
+				var pluginPath = findCorrectPluginFile( workingDir );
+				if ( !pluginPath ) {
+					// check why findCorrectPluginFile() returned false
+					var pluginPaths = CKBuilder.utils.findFilesInDirectory( 'plugin.js', workingDir );
+					if ( pluginPaths.length > 1 ) {
+						var tmpArray = [],
+							workingDirPath = workingDir.getAbsolutePath();
+
+						for ( var i = 0; i < pluginPaths.length; i++ ) {
+							pluginPaths[ i ] = String( pluginPaths[ i ].replace( workingDirPath, '' ) ).replace( /\\/g, '/' );
+							if ( !pluginPaths[ i ].match( /(\/|\\)(?:_source|dev)\1/i ) )
+								tmpArray.push( pluginPaths[ i ] );
+						}
+						if ( !tmpArray.length )
+							errors += "Could not find plugin.js:\n" + pluginPaths.join( "\n" ) + "\n";
+						else if ( tmpArray.length > 1 )
+							errors += "Found more than one plugin.js:\n" + pluginPaths.join( "\n" ) + "\n";
+					} else
+						errors += "Unable to locate plugin.js" + "\n";
+				} else {
+					if ( options && options.pluginName ) {
+						var pluginName = findPluginNameInPluginDefinition( new File( pluginPath ) );
+						if ( pluginName && pluginName !== options.pluginName )
+							errors += "The plugin name defined inside plugin.js (" + pluginName + ") does not match the expected plugin name (" + options.pluginName + ")" + "\n";
+					}
+				}
+			}
+
+			workingDirObj.cleanUp();
+
+			if ( errors && options && options.exitOnError )
+				System.exit( 500 );
+
+			return errors ? errors : "OK";
+		},
+
+		/**
+		 * Preprocesses the specified plugin and saves in an optimized form in the target folder.
+		 *
+		 * @param {String} plugin Path to the plugin
+		 * @param {String} dstDir Path to the destination folder
+		 * @static
+		 */
+		preprocess: function( plugin, dstDir ) {
+			var workingDirObj = CKBuilder.io.prepareWorkingDirectoryIfNeeded( plugin ),
+				workingDir = workingDirObj.directory;
+
+			if ( this.verify( workingDir, { exitOnError: false } ) !== "OK" ) {
+				workingDirObj.cleanUp();
+				throw( "The plugin is invalid" );
+			}
+
+			var pluginPath = findCorrectPluginFile( workingDir );
+			if ( !pluginPath ) {
+				workingDirObj.cleanUp();
+				throw( "The plugin file (plugin.js) was not found in " + workingDir.getCanonicalPath() );
+			}
+
+			var pluginFile = new File( pluginPath ),
+				targetFolder = new File( dstDir );
+
+			try {
+				targetFolder.mkdirs();
+			} catch ( e ) {
+				workingDirObj.cleanUp();
+				throw( "Unable to create target directory: " + targetFolder.getAbsolutePath() + "\nError: " + e.getMessage() );
+			}
+
+			var flags = {},
+				rootFolder = pluginFile.getParentFile();
+
+			CKBuilder.io.copy( rootFolder, targetFolder, function( sourceLocation, targetLocation ) {
+					if ( sourceLocation.isFile() ) {
+						// Manifest file is converted later to a "php.ini" format and saved as manifest.mf
+						if ( String( sourceLocation.getAbsolutePath() ) === String( File( rootFolder, "manifest.js" ).getAbsolutePath() ) )
+							return -1;
+
+						var copied = CKBuilder.tools.fixLineEndings( sourceLocation, targetLocation );
+						if ( copied ) {
+							// Do not process any directives
+							if ( CKBuilder.options.leaveJsUnminified )
+								return 1;
+
+							var flag = CKBuilder.tools.processDirectives( targetLocation, null, true );
+							if ( flag.LEAVE_UNMINIFIED )
+								flags[ targetLocation.getAbsolutePath() ] = flag;
+
+							return 1;
+						}
+					} else {
+						if ( !CKBuilder.options.leaveJsUnminified && String( sourceLocation.getAbsolutePath() ) === String( File( rootFolder, "lang" ).getAbsolutePath() ) )
+							return -1;
+					}
+					return 0;
+				}, function( targetLocation ) {
+					if ( CKBuilder.options.leaveJsUnminified )
+						return;
+
+					if ( CKBuilder.io.getExtension( targetLocation.getName() ) === 'js' ) {
+						var targetPath = targetLocation.getAbsolutePath();
+						if ( flags[ targetPath ] && flags[ targetPath ].LEAVE_UNMINIFIED ) {
+							if ( CKBuilder.options.debug > 1 )
+								print( "Leaving unminified: " + targetLocation.getPath() );
+
+							CKBuilder.io.saveFile( targetLocation, CKBuilder.tools.removeLicenseInstruction( CKBuilder.io.readFile( targetLocation ) ), true );
+							return;
+						}
+						// remove @license information from files that will go into ckeditor.js (plugin.js)
+						if ( String( targetPath ) === String( File( targetFolder, "plugin.js" ).getAbsolutePath() ) ) {
+							if ( CKBuilder.options.debug > 2 )
+								print( "Removing license information from " + targetPath );
+							CKBuilder.io.saveFile( targetLocation, CKBuilder.tools.removeLicenseInstruction( CKBuilder.io.readFile( targetLocation ) ), true );
+						}
+
+						if ( CKBuilder.options.debug )
+							print( "Minifying: " + targetLocation.getPath() );
+
+						CKBuilder.javascript.minify( targetLocation );
+					}
+				} );
+
+			var langFolder = new File( rootFolder, "lang" ),
+				targetLangFolder = new File( targetFolder, "lang" );
+			if ( !CKBuilder.options.leaveJsUnminified && langFolder.exists() ) {
+				targetLangFolder.mkdir();
+				var translations = {};
+				print( "Processing lang folder" );
+				translations.en = CKBuilder.lang.loadLanguageFile( new File( langFolder, "en.js" ) ).translation;
+				var children = langFolder.list();
+				for ( var i = 0; i < children.length; i++ ) {
+					var langFile = children[ i ].match( /^([a-z]{2}(?:-[a-z]+)?)\.js$/ );
+					if ( langFile ) {
+						var langCode = langFile[ 1 ];
+						translations[ langCode ] = CKBuilder.utils.merge( translations.en, CKBuilder.lang.loadLanguageFile( new File( langFolder, children[ i ] ) ).translation );
+						var pseudoObject = JSON.stringify( translations[ langCode ] ).replace( /^\{(.*)\}$/, '$1' );
+						CKBuilder.io.saveFile( File( targetLangFolder, children[ i ] ), pseudoObject, true );
+					}
+				}
+			}
+
+			workingDirObj.cleanUp();
+		}
+	};
+
+}() );
diff --git a/src/lib/samples.js b/src/lib/samples.js
new file mode 100644
index 0000000..a763d25
--- /dev/null
+++ b/src/lib/samples.js
@@ -0,0 +1,233 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+( function() {
+	var regexLib = {
+		PluginsSamples: Pattern.compile( '<!--\\s*PLUGINS_SAMPLES\\s*?-->', Pattern.DOTALL ),
+		AdvancedSamples: Pattern.compile( '<!--\\s*ADVANCED_SAMPLES\\s*-->', Pattern.DOTALL ),
+		InlineEditingSamples: Pattern.compile( '<!--\\s*INLINE_EDITING_SAMPLES\\s*-->', Pattern.DOTALL ),
+		metaTag: Pattern.compile( '<meta(.*?)>', Pattern.DOTALL ),
+		metaName: Pattern.compile( 'name="(.*?)"', Pattern.DOTALL ),
+		metaContent: Pattern.compile( 'content="(.*?)"', Pattern.DOTALL )
+	};
+	/**
+	 * Holds URLs, names and descriptions of samples found in meta tags.
+	 *
+	 * @property {Object} samplesMetaInformation
+	 * @member CKBuilder.samples
+	 */
+	var samplesMetaInformation = {
+		'beta': {},
+		'new': {},
+		'normal': {}
+	};
+
+	/**
+	 * Returns an information gathered from the <meta> tag in HTML.
+	 *
+	 * @param {String} text
+	 * @returns {Object}
+	 * @private
+	 * @member CKBuilder.samples
+	 */
+	function getMetaInformation( text ) {
+		var matcher = regexLib.metaTag.matcher( text ),
+			metaInformation = {};
+
+		while ( matcher.find() ) {
+			var metaText = matcher.group( 1 ),
+				metaNameMatcher = regexLib.metaName.matcher( metaText ),
+				metaContentMatcher = regexLib.metaContent.matcher( metaText );
+
+			if ( metaContentMatcher.find() && metaNameMatcher.find() )
+				metaInformation[ String( metaNameMatcher.group( 1 ) ).replace( /^ckeditor-sample-/, '' ) ] = String( metaContentMatcher.group( 1 ) );
+
+		}
+
+		if ( !metaInformation.group || ( metaInformation.group !== 'Inline Editing' && metaInformation.group !== 'Advanced Samples' ) )
+			metaInformation.group = 'Plugins';
+
+		return metaInformation;
+	}
+
+	/**
+	 * Checks every plugin folder for the "samples" directory, moves the samples into the root "samples directory.
+	 *
+	 * @param {java.io.File} sourceLocation
+	 * @private
+	 * @member CKBuilder.samples
+	 */
+	function mergePluginSamples( sourceLocation ) {
+		var pluginsLocation = new File( sourceLocation, "plugins" );
+		if ( !pluginsLocation.exists() )
+			return;
+
+		var children = pluginsLocation.list();
+		children.sort();
+		for ( var i = 0; i < children.length; i++ ) {
+			if ( String( children[ i ] ) === ".svn" || String( children[ i ] ) === "CVS" || String( children[ i ] ) === ".git" )
+				continue;
+
+			// Find the "samples" folder
+			var pluginSamplesLocation = new File( pluginsLocation, children[ i ] + '/samples' );
+			if ( pluginSamplesLocation.exists() && pluginSamplesLocation.isDirectory() ) {
+				mergeSamples( pluginSamplesLocation, new File( sourceLocation, 'samples/old/' + children[ i ] ), children[ i ] );
+				CKBuilder.io.deleteDirectory( pluginSamplesLocation.getAbsolutePath() );
+			}
+		}
+	}
+
+	/**
+	 * Moves samples from source to the target location, gathers information stored in meta tags.
+	 *
+	 * @param {java.io.File} sourceLocation
+	 * @param {java.io.File} targetLocation
+	 * @param {String} path URL to a sample, relative to the location of index.html with link to old samples
+	 * @private
+	 * @member CKBuilder.samples
+	 */
+	function mergeSamples( sourceLocation, targetLocation, path ) {
+		if ( sourceLocation.isDirectory() ) {
+			if ( !targetLocation.exists() )
+				targetLocation.mkdirs();
+
+			var children = sourceLocation.list();
+			for ( var i = 0; i < children.length; i++ ) {
+				if ( String( children[ i ] ) === ".svn" || String( children[ i ] ) === "CVS" || String( children[ i ] ) === ".git" )
+					continue;
+
+				mergeSamples( new File( sourceLocation, children[ i ] ), new File( targetLocation, children[ i ] ), path + '/' + children[ i ] );
+			}
+
+			if ( !targetLocation.list().length )
+				targetLocation[ 'delete' ]();
+		} else {
+			CKBuilder.io.copyFile( sourceLocation, targetLocation );
+			if ( CKBuilder.io.getExtension( sourceLocation.getName() ) !== 'html' )
+				return;
+
+			var text = CKBuilder.io.readFile( sourceLocation );
+
+			// check if required meta information is available
+			if ( text.indexOf( "ckeditor-sample-name" ) === -1 )
+				return;
+
+			var meta = getMetaInformation( text );
+			if ( meta.isbeta )
+				samplesMetaInformation['beta'][ path ] = meta; // jshint ignore:line
+			else if ( meta.isnew )
+				samplesMetaInformation['new'][ path ] = meta;
+			else
+				samplesMetaInformation['normal'][ path ] = meta; // jshint ignore:line
+		}
+	}
+
+	/**
+	 * Returns a single definition list that represents one sample.
+	 *
+	 * @param {String} url URL to a sample
+	 * @param {Object} info An object with information like name and description
+	 * @returns {String}
+	 * @private
+	 * @member CKBuilder.samples
+	 */
+	function linkToSample( url, info ) {
+		if ( !info.name )
+			return '';
+
+		// Support <code>, <em> and <strong> tags
+		var description = info.description.replace( /<(\/?(?:code|strong|em))>/g, '<$1>' );
+
+		// <dt><a class="samples" href="api.html">Basic usage of the API</a></dt>
+		// <dd>Using the CKEditor JavaScript API to interact with the editor at runtime.</dd>
+		var out = [];
+		out.push( "\n", '<dt><a class="samples" href="', url, '">', info.name, '</a>' );
+		if ( info.isnew )
+			out.push( ' <span class="new">New!</span>' );
+		if ( info.isbeta )
+			out.push( ' <span class="beta">Beta</span>' );
+		out.push( '</dt>', "\n" );
+		out.push( '<dd>', description, '</dd>', "\n" );
+
+		return out.join( '' );
+	}
+
+	/**
+	 * Returns HTML structure for the "Plugins" section.
+	 *
+	 * @param {String} html HTML code with definition lists containing links to samples
+	 * @returns {String}
+	 * @member CKBuilder.samples
+	 */
+	function pluginsSection( html ) {
+		if ( !html )
+			return '';
+
+		return '<h2 class="samples">Plugins</h2>' + "\n" + '<dl class="samples">' + html + '</dl>';
+	}
+
+	/**
+	 * Prepare samples.
+	 *
+	 * @class
+	 */
+	CKBuilder.samples = {
+		/**
+		 * Merges samples from plugins folders into the root "samples/old" folder.
+		 *
+		 * @param {java.io.File} sourceLocation Path to CKEditor, where the "samples" and "plugins" folders are available.
+		 * @static
+		 */
+		mergeSamples: function( sourceLocation ) {
+			var samplesFolder = 'samples/old';
+			var samplesLocation = new File( sourceLocation, samplesFolder );
+			if ( !samplesLocation.exists() ) {
+				if ( CKBuilder.options.debug )
+					print( "INFO: " + samplesFolder + " dir not found in " + sourceLocation.getAbsolutePath() );
+				return;
+			}
+			var indexFile = new File( samplesLocation, 'index.html' );
+			if ( !indexFile.exists() ) {
+				if ( CKBuilder.options.debug )
+					print( "index.html not found in the " + samplesFolder + " directory: " + samplesLocation.getAbsolutePath() );
+				return;
+			}
+
+			var indexHtml = CKBuilder.io.readFile( indexFile );
+			indexHtml = CKBuilder.tools.processDirectivesInString( indexHtml );
+
+			// Nothing to do
+			if ( indexHtml.indexOf( "PLUGINS_SAMPLES" ) === -1 && indexHtml.indexOf( "ADVANCED_SAMPLES" ) === -1 && indexHtml.indexOf( "INLINE_EDITING_SAMPLES" ) === -1 ) {
+				if ( CKBuilder.options.debug )
+					print( samplesFolder + '/index.html does not contain any placeholders to replace' );
+				CKBuilder.io.saveFile( indexFile, indexHtml, true );
+				return;
+			}
+
+			mergePluginSamples( sourceLocation );
+
+			var html = {
+				'Inline Editing': '',
+				'Advanced Samples': '',
+				'Plugins': ''
+			};
+
+			for ( var type in samplesMetaInformation ) {
+				for ( var url in samplesMetaInformation[ type ] ) {
+					html[ samplesMetaInformation[ type ][ url ].group ] += linkToSample( url, samplesMetaInformation[ type ][ url ] );
+				}
+			}
+
+			/* jshint sub: true */
+			indexHtml = regexLib.PluginsSamples.matcher( indexHtml ).replaceFirst( pluginsSection( html[ 'Plugins' ] ) );
+			indexHtml = regexLib.InlineEditingSamples.matcher( indexHtml ).replaceFirst( html[ 'Inline Editing' ] );
+			indexHtml = regexLib.AdvancedSamples.matcher( indexHtml ).replaceFirst( html[ 'Advanced Samples' ] );
+			/* jshint sub: false */
+
+			CKBuilder.io.saveFile( indexFile, indexHtml, true );
+		}
+	};
+}() );
+
diff --git a/src/lib/skin.js b/src/lib/skin.js
new file mode 100644
index 0000000..8190d10
--- /dev/null
+++ b/src/lib/skin.js
@@ -0,0 +1,275 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+( function() {
+	var regexLib = {
+		skinName: Pattern.compile( 'CKEDITOR.skin.name\\s*\\=\\s*([\'"])([a-zA-Z0-9-_]+)\\1', Pattern.DOTALL )
+	};
+
+	/**
+	 * Finds the skin name in give file (skin.js).
+	 *
+	 * @param {java.io.File} file
+	 * @returns {String|null}
+	 * @private
+	 * @member CKBuilder.skin
+	 */
+	function findSkinNameInSkinDefinition( file ) {
+		var code = CKBuilder.io.readFile( file );
+
+		code = CKBuilder.javascript.removeWhiteSpace( code, file.getParentFile().getName() + "/skin.js" );
+		var matcher = regexLib.skinName.matcher( code ),
+			skinName;
+		if ( matcher.find() )
+			skinName = matcher.group( 2 );
+
+		return skinName === null ? null : String( skinName );
+	}
+
+	/**
+	 * Finds the correct skin.js in given directory.
+	 *
+	 * @param {java.io.File} dir
+	 * @returns {Boolean|String} Path to the right skin.js file or false.
+	 * @member CKBuilder.skin
+	 */
+	function findCorrectSkinFile( dir ) {
+		var skinFiles = CKBuilder.utils.findFilesInDirectory( 'skin.js', dir );
+
+		if ( !skinFiles.length )
+			return false;
+		if ( skinFiles.length === 1 )
+			return skinFiles[ 0 ];
+		// let's exclude skin.js located in the _source folder
+		if ( skinFiles.length > 1 ) {
+			var tmpArray = [];
+			for ( var i = 0; i < skinFiles.length; i++ ) {
+				if ( !skinFiles[ i ].match( /(\/|\\)_source\1/ ) )
+					tmpArray.push( skinFiles[ i ] );
+
+			}
+			if ( !tmpArray.length )
+				return false;
+
+			else if ( tmpArray.length > 1 )
+				return false;
+
+			else
+				return tmpArray[ 0 ];
+
+		}
+	}
+
+	/**
+	 * Handle skins. Validate them and preprocess.
+	 *
+	 * @class
+	 */
+	CKBuilder.skin = {
+		/**
+		 * Checks specified skin for errors.
+		 *
+		 * @param {String} skin
+		 * @param {Object} options
+		 * @param {String=} options.skinName
+		 * @param {Boolean=} options.exitOnError
+		 * @static
+		 */
+		verify: function( skin, options ) {
+			var skinPath,
+				errors = '',
+				workingDirObj = CKBuilder.io.prepareWorkingDirectoryIfNeeded( skin ),
+				workingDir = workingDirObj.directory;
+
+			if ( CKBuilder.options.debug > 1 )
+				print( "Validating JS files" );
+
+			errors += CKBuilder.tools.validateJavaScriptFiles( workingDir );
+			errors += CKBuilder.tools.validateJavaScriptFilesUsingCC( workingDir );
+
+			if ( !errors ) {
+				skinPath = findCorrectSkinFile( workingDir );
+				if ( !skinPath ) {
+					// check why findCorrectSkinFile() returned false
+					var skinPaths = CKBuilder.utils.findFilesInDirectory( 'skin.js', workingDir );
+					if ( skinPaths.length > 1 ) {
+						var tmpArray = [],
+							workingDirPath = workingDir.getAbsolutePath();
+						for ( var i = 0; i < skinPaths.length; i++ ) {
+							skinPaths[ i ] = String( skinPaths[ i ].replace( workingDirPath, '' ) ).replace( /\\/g, '/' );
+							if ( !skinPaths[ i ].match( /(\/|\\)_source\1/ ) )
+								tmpArray.push( skinPaths[ i ] );
+						}
+						if ( !tmpArray.length )
+							errors += "Found more than one skin.js:\n" + skinPaths.join( "\n" ) + "\n";
+						else if ( tmpArray.length > 1 )
+							errors += "Found more than one skin.js:\n" + skinPaths.join( "\n" ) + "\n";
+					} else
+						errors += "Unable to locate skin.js";
+				} else {
+					if ( options && options.skinName ) {
+						var skinName = findSkinNameInSkinDefinition( new File( skinPath ) );
+						if ( skinName && skinName !== options.skinName )
+							errors += "The skin name defined inside skin.js (" + skinName + ") does not match the expected skin name (" + options.skinName + ")" + "\n";
+					}
+				}
+			}
+
+			if ( skinPath ) {
+				var skinFile = new File( skinPath ),
+					iconsFolder = new File( skinFile.getParentFile(), 'icons' );
+				// Skin is not obliged to provide icons
+				if ( iconsFolder.exists() && !iconsFolder.isDirectory() )
+					errors += "There is an \"icons\" file, but a folder with this name is expected." + "\n";
+			}
+
+			workingDirObj.cleanUp();
+
+			if ( errors && options && options.exitOnError )
+				System.exit( 500 );
+
+			return errors ? errors : "OK";
+		},
+
+		/**
+		 * Builds the specified skin and saves in an optimized form in the target folder.
+		 *
+		 * @param {String} skin Path to the skin
+		 * @param {String} dstDir Path to the destination folder
+		 * @static
+		 */
+		build: function( skin, dstDir ) {
+			var time = new Date(),
+				startTime = time,
+				skinLocation = new File( dstDir );
+
+			CKBuilder.tools.prepareTargetFolder( skinLocation );
+
+			print( "Building skin: " + skin );
+			this.preprocess( skin, dstDir, true );
+
+			var iconsDir = new File( skinLocation, "icons" );
+			if ( iconsDir.exists )
+				CKBuilder.io.deleteDirectory( File( skinLocation, "icons" ) );
+
+			CKBuilder.utils.printUsedTime( startTime );
+		},
+
+		/**
+		 * Preprocesses the specified skin and saves in an optimized form in the target folder.
+		 *
+		 * @param {String} skin Path to the skin
+		 * @param {String} dstDir Path to the destination folder
+		 * @param {Boolean=} generateSprite Whether to generate strip image from available icons
+		 * @static
+		 */
+		preprocess: function( skin, dstDir, generateSprite ) {
+			var workingDirObj = CKBuilder.io.prepareWorkingDirectoryIfNeeded( skin ),
+				workingDir = workingDirObj.directory;
+
+			if ( !this.verify( workingDir, { exitOnError: false } ) ) {
+				workingDirObj.cleanUp();
+				throw( "The skin is invalid" );
+			}
+
+			var skinPath = findCorrectSkinFile( workingDir );
+			if ( !skinPath ) {
+				workingDirObj.cleanUp();
+				throw( "The skin file (skin.js) was not found in " + workingDir.getCanonicalPath() );
+			}
+
+			var skinFile = new File( skinPath ),
+				name = findSkinNameInSkinDefinition( skinFile );
+			if ( !name ) {
+				workingDirObj.cleanUp();
+				throw( "Unable to find skin name" );
+			}
+			var targetFolder = new File( dstDir );
+
+			try {
+				targetFolder.mkdirs();
+			} catch ( e ) {
+				throw( "Unable to create target directory: " + targetFolder.getAbsolutePath() + "\nError: " + e.getMessage() );
+			}
+
+			var flags = {},
+				rootFolder = skinFile.getParentFile();
+			CKBuilder.io.copy( rootFolder, targetFolder, function( sourceLocation, targetLocation ) {
+					if ( sourceLocation.isFile() ) {
+						var copied = CKBuilder.tools.fixLineEndings( sourceLocation, targetLocation );
+						if ( copied ) {
+							// Do not process any directives
+							if ( CKBuilder.options.leaveJsUnminified )
+								return 1;
+
+							var flag = CKBuilder.tools.processDirectives( targetLocation, null, true );
+							if ( flag.LEAVE_UNMINIFIED )
+								flags[ targetLocation.getAbsolutePath() ] = flag;
+
+							return 1;
+						}
+					}
+					return 0;
+				}, function( targetLocation ) {
+					if ( CKBuilder.options.leaveJsUnminified )
+						return;
+
+					if ( CKBuilder.io.getExtension( targetLocation.getName() ) === 'js' ) {
+						var targetPath = targetLocation.getAbsolutePath();
+						if ( flags[ targetPath ] && flags[ targetPath ].LEAVE_UNMINIFIED ) {
+							if ( CKBuilder.options.debug > 1 )
+								print( "Leaving unminified: " + targetLocation.getPath() );
+
+							CKBuilder.io.saveFile( targetLocation, CKBuilder.tools.removeLicenseInstruction( CKBuilder.io.readFile( targetLocation ) ), true );
+							return;
+						}
+
+						if ( CKBuilder.options.debug )
+							print( "Minifying: " + targetLocation.getPath() );
+
+						CKBuilder.javascript.minify( targetLocation );
+					}
+				} );
+
+			if ( generateSprite ) {
+				var skinIcons = CKBuilder.image.findIcons( targetFolder ),
+					files = [],
+					outputFile = new File( targetFolder, "icons.png" ),
+					outputCssFile = new File( targetFolder, "editor.css" ),
+					noIcons = true,
+					buttonName;
+				// Sorted by plugin name
+				for ( buttonName in skinIcons ) {
+					files.push( new File( skinIcons[ buttonName ] ) );
+					noIcons = false;
+				}
+
+				if ( !noIcons )
+					CKBuilder.image.createSprite( files, outputFile, outputCssFile );
+
+				// HiDPI support, set some variables again
+				skinIcons = CKBuilder.image.findIcons( targetFolder, true );
+				files = [];
+				outputFile = new File( targetFolder, "icons_hidpi.png" );
+				noIcons = true;
+
+				// Sorted by plugin name
+				for ( buttonName in skinIcons ) {
+					files.push( new File( skinIcons[ buttonName ] ) );
+					noIcons = false;
+				}
+
+				if ( !noIcons )
+					CKBuilder.image.createSprite( files, outputFile, outputCssFile, true );
+			}
+
+			if ( !CKBuilder.options.leaveCssUnminified )
+				CKBuilder.css.mergeCssFiles( targetFolder );
+
+			workingDirObj.cleanUp();
+		}
+	};
+
+}() );
diff --git a/src/lib/tools.js b/src/lib/tools.js
new file mode 100644
index 0000000..c5a35e1
--- /dev/null
+++ b/src/lib/tools.js
@@ -0,0 +1,394 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+( function() {
+	var regexLib = {
+		eol: Pattern.compile( '(?:\\x09|\\x20)+$' ),
+		eof: Pattern.compile( '(?:\\x09|\\x20|\\r|\\n)+$' ),
+		Remove: Pattern.compile( '(?m-s:^.*?%REMOVE_START%).*?(?m-s:%REMOVE_END%.*?$)', Pattern.DOTALL ),
+		RemoveCore: Pattern.compile( '(?m-s:^.*?%REMOVE_START_CORE%).*?(?m-s:%REMOVE_END_CORE%.*?$)', Pattern.DOTALL ),
+		RemoveLine: Pattern.compile( '.*%REMOVE_LINE%.*(?:\\r\\n|\\r|\\n)?' ),
+		RemoveLineCore: Pattern.compile( '.*%REMOVE_LINE_CORE%.*(?:\\r\\n|\\r|\\n)?' ),
+		Timestamp: Pattern.compile( '%TIMESTAMP%' ),
+		CopyrightComment: Pattern.compile( '/\\*[\\s\\*]*Copyright[\\s\\S]+?\\*/(?:\\r\\n|\\r|\\n)', Pattern.DOTALL | Pattern.CASE_INSENSITIVE ),
+		LicenseComment: Pattern.compile( '/\\*[\\s\\*]*\\@license[\\s\\S]+?\\*/(?:\\r\\n|\\r|\\n)', Pattern.DOTALL ),
+		Rev: Pattern.compile( '%REV%' ),
+		Version: Pattern.compile( '%VERSION%' ),
+		license: Pattern.compile( '\\@license( )?', Pattern.DOTALL )
+	};
+
+	var lineEndings = {
+		"cgi": "\n",
+		"pl": "\n",
+		"sh": "\n",
+		"readme": "\r\n",
+		"afp": "\r\n",
+		"afpa": "\r\n",
+		"ascx": "\r\n",
+		"asp": "\r\n",
+		"aspx": "\r\n",
+		"bat": "\r\n",
+		"cfc": "\r\n",
+		"cfm": "\r\n",
+		"code": "\r\n",
+		"command": "\r\n",
+		"conf": "\r\n",
+		"css": "\r\n",
+		"dtd": "\r\n",
+		"htaccess": "\r\n",
+		"htc": "\r\n",
+		"htm": "\r\n",
+		"html": "\r\n",
+		"js": "\r\n",
+		"jsp": "\r\n",
+		"lasso": "\r\n",
+		"md": "\r\n",
+		"php": "\r\n",
+		"py": "\r\n",
+		"sample": "\r\n",
+		"txt": "\r\n",
+		"xml": "\r\n"
+	};
+
+	/**
+	 * @class
+	 */
+	CKBuilder.tools = {
+		/**
+		 * Fix line endings in given file. Only selected text files are processed.
+		 *
+		 * @param {java.io.File} sourceFile
+		 * @param {java.io.File} targetFile
+		 * @static
+		 */
+		fixLineEndings: function( sourceFile, targetFile ) {
+			var extension = CKBuilder.io.getExtension( sourceFile.getName() ),
+				bomExtensions = { asp: 1, js: 1 };
+
+			if ( !lineEndings[ extension ] )
+				return false;
+
+			if ( CKBuilder.options.debug > 1 )
+				print( "Fixing line endings in: " + targetFile.getAbsolutePath() );
+
+			var buffer = new StringBuffer(),
+				inStream = new BufferedReader( new InputStreamReader( new FileInputStream( sourceFile ), "UTF-8" ) ),
+				line;
+
+			var firstLine = true;
+			while ( ( line = inStream.readLine() ) != null ) {
+				if ( firstLine ) {
+					var hasBom = line.length() && line.charAt( 0 ) === 65279;
+					if ( !hasBom && extension in bomExtensions )
+						buffer.append( String.fromCharCode( 65279 ) );
+					else if ( hasBom && !( extension in bomExtensions ) )
+						line = line.substring( 1 );
+
+					firstLine = false;
+				}
+
+				// Strip whitespace characters
+				line = regexLib.eol.matcher( line ).replaceAll( "" );
+				buffer.append( line );
+				buffer.append( lineEndings[ extension ] );
+			}
+
+			CKBuilder.io.saveFile( targetFile, regexLib.eof.matcher( buffer.toString() ).replaceAll( lineEndings[ extension ] ) );
+
+			return true;
+		},
+
+		/**
+		 * Updates copyright headers in text files.
+		 *
+		 * @param {java.io.File} targetFile
+		 */
+		updateCopyrights: function( targetFile ) {
+			var extension = CKBuilder.io.getExtension( targetFile.getName() ),
+				bomExtensions = { asp: 1, js: 1 };
+
+			if ( !lineEndings[ extension ] ) {
+				return false;
+			}
+
+			text = CKBuilder.io.readFile( targetFile );
+			if ( text.indexOf( "Copyright" ) === -1 || text.indexOf( "CKSource" ) === -1 ) {
+				return;
+			}
+
+			if ( text.indexOf( 'For licensing, see LICENSE.md or http://ckeditor.com/license' ) !== -1 ) {
+				text = text.replace( 'For licensing, see LICENSE.md or http://ckeditor.com/license', 'This software is covered by CKEditor Commercial License. Usage without proper license is prohibited.' );
+				CKBuilder.io.saveFile( targetFile, text, bomExtensions[ extension ] );
+				return;
+			}
+
+			if ( text.indexOf( 'For licensing, see LICENSE.md or [http://ckeditor.com/license](http://ckeditor.com/license)' ) !== -1 ) {
+				text = text.replace( 'For licensing, see LICENSE.md or [http://ckeditor.com/license](http://ckeditor.com/license)', 'This software is covered by CKEditor Commercial License. Usage without proper license is prohibited.' );
+				CKBuilder.io.saveFile( targetFile, text, bomExtensions[ extension ] );
+				return;
+			}
+		},
+
+		/**
+		 * Returns the copyright statement found in the text
+		 * The Copyright statement starts either with "@license" or with "Copyright".
+		 *
+		 * @param {String} text
+		 * @returns {String}
+		 * @static
+		 */
+		getCopyrightFromText: function( text ) {
+			var matcher = regexLib.CopyrightComment.matcher( text );
+			if ( matcher.find() )
+				return matcher.group( 0 );
+
+			matcher = regexLib.LicenseComment.matcher( text );
+			if ( matcher.find() )
+				return matcher.group( 0 );
+
+			return "";
+		},
+
+		/**
+		 * Remove all copyright statements in given string.
+		 *
+		 * @param {String} text
+		 * @returns {String}
+		 * @static
+		 */
+		removeLicenseInstruction: function( text ) {
+			return String( regexLib.license.matcher( text ).replaceAll( '' ) );
+		},
+
+		/**
+		 * Cleans up the target folder.
+		 *
+		 * @param {java.io.File} targetLocation
+		 * @static
+		 */
+		prepareTargetFolder: function( targetLocation ) {
+			if ( targetLocation.exists() ) {
+				if ( !CKBuilder.options.overwrite )
+					CKBuilder.error( "Target folder already exists: " + targetLocation.getAbsolutePath() );
+
+				print( "Cleaning up target folder" );
+				try {
+					if ( !CKBuilder.io.deleteDirectory( targetLocation ) )
+						throw( "Unable to delete target directory: " + targetLocation.getAbsolutePath() );
+				} catch ( e ) {
+					throw( "Unable to delete target directory: " + targetLocation.getAbsolutePath() );
+				}
+			}
+			try {
+				if ( !targetLocation.mkdirs() )
+					throw( "Unable to create target directory: " + targetLocation.getAbsolutePath() );
+			} catch ( e ) {
+				throw( "Unable to create target directory: " + targetLocation.getAbsolutePath() + "\n" );
+			}
+		},
+
+		/**
+		 * Validate all JS files included in given location using Rhino parser.
+		 *
+		 * @param {java.io.File} sourceLocation Folder to validate.
+		 * @returns {String} An error message with errors, if found any. Empty string if no errors are found.
+		 * @static
+		 */
+		validateJavaScriptFiles: function( sourceLocation ) {
+			var dirList = sourceLocation.list(),
+				result = "";
+
+			for ( var i = 0; i < dirList.length; i++ ) {
+				var f = new File( sourceLocation, dirList[ i ] ),
+					error;
+
+				if ( f.isDirectory() ) {
+					error = this.validateJavaScriptFiles( f );
+					if ( error )
+						result += error;
+				} else if ( CKBuilder.io.getExtension( f.getName() ) === "js" ) {
+					error = this.validateJavaScriptFile( f );
+					if ( error )
+						result += error + "\n";
+				}
+			}
+
+			return result;
+		},
+
+		/**
+		 * Validate all JS files included in given location using Closure Compiler.
+		 *
+		 * @param {java.io.File} sourceLocation Folder to validate.
+		 * @returns {String} An error message with errors, if found any. Empty string if no errors are found.
+		 * @static
+		 */
+		validateJavaScriptFilesUsingCC: function( sourceLocation ) {
+			var dirList = sourceLocation.list();
+			var result = "";
+
+			for ( var i = 0; i < dirList.length; i++ ) {
+				var f = new File( sourceLocation, dirList[ i ] ),
+					error;
+
+				if ( f.isDirectory() ) {
+					error = this.validateJavaScriptFilesUsingCC( f );
+					if ( error )
+						result += error;
+				} else if ( CKBuilder.io.getExtension( f.getName() ) === "js" ) {
+					var code = CKBuilder.io.readFile( f ),
+						errors = CKBuilder.javascript.findErrors( code, f.getParentFile().getName() + "/" + f.getName() );
+
+					if ( errors )
+						result += errors.join( "\n" );
+
+				}
+			}
+			return result;
+		},
+
+		/**
+		 * Validate JS file included in given location using Rhino parser.
+		 *
+		 * @param {java.io.File} sourceLocation Folder to validate.
+		 * @returns {String} An error message with errors, if found any. Empty string if no errors are found.
+		 * @static
+		 */
+		validateJavaScriptFile: function( sourceLocation ) {
+			// Setup the compiler environment, error reporter...
+			var compilerEnv = new CompilerEnvirons(),
+				errorReporter = compilerEnv.getErrorReporter(),
+
+				// Create an instance of the parser...
+				parser = new org.mozilla.javascript.Parser( compilerEnv, errorReporter );
+
+			try {
+				parser.parse( CKBuilder.io.readFile( sourceLocation ), null, 1 );
+				return "";
+			} catch ( e ) {
+				return sourceLocation.getName() + " (line " + e.lineNumber + "):\n    " + e.message + "";
+			}
+		},
+
+		/**
+		 * Replace CKBuilder directives in given file.
+		 * %VERSION%:
+		 *     the "version" string passed to the CKReleaser execution command.
+		 * %REV%:
+		 *     the revision number of the source directory (returned by version control system).
+		 * %TIMESTAMP%:
+		 *     a four characters string containing the
+		 *     concatenation of the "Base 36" value of each of the following components
+		 *     of the program execution date and time: year + month + day + hour.
+		 * %REMOVE_LINE%:
+		 *     removes the line.
+		 * %REMOVE_START% and %REMOVE_END%:
+		 *     removes all lines starting from %REMOVE_START% to %REMOVE_END%,
+		 *     declaration line inclusive.
+		 * %LEAVE_UNMINIFIED%
+		 *     if set, the resulting object contains LEAVE_UNMINIFIED property set to true.
+		 *
+		 * @param {java.io.File} file File in which replace the directives
+		 * @param {Object} directives (optional) An object with values for placeholders.
+		 * @param {Boolean} core Whether to process core directives
+		 * @returns {Object} an object with optional set of flags.
+		 * @static
+		 * Available flags:
+		 * LEAVE_UNMINIFIED (Boolean) Indicates whether the file should be minified.
+		 */
+		processDirectives: function( location, directives, core ) {
+			var flags = {},
+				text = CKBuilder.io.readFile( location );
+
+			if ( text.indexOf( "%LEAVE_UNMINIFIED%" ) !== -1 )
+				flags.LEAVE_UNMINIFIED = true;
+
+			if ( text.indexOf( "%VERSION%" ) !== -1 || text.indexOf( "%REV%" ) !== -1 || text.indexOf( "%TIMESTAMP%" ) !== -1 || text.indexOf( "%REMOVE_START" ) !== -1 || text.indexOf( "%REMOVE_END" ) !== -1 || text.indexOf( "%REMOVE_LINE" ) !== -1 ) {
+				var processedText = this.processDirectivesInString( text, directives );
+				if ( core )
+					processedText = this.processCoreDirectivesInString( processedText );
+
+				if ( text !== processedText ) {
+					if ( CKBuilder.options.debug )
+						print( "Replaced directives in " + location.getAbsolutePath() );
+
+					CKBuilder.io.saveFile( location, processedText );
+				}
+			}
+
+			return flags;
+		},
+
+		/**
+		 * Replace CKBuilder directives in given string.
+		 * %VERSION%:
+		 *     the "version" string passed to the CKBuilder execution command.
+		 * %REV%:
+		 *     the revision number of the source directory (returned by version control system).
+		 * %TIMESTAMP%:
+		 *     a four characters string containing the
+		 *     concatenation of the "Base 36" value of each of the following components
+		 *     of the program execution date and time: year + month + day + hour.
+		 * %REMOVE_LINE%:
+		 *     removes the line.
+		 * %REMOVE_LINE_CORE%:
+		 *     removes the line, but only if file is included in core (merged into ckeditor.js).
+		 * %REMOVE_START% and %REMOVE_END%:
+		 *     removes all lines starting from %REMOVE_START% to %REMOVE_END%,
+		 *     declaration line inclusive.
+		 * %REMOVE_START_CORE% and %REMOVE_END_CORE%:
+		 *     same as %REMOVE_START% and %REMOVE_END%, but works
+		 *     only if file is included in core (merged into ckeditor.js).
+		 * @param text {String} Text in which replace the directives
+		 * @param directives {Object} (Optional) An object with values for placeholders.
+		 * @returns {String} Text
+		 * @static
+		 */
+		processDirectivesInString: function( text, directives ) {
+			directives = directives || {};
+			directives.version = directives.version || CKBuilder.options.version;
+			directives.revision = directives.revision || CKBuilder.options.revision;
+			directives.timestamp = directives.timestamp || CKBuilder.options.timestamp;
+
+			if ( text.indexOf( "%VERSION%" ) !== -1 )
+				text = String( regexLib.Version.matcher( text ).replaceAll( directives.version ) );
+
+			if ( text.indexOf( "%REV%" ) !== -1 )
+				text = String( regexLib.Rev.matcher( text ).replaceAll( directives.revision ) );
+
+			if ( text.indexOf( "%TIMESTAMP%" ) !== -1 )
+				text = String( regexLib.Timestamp.matcher( text ).replaceAll( directives.timestamp ) );
+
+			if ( text.indexOf( "%REMOVE_START%" ) !== -1 && text.indexOf( "%REMOVE_END%" ) !== -1 ) {
+				text = String( regexLib.Remove.matcher( text ).replaceAll( '%REMOVE_LINE%' ) );
+				text = String( regexLib.RemoveLine.matcher( text ).replaceAll( '' ) );
+			} else if ( text.indexOf( "%REMOVE_LINE%" ) !== -1 )
+				text = String( regexLib.RemoveLine.matcher( text ).replaceAll( '' ) );
+
+			return text;
+		},
+
+		/**
+		 * Replace CKBuilder "core" directives in given string.
+		 * %REMOVE_LINE_CORE%:
+		 *     removes the line, but only if file is included in core (merged into ckeditor.js).
+		 * %REMOVE_START_CORE% and %REMOVE_END_CORE%:
+		 *     same as %REMOVE_START% and %REMOVE_END%, but works
+		 *     only if file is included in core (merged into ckeditor.js).
+		 * @param {String} text Text in which replace the directives
+		 * @returns {String}
+		 * @static
+		 */
+		processCoreDirectivesInString: function( text ) {
+			if ( text.indexOf( "%REMOVE_START_CORE%" ) !== -1 && text.indexOf( "%REMOVE_END_CORE%" ) !== -1 ) {
+				text = String( regexLib.RemoveCore.matcher( text ).replaceAll( '%REMOVE_LINE_CORE%' ) );
+				text = String( regexLib.RemoveLineCore.matcher( text ).replaceAll( '' ) );
+			} else if ( text.indexOf( "%REMOVE_LINE_CORE%" ) !== -1 )
+				text = String( regexLib.RemoveLineCore.matcher( text ).replaceAll( '' ) );
+
+			return text;
+		}
+	};
+}() );
diff --git a/src/lib/utils.js b/src/lib/utils.js
new file mode 100644
index 0000000..5fc1a07
--- /dev/null
+++ b/src/lib/utils.js
@@ -0,0 +1,224 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+( function() {
+
+	function escapeProperty( string ) {
+		if ( string.match( /^[a-z][a-z0-9_]+$/i ) )
+			return string;
+
+		return "'" + escapeString( string ) + "'";
+	}
+
+	function escapeString( string ) {
+		return string.replace( /\\/g, "\\\\" ).replace( /\r/g, "\\r" ).replace( /\n/g, "\\n" ).replace( /'/g, "\\'" ).replace( /\u200b/g, "\\u200b" );
+	}
+
+	/**
+	 * Utility class.
+	 *
+	 * @class
+	 */
+	CKBuilder.utils = {
+		/**
+		 * Returns the copyright header with selected newline characters.
+		 *
+		 * @param {String} eol
+		 * @returns {String}
+		 * @static
+		 */
+		copyright: function( eol ) {
+			var copyright,
+				date = new Date();
+
+			if ( CKBuilder.options.commercial )
+				copyright = "/*" + eol + "This software is covered by CKEditor Commercial License. Usage without proper license is prohibited." + eol + "Copyright (c) 2003-" + date.getFullYear() + ", CKSource - Frederico Knabben. All rights reserved." + eol + "*/" + eol;
+			else
+				copyright = "/*" + eol + "Copyright (c) 2003-" + date.getFullYear() + ", CKSource - Frederico Knabben. All rights reserved." + eol + "For licensing, see LICENSE.md or http://ckeditor.com/license" + eol + "*/" + eol;
+
+			return copyright;
+		},
+
+		/**
+		 * Helper function that prints how many seconds an operation took.
+		 *
+		 * @param {Date} timeStart
+		 * @return {Date}
+		 * @static
+		 */
+		printUsedTime: function( timeStart ) {
+			var timeEnd = new Date(),
+				timeTaken = timeEnd - timeStart;
+
+			if ( timeTaken > 1000 )
+				print( "    Time taken.....: " + ( timeTaken / 1000 ) + "seconds" );
+			return timeEnd;
+		},
+
+		/**
+		 * Wrap the JavaScript code into anonymous function call.
+		 *
+		 * @param {String} string Source code
+		 * @returns {String} Wrapped source code
+		 * @static
+		 */
+		wrapInFunction: function( string ) {
+			return '(function(){' + string + '}());';
+		},
+
+		/**
+		 * Pretty print JavaScript object.
+		 *
+		 * @param {Object} obj Object to print
+		 * @param {String=} indent Current indentation
+		 * @returns {String}
+		 * @static
+		 */
+		prettyPrintObject: function( obj, indent ) {
+			var result = "";
+			indent = indent || "";
+
+			for ( var property in obj ) {
+				var value = obj[ property ];
+				if ( typeof value === 'string' )
+					value = "'" + escapeString( value ) + "'";
+				else if ( typeof value === 'object' ) {
+					if ( value instanceof Array )
+						value = "[ " + value + " ]";
+					else {
+						var od = CKBuilder.utils.prettyPrintObject( value, indent + "	" );
+						value = "\n" + indent + "{\n" + od + "\n" + indent + "}";
+					}
+				}
+				result += indent + escapeProperty( property ) + " : " + value + ",\n";
+			}
+			return result.replace( /,\n$/, "" );
+		},
+
+		/**
+		 * Print JavaScript object.
+		 *
+		 * @param {Object} obj Object to print
+		 * @returns {String}
+		 * @static
+		 */
+		printObject: function( obj ) {
+			var result = '';
+
+			for ( var property in obj ) {
+				var value = obj[ property ];
+
+				if ( typeof value === 'string' )
+					value = "'" + value + "'";
+				else if ( typeof value === 'object' ) {
+					if ( value instanceof Array )
+						value = "[" + value + "]";
+					else {
+						var od = CKBuilder.utils.printObject( value );
+						value = "{" + od + "}";
+					}
+				}
+				result += escapeString( property ) + ":" + value + ",";
+			}
+
+			return result;
+		},
+
+		/**
+		 * Find file in the specified folder.
+		 *
+		 * @param {String} filename Name of the file to search for
+		 * @param {java.io.File} dir The directory in which to search
+		 * @returns {java.io.File|null}
+		 * @static
+		 */
+		findFileInDirectory: function( filename, dir ) {
+			var dirList = dir.list();
+
+			for ( var i = 0; i < dirList.length; i++ ) {
+				var f = new File( dir, dirList[ i ] );
+
+				if ( !f.isDirectory() ) {
+					if ( String( f.getName() ) === filename )
+						return f;
+				} else {
+					var file = CKBuilder.utils.findFileInDirectory( filename, f );
+					if ( file )
+						return file;
+				}
+			}
+
+			return null;
+		},
+
+		/**
+		 * Find files in the specified folder.
+		 *
+		 * @param {String} filename Name of the file to search for
+		 * @param {java.io.File} dir The directory in which to search
+		 * @returns {Array} An array with absolute paths to files found
+		 * @static
+		 */
+		findFilesInDirectory: function( filename, dir ) {
+			var dirList = dir.list().sort(),
+				files = [];
+
+			for ( var i = 0; i < dirList.length; i++ ) {
+				var f = new File( dir, dirList[ i ] );
+
+				if ( !f.isDirectory() ) {
+					if ( String( f.getName() ) === filename )
+						files.push( f.getAbsolutePath() );
+
+				} else {
+					var tmp = CKBuilder.utils.findFilesInDirectory( filename, f );
+					if ( tmp )
+						files = files.concat( tmp );
+				}
+			}
+
+			return files;
+		},
+
+		/**
+		 * Overwrites the properties from obj1 with values from obj2.
+		 * Adds properties from obj2 that do not exists in obj1.
+		 * Does not change values of obj1 or obj2.
+		 *
+		 * @param {Object} obj1 The base object to be extended.
+		 * @param {Object} obj2 The object from which copy values.
+		 * @param {Boolean} [fullMerge=true] fullMerge Whether to include in the resulting object properties from obj2 that do not exist in obj1
+		 * @returns {Object} the extended object
+		 * @static
+		 */
+		merge: function( obj1, obj2, fullMerge ) {
+			var result = {};
+
+			for ( var i in obj2 ) {
+				if ( fullMerge === false && typeof( obj1[ i ] ) === 'undefined' )
+					continue;
+
+				try {
+					// Property in destination object set; update its value.
+					if ( typeof( obj2[ i ] ) === 'object' )
+						result[ i ] = this.merge( obj1[ i ], obj2[ i ], fullMerge ); else
+						result[ i ] = obj2[ i ];
+				} catch ( e ) {
+					// Property in destination object not set; create it and set its value.
+					result[ i ] = obj2[ i ];
+				}
+			}
+
+			for ( var j in obj1 ) {
+				if ( typeof obj2[ j ] !== 'undefined' )
+					continue;
+				// Property is missing in source object.
+				result[ j ] = obj1[ j ];
+			}
+
+			return result;
+		}
+	};
+}() );
\ No newline at end of file
diff --git a/test/test.bat b/test/test.bat
new file mode 100644
index 0000000..60e18f6
--- /dev/null
+++ b/test/test.bat
@@ -0,0 +1,11 @@
+:: Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+:: For licensing, see LICENSE.md
+
+ at echo off
+
+:: We need to execute scripts from parent directory...
+cd ..
+
+java -cp lib/rhino/js.jar;lib/apache/commons-cli.jar;lib/javatar/tar.jar;lib/tartool/tartool.jar;lib/closure/compiler.jar org.mozilla.javascript.tools.shell.Main -opt -1 test/test.js
+
+cd test
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..a022e94
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,670 @@
+/*
+ Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md
+ */
+
+var CKBuilderTest = true;
+load( "src/ckbuilder.js" );
+CKBuilder.options.debug = 2;
+
+( function()
+{
+	// Run tests.
+	var passCount = 0, failCount = 0;
+	var assetsPath = 'test/_assets';
+	var assetsDir = new File( assetsPath );
+	var tempPath = 'test/tmp';
+	var tempDir = new File( tempPath );
+
+	function isArray(o) {
+		return Object.prototype.toString.call(o) === '[object Array]';
+	}
+
+	function assertDirectoriesAreEqual( expected, actual, title )
+	{
+		var dirList = expected.list(), actualFile, expectedFile;
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			actualFile = new File( actual, dirList[i] );
+			expectedFile = new File( expected, dirList[i] );
+			assertEquals( true, actualFile.exists(), '[' + title + '] file should exist: ' + actualFile.getPath() );
+			assertEquals( expectedFile.isDirectory(), actualFile.isDirectory(), '[' + title + '] files should be of the same type: ' + actualFile.getPath() );
+			if ( actualFile.exists() )
+			{
+				if ( actualFile.isDirectory() )
+					assertDirectoriesAreEqual( expectedFile, actualFile, title );
+				else
+					assertFilesAreEqual( expectedFile, actualFile, title );
+			}
+		}
+
+		dirList = actual.list();
+
+		// Check for files that should not exists
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			actualFile = new File( actual, dirList[i] );
+			expectedFile = new File( expected, dirList[i] );
+			if ( !expectedFile.exists() )
+				assertEquals( false, actualFile.exists(), '[' + title + '] file should not exist: ' + actualFile.getPath() );
+		}
+	}
+
+	function assertFilesAreEqual( expected, actual, title )
+	{
+		assertEquals( String( md5( CKBuilder.io.readFile( expected ) ) ), String( md5( CKBuilder.io.readFile( actual ) ) ),
+			'[' + title + '] Checking MD5 of ' + actual.getPath());
+	}
+
+	function assertEquals( expected, actual, title )
+	{
+		if ( ( !isArray( expected ) && expected !== actual) || JSON.stringify( expected ) !== JSON.stringify( actual ) )
+		{
+			var error = {
+				expected : expected,
+				actual : actual
+			};
+
+			print( 'FAILED: ' + (title ? title : "") );
+
+//			if ( !error.expected )
+//				throw error;
+
+			print( '  Expected: ' + error.expected );
+			print( '  Actual  : ' + error.actual );
+
+			failCount++;
+		}
+		else
+			passCount++;
+	}
+
+	function md5( s )
+	{
+		s = new java.lang.String( s );
+		var m = java.security.MessageDigest.getInstance( "MD5" );
+		m.update( s.getBytes("UTF-8"), 0, s.length() );
+		return new java.math.BigInteger( 1, m.digest() ).toString( 16 );
+	}
+
+	function error( msg )
+	{
+		print( msg );
+		quit();
+	}
+
+	function prepareTempDirs()
+	{
+		if ( tempDir.exists() && !CKBuilder.io.deleteDirectory( tempPath ) )
+			error( "Can't delete temp directory" );
+
+		if ( !tempDir.mkdir() )
+			error( "Can't create temp directory: " + tempDir );
+
+		var assetsDirList = assetsDir.list();
+		for ( var i = 0; i < assetsDirList.length; i++ )
+		{
+			var f = new File( tempDir, assetsDirList[i] );
+			if ( !f.mkdir() )
+				error( "Can't create temp directory: " + f );
+		}
+	}
+
+	function testLanguageFiles()
+	{
+		print( "\nTesting processing language files\n" );
+		var dir = new File( assetsDir, 'langfiles' );
+		var dirList = dir.list();
+		var pluginNames = { devtools : 1, placeholder : 1, uicolor : 1 };
+		var languages = { en : 1, he : 1, pl : 1 };
+
+		CKBuilder.lang.mergeAll( new File( assetsDir, 'langfiles' ), new File( tempDir, 'langfiles' ), pluginNames, languages );
+
+		for ( var i = 0; i < dirList.length; i++ )
+		{
+			if ( dirList[i].indexOf( ".correct" ) === -1 )
+				continue;
+
+			var correctFile = new File( dir, dirList[i] );
+			var testName = correctFile.getName().replace( ".correct", "" );
+			var tempFile = new File( tempDir + '/langfiles/' + testName );
+
+			assertEquals( CKBuilder.io.readFile( correctFile ), CKBuilder.io.readFile( tempFile ), 'Language file: ' + testName );
+		}
+		var french = new File( tempDir + '/langfiles/fr.js' );
+		assertEquals( french.exists(), false );
+	}
+
+	function testCssProcessor( testFolder, leaveCssUnminified )
+	{
+		print( "\nTesting CSS processor\n" );
+		var correctFile, dir, dirList, test, tempFile;
+
+		CKBuilder.options.leaveCssUnminified = leaveCssUnminified;
+		CKBuilder.io.copy( new File( assetsDir + testFolder ), new File( tempDir + testFolder ) );
+		CKBuilder.css.mergeCssFiles( new File( tempDir + testFolder ) );
+
+		var sourceDir = new File( assetsDir + testFolder );
+		var sourceDirList = sourceDir.list();
+
+		for ( var i = 0 ; i < sourceDirList.length ; i++ )
+		{
+			if ( String( sourceDirList[i] ) === ".svn" || String( sourceDirList[i] ) === ".git" )
+				continue;
+
+			dir = new File(  tempDir + testFolder, sourceDirList[i] );
+			assertEquals( true, dir.exists(), dir + " exists?" );
+
+			dirList = dir.list();
+			assertEquals( true, dirList.length > 0, dir + " not empty?" );
+
+			var foundCorrect = 0;
+			var foundCss = 0;
+			/**
+			 * Loop through files in the target directory and search for valid
+			 * CSS files
+			 */
+			for ( var j = 0 ; j < dirList.length ; j++ )
+			{
+				if ( dirList[j].indexOf( ".css" ) !== -1 )
+					foundCss++;
+
+				if ( dirList[j].indexOf( "correct.txt" ) !== -1 )
+				{
+					foundCorrect++;
+					test = dirList[j].replace( ".correct.txt", "" );
+
+					correctFile = new File( dir, dirList[j] );
+					tempFile = new File( dir, test + '.css' );
+
+					assertEquals( true, tempFile.exists(), tempFile + " exists?" );
+
+					assertEquals( String( md5( CKBuilder.io.readFile( correctFile ) ) ), String( md5( CKBuilder.io.readFile( tempFile ) ) ),
+						'Checking md5 of created file [' + dir.getName() + "/" + test + '.css]' );
+				}
+			}
+			if ( foundCorrect )
+				assertEquals( foundCorrect, foundCss, 'The number of created and correct css files must be equal in skin ' + dir.getName() );
+		}
+	}
+
+	function testSprite()
+	{
+		var plugins = ['basicstyles', 'link', 'list', 'table'];
+		var pluginsLocation = new File( assetsDir, "/sprite/plugins" );
+		var skinLocation = new File( assetsDir, "/sprite/skins/v2" );
+
+		// 1. Unminified CSS, use only specified plugins
+		CKBuilder.options.all = false;
+		CKBuilder.options.leaveCssUnminified = true;
+
+		var imageFile = new File( tempPath + "/sprite/icons.png" );
+		var cssFile = new File( tempPath + "/sprite/icons.css" );
+
+		CKBuilder.image.createFullSprite( pluginsLocation, skinLocation, imageFile, cssFile, plugins );
+
+		assertEquals( CKBuilder.io.readFile( new File( assetsDir, "/sprite/icons.correct.css" ) ), CKBuilder.io.readFile( cssFile ),
+			'Checking content of icons.css' );
+		assertEquals( imageFile.exists(), true, "Sprite image should exist." );
+
+		var image = ImageIO.read( imageFile );
+		// 14 icons x (21px + 8px)
+ 		// 21 pixels - biggest single icon height
+		// 8 pixels - a distance in a non-hidpi strip
+		assertEquals( 14 * (21 + 8), image.getHeight(), "Checking height of sprite image." );
+		assertEquals( 21, image.getWidth(), "Checking width of sprite image." );
+
+
+		// 2. Minified CSS, include icons for all plugins (also the maximize plugin)
+		CKBuilder.options.all = true;
+		CKBuilder.options.leaveCssUnminified = false;
+
+		imageFile = new File( tempPath + "/sprite/icons2.png" );
+		cssFile = new File( tempPath + "/sprite/icons2.css" );
+
+		CKBuilder.image.createFullSprite( pluginsLocation, skinLocation, imageFile, cssFile, plugins );
+
+		assertEquals( CKBuilder.io.readFile( new File( assetsDir, "/sprite/icons2.correct.css" ) ), CKBuilder.io.readFile( cssFile ),
+			'Checking content of icons2.css' );
+		assertEquals( imageFile.exists(), true, "Sprite image should exist." );
+
+		var image = ImageIO.read( imageFile );
+		// 15 icons x (21px + 8px)
+		// 21 pixels - biggest single icon height
+		// 8 pixels - a distance in a non-hidpi strip
+		assertEquals( 15 * (21 + 8), image.getHeight(), "Checking height of sprite image." );
+		assertEquals( 21, image.getWidth(), "Checking width of sprite image." );
+
+		// 3. Unminified CSS, use only specified plugins, hidpi
+		CKBuilder.options.all = false;
+		CKBuilder.options.leaveCssUnminified = true;
+		var skinLocation = new File( assetsDir, "/sprite/skins/sapphire" );
+
+		var imageFile = new File( tempPath + "/sprite/icons3.png" );
+		var cssFile = new File( tempPath + "/sprite/icons3.css" );
+
+		CKBuilder.image.createFullSprite( pluginsLocation, skinLocation, imageFile, cssFile, plugins, true );
+
+		assertEquals( CKBuilder.io.readFile( new File( assetsDir, "/sprite/icons3.correct.css" ) ), CKBuilder.io.readFile( cssFile ),
+			'Checking content of icons3.css' );
+		assertEquals( imageFile.exists(), true, "Sprite image should exist." );
+
+		var image = ImageIO.read( imageFile );
+		// 14 icons x (32px + 16px)
+		// 32 pixels - biggest single icon height
+		// 16 pixels - a distance in a hidpi strip
+		assertEquals( 14 * ( 32 + 16 ), image.getHeight(), "Checking height of sprite image." );
+		assertEquals( 32, image.getWidth(), "Checking width of sprite image." );
+	}
+
+	function testDirectives()
+	{
+		print( "\nTesting directives\n" );
+
+		var name = 'directives';
+		var testName, tempFile, correctFile, sampleFile;
+
+		var dir = new File( assetsDir, 'directives' );
+		var dirList = dir.list();
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			if ( dirList[i].indexOf( ".correct." ) === -1 )
+				continue;
+
+			testName = dirList[i].replace( ".correct.txt", "" );
+
+			sampleFile = new File( dir, testName + '.txt' );
+			correctFile = new File( dir, testName + '.correct.txt' );
+			tempFile = new File( tempDir, name + '/' + testName + '.out.txt' );
+
+			CKBuilder.io.copy( sampleFile, tempFile );
+			CKBuilder.tools.processDirectives( tempFile, { version: '3.1beta', revisionNumber : '1234', timestamp : 'AB89' } );
+
+			assertEquals( CKBuilder.io.readFile( correctFile ), CKBuilder.io.readFile( tempFile ),
+				'releaser.directives[' + testName + ']' );
+		}
+	}
+
+	function testBom()
+	{
+		var file, extension;
+		var dir = new File( tempDir, 'bom' );
+
+		CKBuilder.io.copy( new File( assetsDir, 'bom' ), dir, function( sourceLocation, targetLocation ) {
+			if ( !sourceLocation.isDirectory() )
+				return CKBuilder.tools.fixLineEndings( sourceLocation, targetLocation ) ? 1 : 0;
+		} );
+
+		var children = dir.list();
+		for ( var i = 0 ; i < children.length ; i++ )
+		{
+			file = new File( dir, children[i] );
+
+			extension = CKBuilder.io.getExtension( file.getName() );
+
+			switch ( extension )
+			{
+				case "asp":
+				case "js":
+					// BOM + CRLF
+					assertEquals( 8, file.length(), "testing BOM: " + children[i] );
+					break;
+
+				case "sh":
+					// !BOM + LF
+					assertEquals( 4, file.length(), "testing BOM: " + children[i] );
+					break;
+
+				default:
+					// !BOM + CRLF
+					assertEquals( 5, file.length(), "testing BOM: " + children[i] );
+					break;
+			}
+		}
+	}
+
+	function testLineEndings()
+	{
+		print( "\nTesting line endings\n" );
+		var testName, tempFile, correctFile, sampleFile;
+		var name = "lineendings";
+		var dir = new File( assetsDir, 'lineendings' );
+		var dirList = dir.list();
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			if ( dirList[i].indexOf( ".correct." ) === -1 )
+				continue;
+
+			testName = dirList[i].replace( ".correct", "" );
+
+			sampleFile = new File( assetsDir, name + '/' + testName );
+			correctFile = new File( assetsDir, name + '/' + dirList[i] );
+			tempFile = new File( tempDir, name + '/' + testName );
+
+			CKBuilder.tools.fixLineEndings( sampleFile, tempFile );
+
+			assertEquals( CKBuilder.io.readFile( correctFile ), CKBuilder.io.readFile( tempFile ),
+				'testing line endings: [' + testName + ']' );
+		}
+	}
+
+	function listFiles( file )
+	{
+		var result = [];
+
+		if ( file.isDirectory() )
+		{
+			var children = file.list();
+			if ( !children.length )
+			{
+				result.push( file );
+			}
+			else
+			{
+				for ( var i = 0 ; i < children.length ; i++ )
+				{
+					result.push( listFiles( new File( file, children[i] ) ) );
+				}
+			}
+		}
+		else
+		{
+			result.push( file );
+		}
+
+		return result;
+	}
+
+	function testIgnoringPaths()
+	{
+		print( "\nTesting ignored paths...\n" );
+
+		var sourceLocation = new File( assetsDir, 'ignored' );
+		var targetLocation = new File( tempDir, 'ignored' );
+
+		var ignored = [ 'devtools', 'placeholder/lang/he.js', 'uicolor.js' ];
+		CKBuilder.io.copy( sourceLocation, targetLocation , function( sourceLocation, targetLocation ) {
+			if ( CKBuilder.config.isIgnoredPath( sourceLocation, ignored ) )
+				return -1;
+		});
+
+		var files = listFiles(targetLocation);
+		files.sort();
+		var validResult = [
+			'test/tmp/ignored/a11yhelp/lang/en.js',
+			'test/tmp/ignored/a11yhelp/lang/he.js',
+			'test/tmp/ignored/a11yhelp/plugin.js',
+			'test/tmp/ignored/placeholder/dialogs/placeholder.js',
+			'test/tmp/ignored/placeholder/lang/en.js',
+			'test/tmp/ignored/placeholder/lang/pl.js',
+			'test/tmp/ignored/placeholder/plugin.js',
+			'test/tmp/ignored/uicolor/lang/en.js',
+			'test/tmp/ignored/uicolor/lang/he.js',
+			'test/tmp/ignored/uicolor/plugin.js'];
+
+		assertEquals( files.length, 3, "Comparing plugins directories (same number of subfolders?)" );
+		var areEqual = files.toString().replace(/\\/g, "/") === validResult.toString();
+		assertEquals( true, areEqual, "Comparing plugins directories (are equal?)" );
+	}
+
+	function testLangProps()
+	{
+		print( "\nTesting language properties...\n" );
+
+		var sourceLocation = new File( assetsDir, 'langprops' );
+		var targetLocation = new File( tempDir, 'langprops' );
+
+		CKBuilder.io.copy( sourceLocation, targetLocation );
+
+		var plugins = {
+			devtools : {
+				test : { en : 1, pl : 1, foo : 1 },
+				expected : ['en', 'pl']
+			},
+			div : {
+				test: { foo : 1, bar : 1 },
+				expected: false
+			},
+			find : {
+				test : { en : 1, pl : 1, 'zh-cn' : 1, fr : 0 },
+				expected : ['en', 'pl', 'zh-cn']
+			},
+			colordialog : {
+				test : { en : 1, pl : 1, 'zh-cn' : 1, fr : 1 },
+				expected : ['en', 'pl', 'zh-cn', 'fr']
+			},
+			liststyle : {
+				test : { en : 1, pl : 1, 'zh-cn' : 1, he : 1 },
+				expected : ['en', 'pl', 'zh-cn', 'he']
+			},
+			magicline : {
+				test : { en : 1, pl : 1, foo : 1 },
+				expected : true
+			},
+			specialchar : {
+				test : { en : 1, pl : 1, 'zh-cn' : 1, he : 1 },
+				expected : ['en', 'pl', 'zh-cn', 'he']
+			}
+		};
+
+		for ( var plugin in plugins )
+		{
+			assertEquals( plugins[plugin].expected, CKBuilder.plugin.updateLangProperty( File( targetLocation, 'plugins/' + plugin + '/plugin.js'), plugins[plugin].test ), "lang property (" + plugin + ")" );
+			assertFilesAreEqual( File( sourceLocation, 'plugins_correct/' + plugin + '/plugin.js'), File( targetLocation, 'plugins/' + plugin + '/plugin.js') );
+		}
+	}
+
+	CKBuilder.plugin.updateLangProperty(File("test/_assets/requires/plugin_hr.js"), 'en.pl');
+
+	function testMinification()
+	{
+		print( "\nTesting minification...\n" );
+
+		var sourceLocation = new File( assetsDir, 'minification' );
+		var targetLocation = new File( tempDir, 'minification' );
+
+		CKBuilder.io.copy( sourceLocation, targetLocation , null, function( targetLocation ) {
+			if ( CKBuilder.io.getExtension( targetLocation.getName() ) === 'js'  )
+				CKBuilder.javascript.minify( targetLocation );
+		} );
+
+		var testName, tempFile, correctFile;
+		var dir = new File( tempDir, 'minification' );
+		var dirList = dir.list();
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			if ( dirList[i].indexOf( ".correct" ) === -1 )
+				continue;
+
+			testName = dirList[i].replace( ".correct", "" );
+
+			correctFile = new File( dir, testName + '.correct' );
+			tempFile = new File( dir, testName );
+
+			assertEquals( CKBuilder.io.readFile( correctFile ), CKBuilder.io.readFile( tempFile ),
+				'minification[' + testName + ']' );
+		}
+	}
+
+	function testRequiredPlugins()
+	{
+		print( "\nTesting required plugins...\n" );
+
+		var assetsLocation = new File( assetsDir, 'requires' );
+		assertEquals( ['dialog', 'fakeobjects'], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_flash.js" )));
+		assertEquals( ['richcombo'], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_font.js" )));
+		assertEquals( ['dialog', 'fakeobjects'], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_link.js" )));
+		assertEquals( ['floatpanel'], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_menu.js" )));
+		assertEquals( [], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_xml.js" )));
+		assertEquals( [], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_hr.js" )));
+		assertEquals( [], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_hr2.js" )));
+		assertEquals( ['foo'], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_hr3.js" )));
+		assertEquals( ['dialog', 'contextmenu'], CKBuilder.plugin.getRequiredPlugins(new File( assetsLocation, "plugin_liststyle.js" )));
+	}
+
+	function testSkinBuilder()
+	{
+		print( "\nTesting skin builder...\n" );
+
+		CKBuilder.options.leaveCssUnminified = true;
+		var sourceLocation = new File( assetsDir, 'skins/kama' );
+		var correctResultLocation = new File( assetsDir, 'skins/kama_correct' );
+		var targetLocation = new File( tempDir, 'skins/kama' );
+
+		CKBuilder.skin.build( sourceLocation, targetLocation );
+		assertDirectoriesAreEqual( correctResultLocation, targetLocation, 'Checking skin builder (CSS minification disabled)' );
+
+		CKBuilder.options.leaveCssUnminified = false;
+		var sourceLocation = new File( assetsDir, 'skins_minified/kama' );
+		var correctResultLocation = new File( assetsDir, 'skins_minified/kama_correct' );
+		var targetLocation = new File( tempDir, 'skins_minified/kama' );
+
+		CKBuilder.skin.build( sourceLocation, targetLocation );
+		assertDirectoriesAreEqual( correctResultLocation, targetLocation, 'Checking skin builder (CSS minification enabled)' );
+	}
+
+	function testVerifyPlugins()
+	{
+		print( "\nTesting plugins verification...\n" );
+
+		var pluginsLocation = new File( assetsDir, 'verify_plugins' );
+		var dirList = pluginsLocation.list();
+		var plugins = {
+			'_pubme_extratags_1_1.zip' : { name : '_pubme_extratags',  expected : 'OK' },
+			'apinstein-ckeditor-autocss-2e37374.zip' : { name : 'autocss',  expected : 'OK' },
+			'autosave_1.0.2.zip' : { name : 'autosave',  expected : 'OK' },
+			'confighelper1.2.zip' : { name : 'confighelper',  expected : 'OK' },
+			'fakeelements_checkbox_radio_select.zip' : { name : 'formchanges',  expected : 'OK' },
+			'groupedcolorbutton.zip' : { name : 'groupedcolorbutton',  expected : 'OK' },
+			'highlite_source_with_codemirror.zip' : { name : 'highlightsource',  expected : "The plugin name defined inside plugin.js (sourcepopup) does not match the expected plugin name (highlightsource)\n" },
+			'htmlbuttons1.0.zip' : { name : 'htmlbuttons',  expected : 'OK' },
+			'imagepaste1.0.zip' : { name : 'imagepaste',  expected : 'OK' },
+			'insert-edit_source_code_icons.zip' : { name : 'insertedit',  expected : "The plugin name defined inside plugin.js (scriptcode) does not match the expected plugin name (insertedit)\n" },
+			'languages.zip' : { name : 'languages',  expected : 'OK' },
+			'lightbox_plus.zip' : { name : 'lightboxplus',  expected : "The plugin name defined inside plugin.js (lightbox) does not match the expected plugin name (lightboxplus)\n" },
+			'links_to_own_pages.zip' : { name : 'linktoown',  expected : "The plugin name defined inside plugin.js (internpage) does not match the expected plugin name (linktoown)\n" },
+			'loremIpsum.zip' : { name : 'loremipsum',  expected : "The plugin name defined inside plugin.js (loremIpsum) does not match the expected plugin name (loremipsum)\n" },
+			'onchange1.5.zip' : { name : 'onchange',  expected : 'OK' },
+			'small_google_map.zip' : { name : 'gmap',  expected : 'OK' },
+			'smallerselection0.1.zip' : { name : 'smallerselection',  expected : 'OK' },
+			'video1.3.zip' : { name : 'video',  expected : 'OK' },
+			'w8tcha-CKEditor-oEmbed-Plugin-481d449.zip' : { name : 'oEmbed',  expected : "Found more than one plugin.js:\n/w8tcha-CKEditor-oEmbed-Plugin-481d449/oEmbed_CKEditor3/oEmbed/plugin.js\n/w8tcha-CKEditor-oEmbed-Plugin-481d449/oEmbed_CKEditor4/oEmbed/plugin.js\n" },
+			'whitelist1.0.zip' : { name : 'whitelist',  expected : 'OK' },
+			'xmltemplates1.0.zip' : { name : 'xmltemplates',  expected : 'OK' },
+			'youtube.zip' : { name : 'youtube',  expected : 'OK' },
+			'youtube_mp3.zip' : { name : 'youtube',  expected : "Found more than one plugin.js:\n/youtube_mp3/mp3player/plugin.js\n/youtube_mp3/youtube/plugin.js\n" },
+			'zoom1.0.zip' : { name : 'zoom', expected : 'OK' }
+		};
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			var file = new File( pluginsLocation, dirList[i] );
+			if ( file.isDirectory() )
+			{
+				assertEquals( "OK", CKBuilder.plugin.verify( file.getPath(), { pluginName : String( file.getName() ) } ));
+			}
+			else
+			{
+				assertEquals( plugins[file.getName()].expected, CKBuilder.plugin.verify( file.getPath(), { pluginName : plugins[file.getName()].name } ));
+			}
+			//print('Checking ' + file.getPath());
+
+		}
+	}
+
+	function testVerifySkins()
+	{
+		print( "\nTesting skins verification...\n" );
+
+		var skinsLocation = new File( assetsDir, 'verify_skins' );
+		var dirList = skinsLocation.list();
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			var file = new File( skinsLocation, dirList[i] );
+			if ( file.isDirectory() )
+			{
+				if ( file.getName() == "fake" ) {
+					assertEquals( "The skin name defined inside skin.js (kama) does not match the expected skin name (fake)\n", CKBuilder.skin.verify( file.getPath(), { skinName : file.getName() } ));
+				}
+				else if ( file.getName() == "noicons" ) {
+					assertEquals( "OK", CKBuilder.skin.verify( file.getPath(), { skinName : String( file.getName() ) } ));
+				}
+				else {
+					assertEquals( "OK", CKBuilder.skin.verify( file.getPath(), { skinName : String( file.getName() ) } ));
+				}
+			}
+		}
+	}
+
+	function testSamples()
+	{
+		print( "\nTesting samples merging...\n" );
+
+		var samplesLocation = new File( assetsDir, 'samples/ckeditor-dev' );
+		var targetLocation = new File( tempDir, 'samples' );
+		CKBuilder.io.copy( samplesLocation, targetLocation );
+		CKBuilder.samples.mergeSamples( targetLocation );
+
+		var correctResultLocation = new File( assetsDir, 'samples/ckeditor-dev-correct' );
+		assertDirectoriesAreEqual( correctResultLocation, targetLocation, 'Checking merged samples' );
+
+	}
+
+	function testCopyrights()
+	{
+		print( "\nTesting copyrights...\n" );
+		CKBuilder.options.commercial = true;
+		CKBuilder.options.leaveJsUnminified = true;
+		var sourceLocation = new File( assetsDir, 'copyrights' );
+		var targetLocation = new File( tempDir, 'copyrights' );
+		CKBuilder.io.copy( sourceLocation, targetLocation );
+
+		var testName, tempFile, correctFile;
+		var dir = new File( tempDir, 'copyrights' );
+		var dirList = dir.list();
+
+		for ( var i = 0 ; i < dirList.length ; i++ )
+		{
+			if ( dirList[i].indexOf( ".correct" ) === -1 )
+				continue;
+
+			testName = dirList[i].replace( ".correct", "" );
+
+			correctFile = new File( dir, testName + '.correct' );
+			tempFile = new File( dir, testName );
+			CKBuilder.tools.updateCopyrights( tempFile );
+
+			assertEquals( CKBuilder.io.readFile( correctFile ), CKBuilder.io.readFile( tempFile ),
+					'copyrights[' + testName + ']' );
+		}
+		CKBuilder.options.commercial = false;
+		CKBuilder.options.leaveJsUnminified = false;
+	}
+
+	prepareTempDirs();
+	testLangProps();
+	testLanguageFiles();
+	testSprite();
+	testCssProcessor( "/css", true );
+	testCssProcessor( "/css_minified", false );
+	testDirectives();
+	testBom();
+	testLineEndings();
+	testIgnoringPaths();
+	testMinification();
+	testRequiredPlugins();
+	testSkinBuilder();
+	testVerifyPlugins();
+	testVerifySkins();
+	testSamples();
+	testCopyrights();
+
+	print( '' );
+	print( 'Finished: ' + passCount + ' passed / ' + failCount + ' failed' );
+
+}());
diff --git a/test/test.sh b/test/test.sh
new file mode 100755
index 0000000..6c867ee
--- /dev/null
+++ b/test/test.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+# Copyright (c) 2012-2014, CKSource - Frederico Knabben. All rights reserved.
+# For licensing, see LICENSE.md
+
+# We need to execute scripts from parent directory...
+cd ..
+
+/usr/bin/java -cp lib/rhino/js.jar:lib/apache/commons-cli.jar:lib/javatar/tar.jar:lib/tartool/tartool.jar:lib/closure/compiler.jar org.mozilla.javascript.tools.shell.Main -opt -1 test/test.js
+
+cd test

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/ckbuilder.git



More information about the Reproducible-commits mailing list