Makefile.dryice.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #!/usr/bin/env node
  2. /* ***** BEGIN LICENSE BLOCK *****
  3. * Distributed under the BSD license:
  4. *
  5. * Copyright (c) 2010, Ajax.org B.V.
  6. * All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions are met:
  10. * * Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. * * Neither the name of Ajax.org B.V. nor the
  16. * names of its contributors may be used to endorse or promote products
  17. * derived from this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  20. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  23. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. *
  30. * ***** END LICENSE BLOCK ***** */
  31. var fs = require("fs");
  32. var path = require("path");
  33. var copy = require('architect-build/copy');
  34. var build = require('architect-build/build');
  35. var ACE_HOME = __dirname;
  36. var BUILD_DIR = ACE_HOME + "/build";
  37. var CACHE = {};
  38. function main(args) {
  39. if (args.indexOf("updateModes") !== -1) {
  40. return updateModes();
  41. }
  42. var type = "minimal";
  43. args = args.map(function(x) {
  44. if (x[0] == "-" && x[1] != "-")
  45. return "-" + x;
  46. return x;
  47. }).filter(Boolean);
  48. if (args[2] && (args[2][0] != "-" || args[2].indexOf("h") != -1))
  49. type = args[2];
  50. var i = args.indexOf("--target");
  51. if (i != -1 && args[i+1])
  52. BUILD_DIR = args[i+1];
  53. if (args.indexOf("--h") == -1) {
  54. if (type == "minimal") {
  55. buildAce({
  56. compress: args.indexOf("--m") != -1,
  57. noconflict: args.indexOf("--nc") != -1,
  58. shrinkwrap: args.indexOf("--s") != -1
  59. });
  60. } else if (type == "normal") {
  61. ace();
  62. } else if (type == "demo") {
  63. demo();
  64. } else if (type == "full") {
  65. ace();
  66. demo();
  67. } else if (type == "highlighter") {
  68. // TODO
  69. }
  70. }
  71. }
  72. function showHelp(type) {
  73. console.log("--- Ace Dryice Build Tool ---");
  74. console.log("");
  75. console.log("Options:");
  76. console.log(" minimal Places necessary Ace files out in build dir; uses configuration flags below [default]");
  77. console.log(" normal Runs four Ace builds--minimal, minimal-noconflict, minimal-min, and minimal-noconflict-min");
  78. console.log(" demo Runs demo build of Ace");
  79. console.log(" full all of above");
  80. console.log(" highlighter ");
  81. console.log("args:");
  82. console.log(" --target ./path path to build folder");
  83. console.log("flags:");
  84. console.log(" --h print this help");
  85. console.log(" --m minify");
  86. console.log(" --nc namespace require");
  87. console.log(" --s shrinkwrap (combines all output files into one)");
  88. console.log("");
  89. if (type)
  90. console.log(" output for " + type + " generated in " + BUILD_DIR);
  91. }
  92. function ace() {
  93. console.log('# ace License | Readme | Changelog ---------');
  94. copy.file(ACE_HOME + "/build_support/editor.html", BUILD_DIR + "/editor.html");
  95. copy.file(ACE_HOME + "/LICENSE", BUILD_DIR + "/LICENSE");
  96. copy.file(ACE_HOME + "/ChangeLog.txt", BUILD_DIR + "/ChangeLog.txt");
  97. console.log('# ace ---------');
  98. for (var i = 0; i < 4; i++)
  99. buildAce({compress: i & 2, noconflict: i & 1, check: true});
  100. }
  101. function demo() {
  102. console.log('# kitchen sink ---------');
  103. var version = "", ref = "";
  104. try {
  105. version = JSON.parse(fs.readFileSync(ACE_HOME + "/package.json")).version;
  106. ref = fs.readFileSync(ACE_HOME + "/.git-ref").toString();
  107. } catch(e) {}
  108. function changeComments(data) {
  109. return (data
  110. .replace("doc/site/images/ace-logo.png", "demo/kitchen-sink/ace-logo.png")
  111. .replace(/<!\-\-DEVEL[\d\D]*?DEVEL\-\->/g, "")
  112. .replace(/PACKAGE\-\->|<!\-\-PACKAGE/g, "")
  113. .replace(/\/\*DEVEL[\d\D]*?DEVEL\*\//g, "")
  114. .replace(/PACKAGE\*\/|\/\*PACKAGE/g, "")
  115. .replace("%version%", version)
  116. .replace("%commit%", ref)
  117. );
  118. }
  119. copy(ACE_HOME +"/demo/kitchen-sink/docs/", BUILD_DIR + "/demo/kitchen-sink/docs/");
  120. copy.file(ACE_HOME + "/demo/kitchen-sink/logo.png", BUILD_DIR + "/demo/kitchen-sink/logo.png");
  121. copy.file(ACE_HOME + "/demo/kitchen-sink/styles.css", BUILD_DIR + "/demo/kitchen-sink/styles.css");
  122. copy.file(ACE_HOME + "/kitchen-sink.html", BUILD_DIR + "/kitchen-sink.html", changeComments);
  123. buildSubmodule({}, {
  124. require: ["kitchen-sink/demo"],
  125. projectType: "demo"
  126. }, BUILD_DIR + "/demo/kitchen-sink/demo");
  127. copy(ACE_HOME + "/demo/", BUILD_DIR + "/demo/", {
  128. shallow: true,
  129. exclude: /\s|requirejs/,
  130. include: /\.(js|html)$/,
  131. replace: function(source) {
  132. if (!/^\s*</.test(source))
  133. return source;
  134. var removeRequireJS;
  135. source = source.replace(/<script src="kitchen-sink\/require.js"[\s\S]+?require\(\[([^\]]+).*/, function(e, m) {
  136. removeRequireJS = true;
  137. var scripts = m.split(/,\s*/);
  138. var result = [];
  139. function comment(str) {result.push("<!-- " + str + " -->")}
  140. function script(str) {result.push('<script src="../src/' + str + '.js"></script>')}
  141. scripts.forEach(function(s) {
  142. s = s.replace(/"/g, "");
  143. if (s == "ace/ace") {
  144. comment("load ace");
  145. script("ace");
  146. } else {
  147. var extName = s.match(/[^/]*$/)[0];
  148. comment("load ace " + extName + " extension");
  149. script("ext-" + extName);
  150. }
  151. });
  152. result.push("<script>");
  153. return result.join("\n");
  154. });
  155. if (removeRequireJS)
  156. source = source.replace(/\s*\}\);?\s*(<\/script>)/, "\n$1");
  157. source = source.replace(/"\.\.\/build\//g, function(e) {
  158. console.log(e); return '"../';
  159. });
  160. return source;
  161. }
  162. });
  163. }
  164. function jsFileList(path, filter) {
  165. path = ACE_HOME + "/" + path;
  166. if (!filter)
  167. filter = /_test/;
  168. return fs.readdirSync(path).map(function(x) {
  169. if (x.slice(-3) == ".js" && !filter.test(x) && !/\s|BASE|(\b|_)dummy(\b|_)/.test(x))
  170. return x.slice(0, -3);
  171. }).filter(Boolean);
  172. }
  173. function workers(path) {
  174. return jsFileList(path).map(function(x) {
  175. if (x.slice(-7) == "_worker")
  176. return x.slice(0, -7);
  177. }).filter(function(x) { return !!x; });
  178. }
  179. function modeList() {
  180. return jsFileList("lib/ace/mode", /_highlight_rules|_test|_worker|xml_util|_outdent|behaviour|completions/);
  181. }
  182. function buildAceModule(opts, callback) {
  183. // calling buildAceModuleInternal many times in parallel is slow, so we use queue
  184. if (!buildAceModule.queue) {
  185. buildAceModule.queue = [];
  186. buildAceModule.dequeue = function() {
  187. if (buildAceModule.running) return;
  188. var call = buildAceModule.queue.shift();
  189. buildAceModule.running = call;
  190. if (call)
  191. buildAceModuleInternal.apply(null, call);
  192. };
  193. }
  194. buildAceModule.queue.push([opts, function(err, result) {
  195. callback && callback(err, result);
  196. buildAceModule.running = null;
  197. buildAceModule.dequeue();
  198. }]);
  199. if (!buildAceModule.running) {
  200. buildAceModule.dequeue();
  201. } else {
  202. process.nextTick(buildAceModule.dequeue);
  203. }
  204. }
  205. function buildAceModuleInternal(opts, callback) {
  206. var cache = opts.cache == undefined ? CACHE : opts.cache;
  207. var key = opts.require + "|" + opts.projectType;
  208. if (cache && cache.configs && cache.configs[key])
  209. return write(null, cache.configs[key]);
  210. var pathConfig = {
  211. paths: {
  212. ace: ACE_HOME + "/lib/ace",
  213. "kitchen-sink": ACE_HOME + "/demo/kitchen-sink",
  214. build_support: ACE_HOME + "/build_support",
  215. },
  216. root: ACE_HOME
  217. };
  218. function write(err, result) {
  219. if (cache && key && !(cache.configs && cache.configs[key])) {
  220. cache.configs = cache.configs || Object.create(null);
  221. cache.configs[key] = result;
  222. result.sources = result.sources.map(function(pkg) {
  223. return {deps: pkg.deps};
  224. });
  225. }
  226. if (!opts.outputFile)
  227. return callback(err, result);
  228. var code = result.code;
  229. if (opts.compress) {
  230. if (!result.codeMin)
  231. result.codeMin = compress(result.code);
  232. code = result.codeMin;
  233. }
  234. var targetDir = getTargetDir(opts);
  235. var to = /^([\\/]|\w:)/.test(opts.outputFile)
  236. ? opts.outputFile
  237. : path.join(opts.outputFolder || targetDir, opts.outputFile);
  238. var filters = [];
  239. var ns = opts.ns || "ace";
  240. if (opts.filters)
  241. filters = filters.concat(opts.filters);
  242. if (opts.noconflict)
  243. filters.push(namespace(ns));
  244. var projectType = opts.projectType;
  245. if (projectType == "main" || projectType == "ext") {
  246. filters.push(exportAce(ns, opts.require[0],
  247. opts.noconflict ? ns : "", projectType == "ext"));
  248. }
  249. filters.push(normalizeLineEndings);
  250. filters.forEach(function(f) { code = f(code); });
  251. build.writeToFile({code: code}, {
  252. outputFolder: path.dirname(to),
  253. outputFile: path.basename(to),
  254. }, function() {});
  255. callback && callback(err, result);
  256. }
  257. build(opts.require, {
  258. cache: cache,
  259. quiet: opts.quiet,
  260. pathConfig: pathConfig,
  261. additional: opts.additional,
  262. enableBrowser: true,
  263. keepDepArrays: "all",
  264. noArchitect: true,
  265. compress: false,
  266. ignore: opts.ignore || [],
  267. withRequire: false,
  268. basepath: ACE_HOME,
  269. transforms: [normalizeLineEndings],
  270. afterRead: [optimizeTextModules]
  271. }, write);
  272. }
  273. function buildCore(options, extra, callback) {
  274. options = extend(extra, options);
  275. options.additional = [{
  276. id: "build_support/mini_require",
  277. order: -1000,
  278. literal: true
  279. }];
  280. options.require =["ace/ace"];
  281. options.projectType = "main";
  282. options.ns = "ace";
  283. buildAceModule(options, callback);
  284. }
  285. function buildSubmodule(options, extra, file, callback) {
  286. options = extend(extra, options);
  287. getLoadedFileList(options, function(coreFiles) {
  288. options.outputFile = file + ".js";
  289. options.ignore = options.ignore || coreFiles;
  290. options.quiet = true;
  291. buildAceModule(options, callback);
  292. });
  293. }
  294. function buildAce(options) {
  295. var snippetFiles = jsFileList("lib/ace/snippets");
  296. var modeNames = modeList();
  297. buildCore(options, {outputFile: "ace.js"}, addCb()),
  298. // modes
  299. modeNames.forEach(function(name) {
  300. buildSubmodule(options, {
  301. projectType: "mode",
  302. require: ["ace/mode/" + name]
  303. }, "mode-" + name, addCb());
  304. });
  305. // snippets
  306. modeNames.forEach(function(name) {
  307. if (snippetFiles.indexOf(name + ".js") == -1)
  308. addSnippetFile(name);
  309. buildSubmodule(options, {
  310. require: ["ace/snippets/" + name],
  311. }, "snippets/" + name, addCb());
  312. });
  313. // themes
  314. jsFileList("lib/ace/theme").forEach(function(name) {
  315. buildSubmodule(options, {
  316. projectType: "theme",
  317. require: ["ace/theme/" + name]
  318. }, "theme-" + name, addCb());
  319. });
  320. // keybindings
  321. ["vim", "emacs"].forEach(function(name) {
  322. buildSubmodule(options, {
  323. projectType: "keybinding",
  324. require: ["ace/keyboard/" + name ]
  325. }, "keybinding-" + name, addCb());
  326. });
  327. // extensions
  328. jsFileList("lib/ace/ext").forEach(function(name) {
  329. buildSubmodule(options, {
  330. projectType: "ext",
  331. require: ["ace/ext/" + name]
  332. }, "ext-" + name, addCb());
  333. });
  334. // workers
  335. workers("lib/ace/mode").forEach(function(name) {
  336. buildSubmodule(options, {
  337. projectType: "worker",
  338. require: ["ace/mode/" + name + "_worker"],
  339. ignore: [],
  340. additional: [{
  341. id: "ace/worker/worker",
  342. transforms: [],
  343. order: -1000
  344. }],
  345. }, "worker-" + name, addCb());
  346. });
  347. //
  348. function addCb() {
  349. addCb.count = (addCb.count || 0) + 1;
  350. return done
  351. }
  352. function done() {
  353. if (--addCb.count > 0)
  354. return;
  355. if (options.check)
  356. sanityCheck(options)
  357. console.log("Finished building " + getTargetDir(options))
  358. }
  359. }
  360. function getLoadedFileList(options, callback, result) {
  361. if (!result) {
  362. return buildCore({}, {}, function(e, result) {
  363. getLoadedFileList(options, callback, result);
  364. });
  365. }
  366. var deps = Object.create(null);
  367. result.sources.forEach(function(pkg) {
  368. pkg.deps && pkg.deps.forEach(function(p) {
  369. if (!deps[p]) deps[p] = 1;
  370. });
  371. });
  372. delete deps["ace/theme/textmate"];
  373. deps["ace/ace"] = 1;
  374. callback(Object.keys(deps));
  375. }
  376. function normalizeLineEndings(module) {
  377. if (typeof module == "string")
  378. module = {source: module};
  379. return module.source = module.source.replace(/\r\n/g, "\n");
  380. }
  381. function optimizeTextModules(sources) {
  382. var textModules = {};
  383. return sources.filter(function(pkg) {
  384. if (!pkg.id) {
  385. return true;
  386. }
  387. else if (pkg.id.indexOf("text!") > -1) {
  388. detectTextModules(pkg);
  389. return false;
  390. }
  391. else {
  392. pkg.source = rewriteTextImports(pkg.source, pkg.deps);
  393. return true;
  394. }
  395. }).map(function(pkg) {
  396. if (pkg && pkg.deps) {
  397. pkg.deps = pkg.deps && pkg.deps.filter(function(p) {
  398. return p.indexOf("text!") == -1;
  399. });
  400. }
  401. return pkg;
  402. });
  403. function rewriteTextImports(text, deps) {
  404. return text.replace(/ require\(['"](?:ace|[.\/]+)\/requirejs\/text!(.*?)['"]\)/g, function(_, call) {
  405. if (call) {
  406. var dep;
  407. deps.some(function(d) {
  408. if (d.split("/").pop() == call.split("/").pop()) {
  409. dep = d;
  410. return true;
  411. }
  412. });
  413. call = textModules[dep];
  414. if (call)
  415. return " " + call;
  416. }
  417. });
  418. }
  419. function detectTextModules(pkg) {
  420. var input = pkg.source.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
  421. if (/\.css$/.test(pkg.id)) {
  422. // remove unnecessary whitespace from css
  423. input = input.replace(/\n\s+/g, "\n");
  424. input = '"' + input.replace(/\n/g, '\\\n') + '"';
  425. } else {
  426. // but don't break other files!
  427. input = '"' + input.replace(/\n/g, '\\n\\\n') + '"';
  428. }
  429. textModules[pkg.id] = input;
  430. }
  431. }
  432. function namespace(ns) {
  433. return function(text) {
  434. text = text
  435. .toString()
  436. .replace(/ACE_NAMESPACE\s*=\s*""/, 'ACE_NAMESPACE = "' + ns +'"')
  437. .replace(/\bdefine\(/g, function(def, index, source) {
  438. if (/(^|[;}),])\s*$/.test(source.slice(0, index)))
  439. return ns + "." + def;
  440. return def;
  441. });
  442. return text;
  443. };
  444. }
  445. function exportAce(ns, modules, requireBase, extModules) {
  446. requireBase = requireBase || "window";
  447. return function(text) {
  448. /*globals REQUIRE_NS, MODULES, NS*/
  449. var template = function() {
  450. (function() {
  451. REQUIRE_NS.require(MODULES, function(a) {
  452. a && a.config.init(true);
  453. if (!window.NS)
  454. window.NS = a;
  455. for (var key in a) if (a.hasOwnProperty(key))
  456. window.NS[key] = a[key];
  457. });
  458. })();
  459. };
  460. if (extModules) {
  461. template = function() {
  462. (function() {
  463. REQUIRE_NS.require(MODULES, function() {});
  464. })();
  465. };
  466. }
  467. text = text.replace(/function init\(packaged\) {/, "init(true);$&\n");
  468. if (typeof modules == "string")
  469. modules = [modules];
  470. return (text.replace(/;\s*$/, "") + ";" + template
  471. .toString()
  472. .replace(/MODULES/g, JSON.stringify(modules))
  473. .replace(/REQUIRE_NS/g, requireBase)
  474. .replace(/NS/g, ns)
  475. .slice(13, -1)
  476. );
  477. };
  478. }
  479. function updateModes() {
  480. modeList().forEach(function(m) {
  481. var filepath = __dirname + "/lib/ace/mode/" + m + ".js";
  482. var source = fs.readFileSync(filepath, "utf8");
  483. if (!/this.\$id\s*=\s*"/.test(source))
  484. source = source.replace(/\n([ \t]*)(\}\).call\(\w*Mode.prototype\))/, '\n$1 this.$id = "";\n$1$2');
  485. source = source.replace(/(this.\$id\s*=\s*)"[^"]*"/, '$1"ace/mode/' + m + '"');
  486. fs.writeFileSync(filepath, source, "utf8");
  487. });
  488. }
  489. function generateThemesModule(themes) {
  490. var themelist = [
  491. 'define(function(require, exports, module) {',
  492. '\n\nmodule.exports.themes = ' + JSON.stringify(themes, null, ' '),
  493. ';\n\n});'
  494. ].join('');
  495. fs.writeFileSync(__dirname + '/lib/ace/ext/themelist_utils/themes.js', themelist, 'utf8');
  496. }
  497. function addSnippetFile(modeName) {
  498. var snippetFilePath = ACE_HOME + "/lib/ace/snippets/" + modeName;
  499. if (!fs.existsSync(snippetFilePath + ".js")) {
  500. copy.file(ACE_HOME + "/tool/templates/snippets.js", snippetFilePath + ".js", function(t) {
  501. return t.replace(/%modeName%/g, modeName);
  502. });
  503. }
  504. if (!fs.existsSync(snippetFilePath + ".snippets")) {
  505. fs.writeFileSync(snippetFilePath + ".snippets", "");
  506. }
  507. }
  508. function compress(text) {
  509. var ujs = require("dryice").copy.filter.uglifyjs;
  510. ujs.options.mangle_toplevel = {except: ["ACE_NAMESPACE", "requirejs"]};
  511. ujs.options.beautify = {ascii_only: true, inline_script: true}
  512. return ujs(text);
  513. }
  514. function extend(base, extra) {
  515. Object.keys(extra).forEach(function(k) {
  516. base[k] = extra[k];
  517. });
  518. return base;
  519. }
  520. function getTargetDir(opts) {
  521. var targetDir = BUILD_DIR + "/src";
  522. if (opts.compress)
  523. targetDir += "-min";
  524. if (opts.noconflict)
  525. targetDir += "-noconflict";
  526. return targetDir;
  527. }
  528. function sanityCheck(opts) {
  529. var targetDir = getTargetDir(opts);
  530. require("child_process").execFile(process.execPath, ["-e", "(" + function() {
  531. window = global;
  532. require("./ace");
  533. if (typeof ace.edit != "function")
  534. process.exit(1);
  535. require("fs").readdirSync(".").forEach(function(p) {
  536. if (!/ace\.js$/.test(p) && /\.js$/.test(p))
  537. require("./" + p);
  538. });
  539. process.exit(0);
  540. } + ")()"], {
  541. cwd: targetDir
  542. }, function(err, stdout) {
  543. if (err)
  544. throw err;
  545. });
  546. }
  547. if (!module.parent)
  548. main(process.argv);
  549. else
  550. exports.buildAce = buildAce;