diff --git a/tools/combine b/tools/combine
index e975c0d..3196e6e 100755
--- a/tools/combine
+++ b/tools/combine
@@ -28,11 +28,17 @@ TPL_TEST_PATH="$PATH_TOOLS/combine-test.tpl"
TPL_VAR='/**{CONTENT}**/'
RMTRAIL="$PATH_TOOLS/rmtrail"
-# order matters
-CAT_MODS="warn prop_parser util VisibilityObjectFactory"
-CAT_MODS="$CAT_MODS FallbackVisibilityObjectFactory member_builder"
-CAT_MODS="$CAT_MODS ClassBuilder VisibilityObjectFactoryFactory"
-CAT_MODS="$CAT_MODS class class_final class_abstract interface"
+# determine the order in which modules must be concatenated; order matters to
+# ensure dependencies are loaded before the module that depends on them
+CAT_MODULES=$(
+ cd "$PATH_TOOLS/../" &&
+ grep -rIo ' require(.*)' lib/ \
+ | sed "s/^lib\///;s/\.js://;s/require(.*'\/\(.*\)'.*/\1/" \
+ | node tools/combine-order.js
+) || {
+ echo "Failed to get module list" >&2
+ exit 3
+}
##
# Output template header
@@ -85,7 +91,7 @@ fi
tpl_header
# output each of the modules
-for module in $CAT_MODS; do
+for module in $CAT_MODULES; do
filename="$PATH_LIB/$module.$MODULE_EXT"
if [ ! -f "$filename" ]; then
diff --git a/tools/combine-order.js b/tools/combine-order.js
new file mode 100644
index 0000000..0f2afc6
--- /dev/null
+++ b/tools/combine-order.js
@@ -0,0 +1,190 @@
+/**
+ * Determines dependency order
+ *
+ * This script will determine the order in which files must be concatenated in
+ * order to run properly. Dependencies must be added before the file that
+ * depends on them.
+ *
+ * Circular dependencies are not supported, nor should they be.
+ *
+ * Copyright (C) 2010 Mike Gerwitz
+ *
+ * This file is part of ease.js.
+ *
+ * ease.js is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ * @author Mike Gerwitz
+ * @package tools
+ */
+
+
+// files and their dependencies are expected from stdin in the following format,
+// one relationship per line:
+// file dep
+getStdinData( function( data )
+{
+ var ordered = [],
+ deps = parseLines( data ),
+ current = ''
+ ;
+
+ // Continue the ordering process until there are no more files remaining.
+ // This is necessary because not all files may be a dependency of another.
+ // There may be groups of completely unrelated files.
+ while ( current = Object.keys( deps )[0] )
+ {
+ orderDeps( deps, current, ordered );
+ }
+
+ outputFiles( ordered );
+} );
+
+
+/**
+ * Output each file, one per line
+ *
+ * @param {Array.} files files to output
+ *
+ * @return {undefined}
+ */
+function outputFiles( files )
+{
+ files.forEach( function( file )
+ {
+ console.log( file );
+ } );
+}
+
+
+/**
+ * Recursively order dependencies for each file
+ *
+ * For each file (current var), loop through each of its dependencies. Before
+ * adding itself to the list, it will recurse and loop through the dependency's
+ * dependencies. This process will continue until no more dependencies are
+ * found or found dependencies have already been parsed. This will result in the
+ * file being added to the list after each of its dependencies, recursively. It
+ * may be easier just to read the actual code.
+ *
+ * Circular dependencies are not supported, but they are detected and will
+ * cause termination of the script with a non-zero exit code.
+ *
+ * @param {Object} deps dependency list
+ * @param {string} current file to parse
+ * @param {Array} ordered destination array
+ *
+ * @return {Array} ordered array
+ */
+var orderDeps = ( function()
+{
+ var processed = {},
+ processing = {};
+
+ return function orderDeps( deps, current, ordered )
+ {
+ // if we've already processed this dependency, don't do it again
+ if ( processed[ current ] )
+ {
+ return;
+ }
+
+ // prevent infinite recursion that would be caused by circular
+ // dependencies
+ if ( processing[ current ] )
+ {
+ console.error( "Circular dependency detected: %s", current );
+ process.exit( 1 );
+ }
+
+ processing[ current ] = true;
+
+ // process each dependency for this file
+ ( deps[ current ] || [] ).forEach( function( dep )
+ {
+ // first, insert dependencies of our dependency
+ orderDeps( deps, dep, ordered );
+ } );
+
+ // add self /after/ dependencies have been added
+ ordered.push( current );
+ processed[ current ] = true;
+
+ // ensure we don't parse it again
+ delete deps[ current ];
+ delete processing[ current ];
+
+ return ordered;
+ };
+} )();
+
+
+/**
+ * Parse each line into a dependency list for each file
+ *
+ * @param {string} data string data to parse
+ *
+ * @return {Object.>} dependency list for each file
+ */
+function parseLines( data )
+{
+ var lines = data.split( '\n' ),
+ deps = {};
+
+ lines.forEach( function( line )
+ {
+ // skip blank lines
+ if ( !line )
+ {
+ return;
+ }
+
+ var dep_info = line.split( ' ' ),
+ file = dep_info[ 0 ],
+ dep = dep_info[ 1 ]
+ ;
+
+ deps[ file ] = deps[ file ] || [];
+ deps[ file ].push( dep );
+ } );
+
+ return deps;
+}
+
+
+/**
+ * Asynchronously retrieve data from stdin
+ *
+ * @param {function(Object)} callback to call with data
+ *
+ * @return {string} data from stdin
+ */
+function getStdinData( callback )
+{
+ var data = '';
+
+ process.stdin.resume();
+ process.stdin.setEncoding( 'ascii' );
+
+ process.stdin
+ .on( 'data', function( chunk )
+ {
+ data += chunk;
+ } )
+ .on( 'end', function()
+ {
+ callback( data );
+ } )
+ ;
+}
+