diff --git a/Package.php b/Package.php
new file mode 100644
index 0000000..75cbbf8
--- /dev/null
+++ b/Package.php
@@ -0,0 +1,105 @@
+path = $this->resolve_path($package_path);
+ $this->root_dir = dirname($this->path);
+ $this->parse($this->path);
+ }
+ }
+
+ public function __toString()
+ {
+ return $this->get_name();
+ }
+
+ static function decode($path){
+ return preg_match('/\.json$/', $path) ? json_decode(file_get_contents($path), true) : YAML::decode_file($path);
+ }
+
+ static function glob($path, $pattern = '*', $flags = 0, $depth = 0){
+ $matches = array();
+ $folders = array(rtrim($path, DIRECTORY_SEPARATOR));
+
+ while ($folder = array_shift($folders)){
+ $matches = array_merge($matches, glob($folder.DIRECTORY_SEPARATOR.$pattern, $flags));
+
+ if (!$depth) continue;
+
+ $moreFolders = glob($folder.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
+ $depth = ($depth < -1) ? -1: $depth + count($moreFolders) - 2;
+ $folders = array_merge($folders, $moreFolders);
+ }
+ return $matches;
+ }
+
+ public function add_source($source_path = '')
+ {
+ if (!is_a($source_path, 'Source')) $source_path = new Source($this->get_name(), $source_path);
+ $this->sources[] = $source_path;
+ }
+
+ public function get_name()
+ {
+ return $this->name;
+ }
+
+ public function get_path()
+ {
+ return $this->path;
+ }
+
+ public function get_sources()
+ {
+ return $this->sources;
+ }
+
+ public function parse($package_path)
+ {
+ $package = self::decode($package_path);
+
+ foreach ($package as $key => $value){
+ $method = 'parse_' . strtolower($key);
+ if (is_callable(array($this, $method))) $this->$method($value);
+ }
+ }
+
+ public function parse_name($name)
+ {
+ $this->set_name($name);
+ }
+
+ public function parse_sources($sources)
+ {
+ # todo(ibolmo): 5, should be a class option.
+ if (is_string($sources)) $sources = self::glob($this->path, $sources, 0, 5);
+ foreach ($sources as $source) $this->add_source($this->root_dir . '/' . $source);
+ }
+
+ public function set_name($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function resolve_path($path){
+ if (!is_dir($path) && file_exists($path)) return $path;
+
+ $pathinfo = pathinfo($path);
+ $path = $pathinfo['dirname'] . '/' . $pathinfo['basename'] . '/';
+
+ if (file_exists($path . 'package.yml')) return $path . 'package.yml';
+ if (file_exists($path . 'package.yaml')) return $path . 'package.yaml';
+ if (file_exists($path . 'package.json')) return $path . 'package.json';
+
+ throw new Exception("package.(ya?ml|json) not found in $path.");
+ }
+}
diff --git a/Packager.php b/Packager.php
new file mode 100644
index 0000000..bc40693
--- /dev/null
+++ b/Packager.php
@@ -0,0 +1,174 @@
+configure();
+ if ($package_paths) foreach ((array) $package_paths as $package) $this->add_package($package);
+ }
+
+ static function get_instance()
+ {
+ if (!self::$instance) self::$instance = new Packager();
+ return self::$instance;
+ }
+
+ static function strip_blocks($code, $blocks)
+ {
+ foreach ((array) $blocks as $block){
+ $code = preg_replace_callback("%(/[/*])\s*<$block>(.*?)$block>(?:\s*\*/)?%s", function($matches){
+ return (strpos($matches[2], ($matches[1] == "//") ? "\n" : "*/") === false) ? $matches[2] : "";
+ }, $code);
+ }
+ return $code;
+ }
+
+ public function add_component(Source $source, $component)
+ {
+ $index = $this->get_source_index($source);
+ if ($index < 0) $index = $this->add_source($source);
+
+ foreach ($this->generators as $name => $callback){
+ $key = call_user_func($callback, $source, $component);
+ $this->set_key($key, $index, $name);
+ }
+
+ return $index;
+ }
+
+ public function add_generator($name, $callback)
+ {
+ $this->generators[$name] = $callback;
+ }
+
+ public function add_package($package)
+ {
+ if (!is_a($package, 'Package')) $package = new Package($package);
+ $this->packages[$package->get_name()] = $package;
+ }
+
+ public function add_source(Source $source)
+ {
+ $index = array_push($this->sources, $source) - 1;
+ return $this->keys[$source->get_name()] = $index;
+ }
+
+ public function build(Source $source)
+ {
+ $build = array_map(function($source){
+ return $source->get_code();
+ }, $this->get_required_for_source($source));
+ $build[] = $source->get_code();
+ return implode('', $build);
+ }
+
+ public function configure()
+ {
+ $this->add_generator('component name', function(Source $source, $component){
+ return $component;
+ });
+
+ $this->add_generator('package and source name', function(Source $source, $component){
+ return sprintf('%s/%s', $source->get_package_name(), $source->get_name());
+ });
+
+ $this->add_generator('package and component name', function(Source $source, $component){
+ return sprintf('%s/%s', $source->get_package_name(), $component);
+ });
+ }
+
+ public function get_packages()
+ {
+ return $this->packages;
+ }
+
+ public function get_source_by_name($name)
+ {
+ $index = $this->get_source_index($name);
+ if ($index > -1) return $this->sources[$index];
+
+ $this->warn("Could not find source '$name'.");
+ return null;
+ }
+
+ public function get_source_index($key)
+ {
+ if ($key instanceof Source) $key = $key->get_name();
+ return isset($this->keys[$key]) ? $this->keys[$key] : -1;
+ }
+
+ public function get_sources()
+ {
+ return $this->sources;
+ }
+
+ public function get_required_for_source(Source $source, &$required = null)
+ {
+ $return = false;
+ if (!$required){
+ $return = true;
+ $required = array();
+ }
+
+ foreach ($source->get_requires() as $require){
+ if (!($require instanceof Source)) $require = $this->get_source_by_name($require);
+ if (!$require) continue;
+ if ($require->has_requires()) $this->get_required_for_source($require, $required);
+ }
+ foreach ($source->get_requires() as $require){
+ if (!($require instanceof Source)) $require = $this->get_source_by_name($require);
+ if (!$require) continue;
+ if (!in_array($require, $required)) $required[] = $require;
+ }
+
+ if ($return) return $required;
+ }
+
+ public function remove_component(Source $source, $component)
+ {
+ foreach ($this->generators as $name => $callback){
+ $key = call_user_func($callback, $source, $component);
+ unset($this->keys[$key]);
+ }
+ }
+
+ public function remove_package($package_name)
+ {
+ if ($package_name instanceof Package) $package_name = $package_name->get_name();
+ if (!isset($this->packages[$package_name])) return false;
+ foreach($this->packages[$package_name]->get_sources() as $source) $this->remove_source($source);
+ unset($this->packages[$package_name]);
+ return true;
+ }
+
+ public function remove_source(Source $source)
+ {
+ $name = $source->get_name();
+ $index = $this->keys[$name];
+ foreach ($source->get_provides() as $component) $this->remove_component($source, $component);
+ unset($this->keys[$name], $this->sources[$index]);
+ }
+
+ protected function set_key($key, $index, $generator_name)
+ {
+ if (isset($this->keys[$key])) $this->warn("Generator '$generator_name' set component key '$key'.");
+ $this->keys[$key] = $index;
+ }
+
+ protected function warn($message)
+ {
+ # todo(ibolmo): log mixin
+ }
+
+}
diff --git a/README.md b/README.md
index 97f46c9..4f96e7c 100644
--- a/README.md
+++ b/README.md
@@ -1,160 +1,45 @@
Packager
========
-Packager is a PHP 5.2+ library to concatenate libraries split in multiple files in a single file. It automatically calculates dependancies. Packager requires a yml header syntax in every file, and a `package.yml` manifest file, as seen on the MooTools project.
+Packager is a PHP 5.3+ library to concatenate libraries split in multiple files in a single file. It automatically calculates dependancies.
-Packager API
-============
+Packager requires a yml header syntax in every file, and a `package.yml` manifest file, as seen on the MooTools project.
-Constructor
------------
+2.0 Branch
+----------
+This is an experimental branch, dedicated to a more programmatic approach to packaging (building) your JavaScript files.
-The constructor of this class accepts either a path to a package or a list of path to packages. `package.yml` must not be included in the path.
+With the previous Packager, you were **required** to define a `package.yml`. This is no longer the case. You're still able to work with your `package.json` or `package.yml` files, but you have the ability to define dependencies at runtime and build only the dependencies for that file.
-### Example
-
- $pkg = new Packager("/Users/kamicane/Sites/mootools-core/");
- $pkg = new Packager(array("/Users/kamicane/Sites/mootools-core/", "/Users/kamicane/Sites/mootools-more/"));
+Why?
+----
+I needed a more dynamic packager. On my web development, each web page varies between JavaScript uses. My home page, for example, might have a login area but the about us page does not. Why should I build a single `mootools.js` for the whole site? Even if I could keep track of what pages require what, it's not my job or the application's job to keep that in order.
-Adding a manifest
------------------
+Instead, I wanted to: ``. I wanted homepage.js to have a yml header, and for Packager to build a _specific build_ for this **specific page**. The next step would be to create agent specific builds, but that's for another branch.
-* `parse_manifest` » adds a manifest to this instance
+API 2.0
+=======
+Remember, the emphasis has been to create an actual API. The following are classes and their purpose. See the linked wiki pages, for additional method signatures and documentation.
+[Packager](#)
+Packager is the "registry" of components, and the manager of building the dependencies.
-Working with files
-------------------
+[Package](#)
+A Package is a container of many sources. Due to the emphasis on Source, Package is not as useful **yet**.
-### Getters
+[Source](#)
+A Source has many components (provides) and dependencies (requires). By defining a Source, you can add (provide) to the registry and build all dependencies for the source.
- * `get_all_files` » gets an list of all files
- * `get_file_dependancies` » gets an ordered list of every file that this file depends on
- * `get_file_path` » gets the file path
- * `get_file_source` » gets the file source
- * `get_file_description` » gets the file description
- * `get_file_provides` » gets a list of the file provided components
+[Command Line Script](#)
+This is currently broken, and low priority for me. Pull requests, greatly appreciated.
-### Converters
+Since I'm using Packager 2.0 as an API I no longer need a `npm|gem|pear`-like library for my development.
- * `complete_file` » converts a single file to an ordered list of files
- * `complete_files` » converts a list of files to an ordered list of files
-### Generators
+Getting Stated
+==============
+banana banana banana
- * `build_from_files` » returns a string containing the source of the selected files and their dependancies
- * `write_from_files` » writes a file with the selected files and their dependancies
+For now, take a look at [sfPackagerPlugin](https://github.com/ibolmo/sfPackagerPlugin) for a programmatic usage of the 2.0 branch. In particular take a look at: [PackagerHelper](https://github.com/ibolmo/sfPackagerPlugin/blob/master/lib/helper/PackagerHelper.php) which has the "useful" interface, I had been talking about.
-Working with components
------------------------
-
-### Converting to files
-
- * `component_to_file` » gets the name of the file that provides this component
- * `components_to_files` » converts a list of components to a list of files
-
-### Generators
-
- * `build_from_components` » returns a string containing the source of the selected components and their dependancies
- * `write_from_components` » writes a file with the selected components and their dependancies
-
-
-Class usage
------------
-
-### Syntax
-
- $pkg = new Packager(`$path_to_manifest`);
-
-### Example
-
- $pkg = new Packager("/Users/kamicane/Sites/mootools-core/");
-
- $pkg->write_from_components("/Users/kamicane/Sites/mootools.js", array('Type', 'Array'));
-
-Packager Command Line script
-----------------------------
-
-The Packager command line script is your one-stop solution to build any of your packages at once. Works on unices.
-
-### Syntax
-
- ./packager COMMAND +option1 argument1 argument2 +option2 argument1 argument2
-
-* `COMMAND` a packager command *(required)*
-* `+option` options for commands *(optional)*
-
-### Commands
-
-* `register` registers a package. Creates a .packages.yml file in your home folder.
-* `unregister` unregisters a package
-* `list` list registered packages
-* `build` builds a single file with the supplied packages / files / components
-
-### Registering a Package
-
-#### Example
-
- ./packager register /Users/kamicane/mootools-core
- » the package Core has been registered
-
-
-### Listing Packages
-
-#### Example
-
- ./packager list
- » Core: /Users/kamicane/mootools-core
-
-
-### Unregistering a Package
-
-#### Example
-
- ./packager unregister Core
- » the package Core has been unregistered
-
-### Building Packages
-
-#### Examples
-
- ./packager build Core/Type Core/Fx ART/ART.Element
-
-Which is the same as...
-
- ./packager build +components Core/Type Core/Fx ART/ART.Element
-
-Which builds the passed in components (and their dependancies) using your registered packages.
-
- ./packager build +files Core/Core Core/Fx ART/ART
-
-This builds the passed in files (and their dependancies) using your registered packages.
-
- ./packager build ART/*
-
-Builds every component from ART, and their dependancies, using your registered packages.
-
- ./packager build SomePackage/SomeComponent +packages /Users/kamicane/Sites/some-package
-
-Builds the selected components using your registered packages and a temporary package that resides in /Users/kamicane/Sites/some-package, without having to register it first.
-
- ./packager build SomePackage/SomeComponent -packages Core
-
-Builds the selected components using your registered packages minus the package names you pass to -packages. This lets you build your components without dependancies.
-
- ./packager build ART/SomeComponent +use-only ART
-
-Builds the selected components using only ART of your registered packages. This lets you build your components without dependancies.
-
- ./packager build SomePackage/SomeComponent +use-only +packages /Users/kamicane/Sites/some-package
-
-Builds the selected components using none of your registered packages plus the passed in package, without registering it. This lets you build your components without dependancies.
-
- ./packager build +components ART/ART +files ART/ART.Base
-
-You can mix components and files
-
- ./packager build Core/* > mootools.js
-
-This is how you output to a file
-
diff --git a/Source.php b/Source.php
new file mode 100644
index 0000000..80eddbc
--- /dev/null
+++ b/Source.php
@@ -0,0 +1,143 @@
+package_name = $package_name;
+ if ($source_path) $this->parse($source_path);
+ }
+
+ public function __toString()
+ {
+ return $this->get_name();
+ }
+
+ static function normalize_name($default, $name){
+ $exploded = explode('/', $name);
+ if (count($exploded) == 1) return array($default, $exploded[0]);
+ if (empty($exploded[0])) return array($default, $exploded[1]);
+ return array($exploded[0], $exploded[1]);
+ }
+
+ public function build()
+ {
+ return Packager::get_instance()->build($this);
+ }
+
+ public function get_code()
+ {
+ return $this->code;
+ }
+
+ public function get_name()
+ {
+ if (!$this->name) $this->name = basename($this->path, '.js');
+ if (!$this->name) $this->name = $this->package_name;
+ return $this->name;
+ }
+
+ public function get_package_name()
+ {
+ return $this->package_name;
+ }
+
+ public function get_provides()
+ {
+ return $this->provides;
+ }
+
+ public function get_requires()
+ {
+ return $this->requires;
+ }
+
+ public function has_requires()
+ {
+ return !empty($this->requires);
+ }
+
+ public function parse($source_path = '')
+ {
+ if ($source_path){
+ $this->path = $source_path;
+ $this->code = file_get_contents($source_path);
+ }
+
+ if (!$this->code) throw new RuntimeException('Missing the code to parse. Did you forget to supply the source_path or set_code?');
+
+ preg_match(self::DESCRIPTOR_REGEX, $this->code, $matches);
+
+ if (!empty($matches)){
+ $header = YAML::decode($matches[0]);
+
+ foreach ($header as $key => $value){
+ $method = 'parse_' . strtolower($key);
+ if (is_callable(array($this, $method))) $this->$method($value);
+ }
+ } else {
+ $this->warn("No yaml header present in $source_path");
+ }
+ }
+
+ public function parse_name($name)
+ {
+ $this->name = $name;
+ }
+
+ public function parse_provides($provides)
+ {
+ $provides = (array) $provides;
+ $this->provides($provides);
+ }
+
+ public function parse_requires($requires)
+ {
+ $requires = (array) $requires;
+ foreach ($requires as $i => $require) $requires[$i] = implode('/', self::normalize_name($this->package_name, $require));
+ $this->requires($requires);
+ }
+
+ public function provides($provides)
+ {
+ $packager = Packager::get_instance();
+ foreach ((array) $provides as $component){
+ $packager->add_component($this, $component);
+ $this->provides[] = $component;
+ }
+ return $this;
+ }
+
+ public function requires($requires)
+ {
+ $this->requires = (array) $requires;
+ return $this;
+ }
+
+ public function set_name($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function set_code($code)
+ {
+ $this->code = $code;
+ return $this;
+ }
+
+ public function warn($message)
+ {
+ # todo(ibolmo): log mixin
+ }
+}
diff --git a/helpers/yaml.php b/helpers/yaml.php
index 7fdf1f7..816ac8d 100644
--- a/helpers/yaml.php
+++ b/helpers/yaml.php
@@ -1,6 +1,6 @@
get_package_name();
- $path = $pkg->get_package_manifest();
+ $pkg = new Package($realpath);
+ $name = $pkg->get_name();
+ $path = $pkg->get_path();
$packages[$name] = $path;
file_put_contents("$home/.packages.yml", YAML::encode($packages));
-
- Packager::warn("the package $name has been registered as $path\n");
+
+ info("the package $name has been registered as $path\n");
break;
@@ -46,7 +60,7 @@ switch ($command){
$name = array_shift($argv);
if (empty($packages[$name])){
- Packager::warn("there is no package called $name\n");
+ warn("there is no package called $name\n");
break;
}
@@ -54,7 +68,7 @@ switch ($command){
file_put_contents("$home/.packages.yml", YAML::encode($packages));
- Packager::warn("the package $name has been unregistered\n");
+ info("the package $name has been unregistered\n");
break;
@@ -63,18 +77,18 @@ switch ($command){
$package_name = array_shift($argv);
if (empty($package_name)){
- foreach ($packages as $name => $path) Packager::warn("$name: $path\n");
+ foreach ($packages as $name => $path) info("$name: $path\n");
} else {
if (!empty($packages[$package_name])){
$package_path = $packages[$package_name];
- $pkg = new Packager($package_path);
- $files = $pkg->get_all_files();
- foreach ($files as $file){
- $file_name = $pkg->get_file_name($file);
- Packager::warn("- $file_name: [" . implode(", ", $pkg->get_file_provides($file)) . "]\n");
+ $pkg = new Package($package_path);
+ $sources = $pkg->get_sources();
+ foreach ($sources as $source) {
+ $name = $source->get_name();
+ info("- $name: [" . implode(", ", $source->get_provides()) . "]\n");
}
} else {
- Packager::warn("The package $package_name has not been found.\n");
+ warn("The package $package_name has not been found.\n");
}
}
@@ -135,10 +149,11 @@ switch ($command){
if (!$selected['use-only'] || array_contains($selected['use-only'], $name)) $paths[] = $path;
}
- $pkg = new Packager($paths);
+ $packager = Packager::get_instance();
+ foreach ($paths as $path) $packager->add_package($path);
- foreach ($selected['remove-packages'] as $package_name) $pkg->remove_package($package_name);
- foreach ($selected['add-packages'] as $package_path) $pkg->add_package($package_path);
+ foreach ($selected['remove-packages'] as $package_name) $packager->remove_package($package_name);
+ foreach ($selected['add-packages'] as $package_path) $packager->add_package($package_path);
$re = "/^([^\/]+)\/\*$/";
$wildcards = array();
@@ -163,31 +178,20 @@ switch ($command){
}
}
- $pkg->validate($files, $components, $wildcards);
-
- foreach ($components as $component){
- $file = $pkg->component_to_file($component);
- if ($file) array_include($files, $file);
- }
-
- foreach ($wildcards as $package){
- $all = $pkg->get_all_files($package);
- foreach ($all as $file) array_include($files, $file);
- }
-
- $files = $pkg->complete_files($files);
+ $source = new Source('packager-cli');
+ $source->requires(array_merge($files, $wildcards));
+ warn("Build using: " . implode(', ', $packager->get_packages()) . "\n");
+ warn("Included Files/Components:\n");
- Packager::warn("Build using: " . implode(', ', $pkg->get_packages()) . "\n");
-
- Packager::warn("Included Files/Components:\n");
-
- foreach ($files as $file){
- $file_name = $pkg->get_file_name($file);
- $file_package = $pkg->get_file_package($file);
- Packager::warn("- $file_package/$file_name: [" . implode(", ", $pkg->get_file_provides($file)) . "]\n");
+ $required = $packager->get_required_for_source($source);
+ foreach ($required as $required_source){
+ $file_name = $required_source->get_name();
+ $file_package = $required_source->get_package_name();
+
+ warn("- $file_package/$file_name: [" . implode(", ", $required_source->get_provides()) . "]\n");
}
- echo $pkg->build($files, array(), array(), $blocks);
+ echo Packager::strip_blocks($source->build(), $blocks);
break;
@@ -204,7 +208,7 @@ switch ($command){
}
function usage($command = ''){
- $dir = dirname(__FILE__);
+ $dir = __DIR__;
if (empty($command) || !file_exists("$dir/help/$command.txt")) $command = 'default';
echo file_get_contents("$dir/help/$command.txt");
}
diff --git a/packager.php b/packager.php
deleted file mode 100644
index b5ecb32..0000000
--- a/packager.php
+++ /dev/null
@@ -1,341 +0,0 @@
-parse_manifest($package_path);
- }
-
- private function parse_manifest($path){
- $pathinfo = pathinfo($path);
-
- if (is_dir($path)){
-
- $package_path = $pathinfo['dirname'] . '/' . $pathinfo['basename'] . '/';
-
- if (file_exists($package_path . 'package.yml')){
- $manifest_path = $package_path . 'package.yml';
- $manifest_format = 'yaml';
- } else if (file_exists($package_path . 'package.yaml')){
- $manifest_path = $package_path . 'package.yaml';
- $manifest_format = 'yaml';
- } else if (file_exists($package_path . 'package.json')){
- $manifest_path = $package_path . 'package.json';
- $manifest_format = 'json';
- }
-
- } else if (file_exists($path)){
- $package_path = $pathinfo['dirname'] . '/';
- $manifest_path = $package_path . $pathinfo['basename'];
- $manifest_format = $pathinfo['extension'];
- }
-
- if ($manifest_format == 'json') $manifest = json_decode(file_get_contents($manifest_path), true);
- else if ($manifest_format == 'yaml' || $manifest_format == 'yml') $manifest = YAML::decode_file($manifest_path);
-
- if (empty($manifest)) throw new Exception("manifest not found in $package_path, or unable to parse manifest.");
-
- $package_name = $manifest['name'];
-
- if ($this->root == null) $this->root = $package_name;
-
- if (array_has($this->manifests, $package_name)) return;
-
- $manifest['path'] = $package_path;
- $manifest['manifest'] = $manifest_path;
-
- $this->manifests[$package_name] = $manifest;
-
- foreach ($manifest['sources'] as $i => $path){
-
- $path = $package_path . $path;
-
- // this is where we "hook" for possible other replacers.
- $source = file_get_contents($path);
-
- $descriptor = array();
-
- // get contents of first comment
- preg_match('/\/\*\s*^---(.*?)^\.\.\.\s*\*\//ms', $source, $matches);
-
- if (!empty($matches)) $descriptor = YAML::decode($matches[0]);
-
- // populate / convert to array requires and provides
- $requires = (array)(!empty($descriptor['requires']) ? $descriptor['requires'] : array());
- $provides = (array)(!empty($descriptor['provides']) ? $descriptor['provides'] : array());
- $file_name = !empty($descriptor['name']) ? $descriptor['name'] : basename($path, '.js');
-
- // "normalization" for requires. Fills up the default package name from requires, if not present.
- foreach ($requires as $i => $require)
- $requires[$i] = implode('/', $this->parse_name($package_name, $require));
-
- $license = array_get($descriptor, 'license');
-
- $this->packages[$package_name][$file_name] = array_merge($descriptor, array(
- 'package' => $package_name,
- 'requires' => $requires,
- 'provides' => $provides,
- 'source' => $source,
- 'path' => $path,
- 'package/name' => $package_name . '/' . $file_name,
- 'license' => empty($license) ? array_get($manifest, 'license') : $license
- ));
-
- }
- }
-
- public function add_package($package_path){
- $this->parse_manifest($package_path);
- }
-
- public function remove_package($package_name){
- unset($this->packages[$package_name]);
- unset($this->manifests[$package_name]);
- }
-
- // # private UTILITIES
-
- private function parse_name($default, $name){
- $exploded = explode('/', $name);
- $length = count($exploded);
- if ($length == 1) return array($default, $exploded[0]);
- if (empty($exploded[0])) return array($default, $exploded[1]);
- return array($exploded[0], $exploded[1]);
- }
-
- // # private HASHES
-
- private function component_to_hash($name){
- $pair = $this->parse_name($this->root, $name);
- $package = array_get($this->packages, $pair[0]);
-
- if (!empty($package)){
- $component = $pair[1];
-
- foreach ($package as $file => $data){
- foreach ($data['provides'] as $c){
- if ($c == $component) return $data;
- }
- }
- }
-
- return null;
- }
-
- private function file_to_hash($name){
- $pair = $this->parse_name($this->root, $name);
- $package = array_get($this->packages, $pair[0]);
-
- if (!empty($package)){
- $file_name = $pair[1];
-
- foreach ($package as $file => $data){
- if ($file == $file_name) return $data;
- }
- }
-
- return null;
- }
-
- public function file_exists($name){
- return $this->file_to_hash($name) ? true : false;
- }
-
- public function component_exists($name){
- return $this->component_to_hash($name) ? true : false;
- }
-
- public function package_exists($name){
- return array_contains($this->get_packages(), $name);
- }
-
- public function validate($more_files = array(), $more_components = array(), $more_packages = array()){
-
- foreach ($this->packages as $name => $files){
- foreach ($files as $file){
- $file_requires = $file['requires'];
- foreach ($file_requires as $component){
- if (!$this->component_exists($component)){
- self::warn("WARNING: The component $component, required in the file " . $file['package/name'] . ", has not been provided.\n");
- }
- }
- }
- }
-
- foreach ($more_files as $file){
- if (!$this->file_exists($file)) self::warn("WARNING: The required file $file could not be found.\n");
- }
-
- foreach ($more_components as $component){
- if (!$this->component_exists($component)) self::warn("WARNING: The required component $component could not be found.\n");
- }
-
- foreach ($more_packages as $package){
- if (!$this->package_exists($package)) self::warn("WARNING: The required package $package could not be found.\n");
- }
- }
-
- // # public BUILD
-
- public function build($files = array(), $components = array(), $packages = array(), $blocks = array()){
-
- if (!empty($components)){
- $more = $this->components_to_files($components);
- foreach ($more as $file) array_include($files, $file);
- }
-
- foreach ($packages as $package){
- $more = $this->get_all_files($package);
- foreach ($more as $file) array_include($files, $file);
- }
-
- $files = $this->complete_files($files);
-
- if (empty($files)) return '';
-
- $included_sources = array();
- foreach ($files as $file) $included_sources[] = $this->get_file_source($file);
-
- $source = implode($included_sources, "\n\n");
-
- foreach ($blocks as $block){
- $source = preg_replace_callback("%(/[/*])\s*<$block>(.*?)$block>(?:\s*\*/)?%s", array($this, "block_replacement"), $source);
- }
-
- return $source . "\n";
- }
-
- private function block_replacement($matches){
- return (strpos($matches[2], ($matches[1] == "//") ? "\n" : "*/") === false) ? $matches[2] : "";
- }
-
- public function build_from_files($files){
- return $this->build($files);
- }
-
- public function build_from_components($components){
- return $this->build(array(), $components);
- }
-
- public function write_from_files($file_name, $files = null){
- $full = $this->build_from_files($files);
- file_put_contents($file_name, $full);
- }
-
- public function write_from_components($file_name, $components = null){
- $full = $this->build_from_components($components);
- file_put_contents($file_name, $full);
- }
-
- // # public FILES
-
- public function get_all_files($of_package = null){
- $files = array();
- foreach ($this->packages as $name => $package){
- if ($of_package == null || $of_package == $name) foreach ($package as $file){
- $files[] = $file['package/name'];
- }
- }
- return $files;
- }
-
- public function get_file_dependancies($file){
- $hash = $this->file_to_hash($file);
- if (empty($hash)) return array();
- return $this->complete_files($this->components_to_files($hash['requires']));
- }
-
- public function complete_file($file){
- $files = $this->get_file_dependancies($file);
- $hash = $this->file_to_hash($file);
- if (empty($hash)) return array();
- array_include($files, $hash['package/name']);
- return $files;
- }
-
- public function complete_files($files){
- $ordered_files = array();
- foreach ($files as $file){
- $all_files = $this->complete_file($file);
- foreach ($all_files as $one_file) array_include($ordered_files, $one_file);
- }
- return $ordered_files;
- }
-
- // # public COMPONENTS
-
- public function component_to_file($component){
- return array_get($this->component_to_hash($component), 'package/name');
- }
-
- public function components_to_files($components){
- $files = array();
- foreach ($components as $component){
- $file_name = $this->component_to_file($component);
- if (!empty($file_name) && !in_array($file_name, $files)) $files[] = $file_name;
- }
- return $files;
- }
-
- // # dynamic getter for PACKAGE properties and FILE properties
-
- public function __call($method, $arguments){
- if (strpos($method, 'get_file_') === 0){
- $file = array_get($arguments, 0);
- if (empty($file)) return null;
- $key = substr($method, 9);
- $hash = $this->file_to_hash($file);
- return array_get($hash, $key);
- }
-
- if (strpos($method, 'get_package_') === 0){
- $key = substr($method, 12);
- $package = array_get($arguments, 0);
- $package = array_get($this->manifests, (empty($package)) ? $this->root : $package);
- return array_get($package, $key);
- }
-
- return null;
- }
-
- public function get_packages(){
- return array_keys($this->packages);
- }
-
- // authors normalization
-
- public function get_package_authors($package = null){
- if (empty($package)) $package = $this->root;
- $package = array_get($this->manifests, $package);
- if (empty($package)) return array();
- return $this->normalize_authors(array_get($package, 'authors'), array_get($package, 'author'));
- }
-
- public function get_file_authors($file){
- $hash = $this->file_to_hash($file);
- if (empty($hash)) return array();
- return $this->normalize_authors(array_get($hash, 'authors'), array_get($hash, 'author'), $this->get_package_authors());
- }
-
- private function normalize_authors($authors = null, $author = null, $default = null){
- $use = empty($authors) ? $author : $authors;
- if (empty($use) && !empty($default)) return $default;
- if (is_array($use)) return $use;
- if (empty($use)) return array();
- return array($use);
- }
-
-}
diff --git a/test/fixtures/Source/Array.js b/test/fixtures/Source/Array.js
new file mode 100644
index 0000000..feee361
--- /dev/null
+++ b/test/fixtures/Source/Array.js
@@ -0,0 +1,176 @@
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ every: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]);
+ }
+ return results;
+ },
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ indexOf: function(item, from){
+ var len = this.length;
+ for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return value.toInt(16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+//<1.2compat>
+
+Array.alias('extend', 'append');
+
+var $pick = function(){
+ return Array.from(arguments).pick();
+};
+
+//1.2compat>
diff --git a/test/fixtures/Source/Browser.js b/test/fixtures/Source/Browser.js
new file mode 100644
index 0000000..93946be
--- /dev/null
+++ b/test/fixtures/Source/Browser.js
@@ -0,0 +1,262 @@
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var UID = 1;
+
+this.$uid = (window.ActiveXObject) ? function(item){
+ return (item.uid || (item.uid = [UID++]))[0];
+} : function(item){
+ return item.uid || (item.uid = UID++);
+};
+
+$uid(window);
+$uid(document);
+
+var ua = navigator.userAgent.toLowerCase(),
+ platform = navigator.platform.toLowerCase(),
+ UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
+ mode = UA[1] == 'ie' && document.documentMode;
+
+var Browser = this.Browser = {
+
+ extend: Function.prototype.extend,
+
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+
+ version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+
+ Platform: {
+ name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
+ },
+
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+
+ Plugins: {}
+
+};
+
+Browser[Browser.name] = true;
+Browser[Browser.name + parseInt(Browser.version, 10)] = true;
+Browser.Platform[Browser.Platform.name] = true;
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+// Flash detection
+
+var version = (Function.attempt(function(){
+ return navigator.plugins['Shockwave Flash'].description;
+}, function(){
+ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+}) || '0 r0').match(/\d+/g);
+
+Browser.Plugins.Flash = {
+ version: Number(version[0] || '0.' + version[1]) || 0,
+ build: Number(version[2]) || 0
+};
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/