/** * 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) 2011 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 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * * @author Mike Gerwitz */ // 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 ); } ) ; }