toposort.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. !function() {
  2. "use strict";
  3. /**
  4. * Topological sort class.
  5. * Original by Marcel Klehr, contributed by Gustavo Henke.
  6. *
  7. * @class
  8. * @since 0.1.0
  9. * @see https://github.com/marcelklehr/toposort
  10. * @author Marcel Klehr <mklehr@gmx.net>
  11. *
  12. * @see https://github.com/gustavohenke/toposort
  13. * @author Gustavo Henke <gustavo@injoin.com.br>
  14. */
  15. function Toposort() {
  16. var self = this;
  17. var edges = [];
  18. /**
  19. * Adds dependency edges.
  20. *
  21. * @since 0.1.0
  22. * @param {String} item An dependent name. Must be an string and not empty
  23. * @param {String[]|String} [deps] An dependency or array of dependencies
  24. * @returns {Toposort} The Toposort instance
  25. */
  26. self.add = function( item, deps ) {
  27. if( typeof item !== "string" || !item ) {
  28. throw new TypeError( "Dependent name must be given as a not empty string" );
  29. }
  30. deps = Array.isArray( deps ) ? deps.slice() : [deps];
  31. if( deps.length ) {
  32. deps.forEach( function( dep ) {
  33. if( typeof dep !== "string" || !dep ) {
  34. throw new TypeError(
  35. "Dependency name must be given as a not empty string"
  36. );
  37. }
  38. edges.push( [item, dep] );
  39. } );
  40. } else {
  41. edges.push( [item] );
  42. }
  43. return self;
  44. };
  45. /**
  46. * Runs the toposorting and return an ordered array of strings
  47. *
  48. * @since 0.1.0
  49. * @returns {String[]} The list of items topologically sorted.
  50. */
  51. self.sort = function() {
  52. var nodes = [];
  53. var sorted = [];
  54. edges.forEach( function( edge ) {
  55. edge.forEach( function( n ) {
  56. if( nodes.indexOf( n ) === -1 ) {
  57. nodes.push( n );
  58. }
  59. } );
  60. } );
  61. function visit( node, predecessors, i ) {
  62. var index, predsCopy;
  63. predecessors = predecessors || [];
  64. if( predecessors.indexOf( node ) > -1 ) {
  65. throw new Error(
  66. "Cyclic dependency found. '" + node + "' is dependent of itself.\n" +
  67. "Dependency Chain: " + predecessors.join( " -> " ) + " => " + node
  68. );
  69. }
  70. index = nodes.indexOf( node );
  71. if( index === -1 ) {
  72. return i;
  73. }
  74. nodes.splice( index, 1 );
  75. if( predecessors.length === 0 ) {
  76. i--;
  77. }
  78. predsCopy = predecessors.slice();
  79. predsCopy.push( node );
  80. edges.filter( function( e ) {
  81. return e[0] === node;
  82. } ).forEach( function( e ) {
  83. i = visit( e[1], predsCopy, i );
  84. } );
  85. sorted.unshift( node );
  86. return i;
  87. }
  88. for( var i = 0; i < nodes.length; i++ ) {
  89. i = visit( nodes[i], null, i );
  90. }
  91. return sorted;
  92. };
  93. }
  94. if( typeof module === "object" && module && typeof module.exports === "object" ) {
  95. // Expose toposort to CommonJS loaders (aka Node)
  96. module.exports = exports.Toposort = Toposort;
  97. } else {
  98. // Expose toposort to AMD loaders (aka Require.js)
  99. if( typeof define === "function" && define.amd ) {
  100. define( function() {
  101. return Toposort;
  102. } );
  103. }
  104. // Expose toposort as a browser global
  105. if( typeof window === "object" ) {
  106. window.Toposort = Toposort;
  107. }
  108. }
  109. }();