jxLoader.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. /**
  2. * The jxLoader main class which does all of the work of ordering files from various
  3. * repositories according to their dependencies.
  4. */
  5. //requires
  6. var yaml = require('js-yaml'),
  7. sys = require('sys'),
  8. fsp = require('promised-io/fs'),
  9. fs = require('fs'),
  10. path = require('path'),
  11. util = require('util'),
  12. Walker = require('walker');
  13. //check to see if mootools is already in the environment
  14. if (typeof MooTools == 'undefined') {
  15. require('mootools').apply(GLOBAL);
  16. sys.puts('loaded mootools');
  17. } else {
  18. sys.puts('mootools already loaded');
  19. }
  20. var jxLoader = new Class({
  21. Implements: [Events, Options],
  22. options: {},
  23. config: null,
  24. repos: null,
  25. flat: null,
  26. numRepos: 0,
  27. loadedRepos: 0,
  28. logger: null,
  29. debug: false,
  30. initialize: function (options) {
  31. this.setOptions(options);
  32. this.config = {};
  33. this.repos = {};
  34. this.logger = !nil(options.logger) ? options.logger : console;
  35. this.logger.info("Received logger in jxLoader");
  36. this.debug = nil(options.debug)? false : options.debug;
  37. },
  38. /**
  39. * Add a repository to the loader
  40. *
  41. * Paramaters:
  42. * config - The config should be an object that lists the appropriate keys for
  43. * the listed repository
  44. */
  45. addRepository: function (config) {
  46. this.config = Object.merge(this.config, {repos: config});
  47. this.numRepos = Object.getLength(this.config.repos);
  48. Object.each(this.config.repos, function(conf, key){
  49. if (nil(this.repos[key])) {
  50. this.loadRepository(key, conf);
  51. }
  52. },this);
  53. },
  54. loadRepository: function (key, config) {
  55. var p = config.paths.js,
  56. me = this;
  57. //walk the path and process all files we find...
  58. Walker(p).filterDir(function(dir){
  59. return !(dir.test('^\.[\S\s]*$','i'));
  60. }).on('file', function(file){
  61. try {
  62. var data = fs.readFileSync(file, 'utf-8');
  63. if (this.debug) sys.puts('File contents: ' + sys.inspect(data));
  64. //process the file
  65. var descriptor = {},
  66. regexp = /^-{3}\s*\n*([\S\s]*)\n+\.{3}/m, //regexp to get yaml contents
  67. matches = data.match(regexp);
  68. if (me.debug) me.logger.debug('All matches from getting yaml headers: ' + util.inspect(matches,false,null));
  69. if (!nil(matches)) {
  70. matches.shift();
  71. delete matches.index;
  72. delete matches.input;
  73. if (me.debug) me.logger.debug('matches is a ' + typeOf(matches));
  74. if (me.debug) me.logger.debug('Matches from getting yaml headers: ' + matches[0]);
  75. //remove \n from the string
  76. var str = matches[0].replace(new RegExp('\r','g'),'');
  77. if (me.debug) me.logger.debug('Matches from getting yaml headers after replacement: ' + str);
  78. try {
  79. descriptor = yaml.load(str);
  80. } catch (err) {
  81. me.logger.error('!!! error converting yaml');
  82. me.logger.error('YAML object: ' + util.inspect(yaml,false,null));
  83. me.logger.error('error: ' + util.inspect(err,false,null));
  84. throw err;
  85. }
  86. me.logger.debug('object returned from yaml eval = ' + util.inspect(descriptor,false,null));
  87. var requires = Array.from(!nil(descriptor.requires) ? descriptor.requires : []);
  88. var provides = Array.from(!nil(descriptor.provides) ? descriptor.provides : []);
  89. var optional = Array.from(!nil(descriptor.optional) ? descriptor.optional : []);
  90. var filename = path.basename(file);
  91. //normalize requires and optional. Fills up the default package name
  92. //if one is not present and strips version info
  93. requires.each(function(r, i){
  94. requires[i] = me.parse_name(key, r).join('/').replace(' ','');
  95. },this);
  96. optional.each(function(r, i){
  97. optional[i] = me.parse_name(key, r).join('/').replace(' ','');
  98. },this);
  99. if (nil(me.repos[key])) {
  100. me.repos[key] = {};
  101. }
  102. var index = String.uniqueID();
  103. me.repos[key][index] = Object.merge(descriptor,{
  104. repo: key,
  105. requires: requires,
  106. provides: provides,
  107. optional: optional,
  108. path: file
  109. });
  110. //make sure this is truly an object, not an array
  111. var obj = {};
  112. Object.each(me.repos[key][index], function(value, key){
  113. obj[key] = value;
  114. });
  115. me.repos[key][index] = obj;
  116. if (me.debug) me.logger.debug('Done processing ' + filename);
  117. } else {
  118. //there is no yaml header... drop this file
  119. me.logger.debug('no header for ' + file);
  120. }
  121. } catch (err) {
  122. me.logger.error('!!!err : ' + util.inspect(err,false,null));
  123. //do nothing, just finish up
  124. //sys.puts('no file ' + file);
  125. throw err;
  126. }
  127. return;
  128. })
  129. .on('end',function(){
  130. this.loadedRepos++;
  131. if (this.loadedRepos == this.numRepos) {
  132. this.fireEvent('loadRepoDone', [key]);
  133. }
  134. }.bind(this));
  135. },
  136. parse_name: function (def, name){
  137. var exploded = name.split('/');
  138. if (this.debug) this.logger.debug('exploded = ' + util.inspect(exploded,false,null));
  139. if (exploded.length == 1 ) {
  140. if (this.debug) this.logger.debug("returning from parse_name: " + util.inspect([def, exploded[0]],false,null));
  141. return [def, exploded[0]];
  142. }
  143. if (nil(exploded[0]) || exploded[0].length === 0) {
  144. if (this.debug) this.logger.debug("returning from parse_name: " + util.inspect([def, exploded[1]],false,null));
  145. return [def, exploded[1]];
  146. }
  147. var exploded2 = exploded[0].split(':');
  148. if (exploded2.length == 1) {
  149. if (this.debug) this.logger.debug("returning from parse_name: " + util.inspect(exploded,false,null));
  150. return exploded;
  151. }
  152. if (this.debug) this.logger.debug("returning from parse_name: " + util.inspect([exploded2[0],exploded[1]],false,null));
  153. return [exploded2[0],exploded[1]];
  154. },
  155. flatten: function (obj) {
  156. var flat = {};
  157. Object.each(obj, function(items, repo){
  158. Object.each(items, function(value, key){
  159. value.provides.each(function(val){
  160. val = val.replace(' ','');
  161. flat[repo.toLowerCase() + '/' + val.toLowerCase()] = value;
  162. },this);
  163. },this);
  164. },this);
  165. return flat;
  166. },
  167. getRepoArray: function () {
  168. return this.repos;
  169. },
  170. getFlatArray: function () {
  171. return this.flat;
  172. },
  173. compileDeps: function (classes, repos, type, opts, exclude) {
  174. opts = !nil(opts) ? opts : true;
  175. exclude = !nil(exclude) ? exclude : [];
  176. var list = [];
  177. if (nil(this.flat)) {
  178. this.flat = this.flatten(this.repos);
  179. }
  180. if (!nil(repos)) {
  181. sys.puts("in compileDeps... ready to get repo info");
  182. sys.puts("repos to get: " + util.inspect(repos, false, null));
  183. sys.puts("type: " + type);
  184. Array.from(repos).each(function(val){
  185. var o = {};
  186. o[val] = this.repos[val];
  187. var flat = this.flatten(o);
  188. Object.each(flat, function(obj, key){
  189. obj.visited = false;
  190. },this);
  191. Object.each(flat, function(obj, key){
  192. list = this.includeDependencies(val, key, opts, exclude, flat, list, type, [key]);
  193. },this);
  194. },this);
  195. }
  196. if (!nil(classes)) {
  197. classes.each(function(val){
  198. var r = this.findRepo(val);
  199. //clear visited reference
  200. Object.each(this.flat, function(obj, key){
  201. obj.visited = false;
  202. },this);
  203. list = this.includeDependencies(r, val, opts, exclude, this.flat, list, type);
  204. },this);
  205. }
  206. this.logger.info('list of dependencies: ' + util.inspect(list,false,null));
  207. return list;
  208. },
  209. compile: function (classes, repos, type, includeDeps, theme, exclude, opts) {
  210. type = !nil(type) ? type : 'js';
  211. includeDeps = !nil(includeDeps) ? includeDeps : true;
  212. theme = !nil(theme) ? theme : '';
  213. exclude = !nil(exclude) ? exclude : [];
  214. opts = !nil(opts) ? opts : true;
  215. if (nil(this.flat)) {
  216. this.flat = this.flatten(this.repos);
  217. }
  218. var deps,
  219. ret;
  220. this.logger.info("repos passed in: " + util.inspect(repos,false,null));
  221. if (includeDeps || !nil(repos)) {
  222. deps = this.compileDeps(classes, repos, type, opts, exclude);
  223. } else {
  224. deps = this.convertClassesToDeps(classes, type, exclude);
  225. }
  226. if (deps.length > 0) {
  227. var included = [],
  228. sources = [],
  229. ret2;
  230. if (type == 'js') {
  231. ret2 = this.getJsFiles(sources, included, deps);
  232. } else {
  233. ret2 = this.getCssFiles(sources, included, theme, deps);
  234. }
  235. ret = {
  236. included: ret2.includes,
  237. source: ret2.sources.join('\n\n')
  238. };
  239. } else {
  240. ret = false;
  241. }
  242. if (this.debug) this.logger.warn("returning: " + util.inspect(ret,false,null));
  243. return ret;
  244. },
  245. includeDependencies: function (repo, klass, opts, exclude, flat, list, type, ml) {
  246. klass = klass.contains('/') ? klass : repo.toLowerCase() + '/' + klass.toLowerCase();
  247. if (!Object.keys(flat).contains(klass)) {
  248. return list;
  249. }
  250. var inf = flat[klass];
  251. if ((inf.visited && ml.contains(klass)) ||
  252. (type=='js' && (exclude.contains(inf.path) || list.contains(inf.path))) ||
  253. (type=='css' && (exclude.contains(klass) || list.contains(klass))) ||
  254. (type=='jsdeps' && (exclude.contains(inf.path) || list.contains(klass)))) {
  255. return list;
  256. }
  257. var requires = Array.from(inf.requires);
  258. flat[klass].visited = true;
  259. if (opts && Object.keys(inf).contains('optional') && inf.optional.length > 0) {
  260. requires.combine(inf.optional);
  261. }
  262. if (requires.length > 0) {
  263. requires.each(function(req){
  264. var parts = req.split('/');
  265. if (nil(ml)) {
  266. ml = [];
  267. }
  268. ml.push(klass);
  269. list = this.includeDependencies(parts[0],parts[1],opts, exclude, flat, list, type, ml);
  270. ml.pop();
  271. },this);
  272. }
  273. if (type=='js') {
  274. list.push(inf.path);
  275. } else {
  276. list.push(klass);
  277. }
  278. return list;
  279. },
  280. convertClassesToDeps: function (classes, type, exclude) {
  281. var list;
  282. if (typeOf(classes) != 'array') {
  283. classes = Array.from(classes);
  284. }
  285. classes.each(function(klass){
  286. if (klass.contains('/')) {
  287. if (type=='js' && !exclude.contains(this.flat[klass.toLowerCase()].path)) {
  288. list.push(this.flat[klass.toLowerCase()].path);
  289. } else if (type=='css' && !exclude.contains(klass)) {
  290. list.push(klass);
  291. } else {
  292. Object.each(this.flat, function(arr, key) {
  293. var parts = key.split('/');
  294. if (parts[0].toLowerCase() == klass.toLowerCase()) {
  295. if (type=='js' && !exclude.contains(arr.path)) {
  296. list.push(arr.path);
  297. } else if (type=='css' && !exclude.contains(klass)) {
  298. list.push(key);
  299. }
  300. }
  301. },this);
  302. }
  303. }
  304. },this);
  305. return list;
  306. },
  307. findRepo: function(klass) {
  308. if (klass.contains('/')) {
  309. var parts = klass.split('/');
  310. return parts[0];
  311. } else {
  312. if (nil(this.flat)) {
  313. this.flat = this.flatten(this.repos);
  314. }
  315. var ret;
  316. Object.each(this.flat, function(arr, key){
  317. var parts = key.split('/');
  318. if (parts[1].toLowerCase() == klass.toLowerCase()) {
  319. ret = parts[0];
  320. }
  321. },this);
  322. return ret;
  323. }
  324. },
  325. getJsFiles: function (sources, included, deps) {
  326. this.logger.debug('list of dependencies: ' + util.inspect(deps,false,null));
  327. deps.each(function(filename){
  328. this.logger.debug('reading file: ' + filename);
  329. var s = fs.readFileSync(filename, 'utf-8');
  330. //Remove any tags
  331. if (this.options.tags !== null && this.options.tags !== undefined) {
  332. Array.from(this.options.tags).each(function(tag){
  333. var t = tag.escapeRegExp();
  334. re = new RegExp('//<' + t + '>[\\s\\S]*?//</' + t + '>','gi');
  335. s = s.replace(re,'');
  336. re = new RegExp('/\\*<' + t + '>\\*/[\\s\\S]*?/\\*</' + t + '>\\*/','gi');
  337. this.logger.debug('2nd regex = ' + util.inspect(re,false,null));
  338. s = s.replace(re,'');
  339. },this);
  340. }
  341. sources.push(s);
  342. included.push(filename);
  343. this.logger.debug('done reading file: ' + filename);
  344. },this);
  345. return {
  346. includes: included,
  347. sources: sources
  348. };
  349. },
  350. getCssFiles: function (sources, included, theme, deps) {
  351. deps.each(function(dep){
  352. var parts = dep.split('/');
  353. included.push(dep);
  354. if (!nil(this.config.repos[parts[0]].paths.css)) {
  355. var csspath = this.config.repos[parts[0]].paths.css;
  356. csspath = csspath.replace('{theme}',theme);
  357. csspath = fs.realpathSync(csspath);
  358. var cssfiles = !nil(this.flat[dep].css) ? this.flat[dep].css : '';
  359. if (cssfiles.length > 0) {
  360. cssfiles.each(function(css){
  361. var fp = csspath + '/' + css + '.css';
  362. if (path.existsSync(fp)) {
  363. var s = fs.readFileSync(fp, 'utf-8');
  364. if (this.options.rewriteImageUrl && !nil(this.config.repos[parts[0]].imageUrl)) {
  365. s = s.replace(new RegExp(this.config.repos[parts[0]].imageUrl, 'g'),this.options.imagePath);
  366. } else {
  367. this.logger.info('not updating urls in css file ' + css);
  368. }
  369. sources.push(s);
  370. } else {
  371. if (!nil(this.config.repos[parts[0]].paths.cssalt)) {
  372. var csspathalt = this.config.repos[parts[0]].paths.cssalt;
  373. csspathalt = csspathalt.replace('{theme}',theme);
  374. csspathalt = fs.realpathSync(csspathalt);
  375. fp = csspathalt + '/' + css + '.css';
  376. if (path.existsSync(fp)) {
  377. var s = fs.readFileSync(fp, 'utf-8');
  378. if (this.options.rewriteImageUrl && !nil(this.config.repos[parts[0]].imageUrl)) {
  379. s = s.replace(new RegExp(this.config.repos[parts[0]].imageUrl, 'g'),this.options.imagePath);
  380. } else {
  381. this.logger.info('not updating urls in css file ' + css);
  382. }
  383. sources.push(s);
  384. }
  385. }
  386. }
  387. },this);
  388. if (this.options.moveImages && !nil(this.flat[dep].images)) {
  389. var imageFiles = this.flat[dep].images;
  390. if (imageFiles.length > 0) {
  391. var ipath = this.config.repos[parts[0]].paths.images,
  392. imageLocation = this.config.repos[parts[0]].imageLocation;
  393. if (ipath.contains('{theme}')) {
  394. ipath = ipath.replace('{theme}', theme);
  395. }
  396. ipath = fs.realpathSync(ipath);
  397. //create destination if it's not already there
  398. if (!path.existsSync(imageLocation)) {
  399. fs.mkdirSync(imageLocation);
  400. }
  401. imageFiles.each(function(file){
  402. if (!path.existsSync(imageLocation + '/' + file)) {
  403. var inStr = fs.createReadStream(ipath + '/' + file),
  404. outStr = fs.createWriteStream(imageLocation + '/' + file);
  405. inStr.pipe(outStr);
  406. } else {
  407. this.logger.info('\t\tFile already exists');
  408. }
  409. },this);
  410. } else {
  411. this.logger.info('No image files to move');
  412. }
  413. } else {
  414. this.logger.info('Not moving image files');
  415. }
  416. }
  417. }
  418. },this);
  419. return {
  420. includes: included,
  421. sources: sources
  422. };
  423. }
  424. });
  425. exports.jxLoader = jxLoader;