Makefile.dryice.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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});
  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 = BUILD_DIR + "/src";
  235. if (opts.compress)
  236. targetDir += "-min";
  237. if (opts.noconflict)
  238. targetDir += "-noconflict";
  239. var to = /^([\\/]|\w:)/.test(opts.outputFile)
  240. ? opts.outputFile
  241. : path.join(opts.outputFolder || targetDir, opts.outputFile);
  242. var filters = [];
  243. var ns = opts.ns || "ace";
  244. if (opts.filters)
  245. filters = filters.concat(opts.filters);
  246. if (opts.noconflict)
  247. filters.push(namespace(ns));
  248. var projectType = opts.projectType;
  249. if (projectType == "main" || projectType == "ext") {
  250. filters.push(exportAce(ns, opts.require[0],
  251. opts.noconflict ? ns : "", projectType == "ext"));
  252. }
  253. filters.forEach(function(f) { code = f(code); });
  254. build.writeToFile({code: code}, {
  255. outputFolder: path.dirname(to),
  256. outputFile: path.basename(to),
  257. }, function() {});
  258. callback && callback(err, result);
  259. }
  260. build(opts.require, {
  261. cache: cache,
  262. quiet: opts.quiet,
  263. pathConfig: pathConfig,
  264. additional: opts.additional,
  265. enableBrowser: true,
  266. keepDepArrays: "all",
  267. noArchitect: true,
  268. compress: false,
  269. ignore: opts.ignore || [],
  270. withRequire: false,
  271. basepath: ACE_HOME,
  272. transforms: [normalizeLineEndings],
  273. afterRead: [optimizeTextModules]
  274. }, write);
  275. }
  276. function buildCore(options, extra, callback) {
  277. options = extend(extra, options);
  278. options.additional = [{
  279. id: "build_support/mini_require",
  280. order: -1000,
  281. literal: true
  282. }];
  283. options.require =["ace/ace"];
  284. options.projectType = "main";
  285. options.ns = "ace";
  286. buildAceModule(options, callback);
  287. }
  288. function buildSubmodule(options, extra, file, callback) {
  289. options = extend(extra, options);
  290. getLoadedFileList(options, function(coreFiles) {
  291. options.outputFile = file + ".js";
  292. options.ignore = options.ignore || coreFiles;
  293. options.quiet = true;
  294. buildAceModule(options, callback);
  295. });
  296. }
  297. function buildAce(options) {
  298. var snippetFiles = jsFileList("lib/ace/snippets");
  299. var modeNames = modeList();
  300. buildCore(options, {outputFile: "ace.js"}),
  301. // modes
  302. modeNames.forEach(function(name) {
  303. buildSubmodule(options, {
  304. projectType: "mode",
  305. require: ["ace/mode/" + name]
  306. }, "mode-" + name);
  307. });
  308. // snippets
  309. modeNames.forEach(function(name) {
  310. if (snippetFiles.indexOf(name + ".js") == -1)
  311. addSnippetFile(name);
  312. buildSubmodule(options, {
  313. require: ["ace/snippets/" + name],
  314. }, "snippets/" + name);
  315. });
  316. // themes
  317. jsFileList("lib/ace/theme").forEach(function(name) {
  318. buildSubmodule(options, {
  319. projectType: "theme",
  320. require: ["ace/theme/" + name]
  321. }, "theme-" + name);
  322. });
  323. // keybindings
  324. ["vim", "emacs"].forEach(function(name) {
  325. buildSubmodule(options, {
  326. projectType: "keybinding",
  327. require: ["ace/keyboard/" + name ]
  328. }, "keybinding-" + name);
  329. });
  330. // extensions
  331. jsFileList("lib/ace/ext").forEach(function(name) {
  332. buildSubmodule(options, {
  333. projectType: "ext",
  334. require: ["ace/ext/" + name]
  335. }, "ext-" + name);
  336. });
  337. // workers
  338. workers("lib/ace/mode").forEach(function(name) {
  339. buildSubmodule(options, {
  340. projectType: "worker",
  341. require: ["ace/mode/" + name + "_worker"],
  342. ignore: [],
  343. additional: [{
  344. id: "ace/worker/worker",
  345. transforms: [],
  346. order: -1000
  347. }],
  348. }, "worker-" + name);
  349. });
  350. }
  351. function getLoadedFileList(options, callback, result) {
  352. if (!result) {
  353. return buildCore({}, {}, function(e, result) {
  354. getLoadedFileList(options, callback, result);
  355. });
  356. }
  357. var deps = Object.create(null);
  358. result.sources.forEach(function(pkg) {
  359. pkg.deps && pkg.deps.forEach(function(p) {
  360. if (!deps[p]) deps[p] = 1;
  361. });
  362. });
  363. delete deps["ace/theme/textmate"];
  364. callback(Object.keys(deps));
  365. }
  366. function normalizeLineEndings(module) {
  367. module.source = module.source.replace(/\r\n/g, "\n");
  368. }
  369. function optimizeTextModules(sources) {
  370. var textModules = {};
  371. return sources.filter(function(pkg) {
  372. if (!pkg.id) {
  373. return true;
  374. }
  375. else if (pkg.id.indexOf("text!") > -1) {
  376. detectTextModules(pkg);
  377. return false;
  378. }
  379. else {
  380. pkg.source = rewriteTextImports(pkg.source, pkg.deps);
  381. return true;
  382. }
  383. }).map(function(pkg) {
  384. if (pkg && pkg.deps) {
  385. pkg.deps = pkg.deps && pkg.deps.filter(function(p) {
  386. return p.indexOf("text!") == -1;
  387. });
  388. }
  389. return pkg;
  390. });
  391. function rewriteTextImports(text, deps) {
  392. return text.replace(/ require\(['"](?:ace|[.\/]+)\/requirejs\/text!(.*?)['"]\)/g, function(_, call) {
  393. if (call) {
  394. var dep;
  395. deps.some(function(d) {
  396. if (d.split("/").pop() == call.split("/").pop()) {
  397. dep = d;
  398. return true;
  399. }
  400. });
  401. call = textModules[dep];
  402. if (call)
  403. return " " + call;
  404. }
  405. });
  406. }
  407. function detectTextModules(pkg) {
  408. var input = pkg.source.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
  409. if (/\.css$/.test(pkg.id)) {
  410. // remove unnecessary whitespace from css
  411. input = input.replace(/\n\s+/g, "\n");
  412. input = '"' + input.replace(/\n/g, '\\\n') + '"';
  413. } else {
  414. // but don't break other files!
  415. input = '"' + input.replace(/\n/g, '\\n\\\n') + '"';
  416. }
  417. textModules[pkg.id] = input;
  418. }
  419. }
  420. function namespace(ns) {
  421. return function(text) {
  422. text = text
  423. .toString()
  424. .replace(/ACE_NAMESPACE\s*=\s*""/, 'ACE_NAMESPACE = "' + ns +'"')
  425. .replace(/(\.define)|\bdefine\(/g, function(_, a) {
  426. return a || ns + ".define(";
  427. });
  428. return text;
  429. };
  430. }
  431. function exportAce(ns, modules, requireBase, extModules) {
  432. requireBase = requireBase || "window";
  433. return function(text) {
  434. /*globals REQUIRE_NS, MODULES, NS*/
  435. var template = function() {
  436. (function() {
  437. REQUIRE_NS.require(MODULES, function(a) {
  438. a && a.config.init(true);
  439. if (!window.NS)
  440. window.NS = a;
  441. for (var key in a) if (a.hasOwnProperty(key))
  442. window.NS[key] = a[key];
  443. });
  444. })();
  445. };
  446. if (extModules) {
  447. template = function() {
  448. (function() {
  449. REQUIRE_NS.require(MODULES, function() {});
  450. })();
  451. };
  452. }
  453. text = text.replace(/function init\(packaged\) {/, "init(true);$&\n");
  454. if (typeof modules == "string")
  455. modules = [modules];
  456. return (text.replace(/;\s*$/, "") + ";" + template
  457. .toString()
  458. .replace(/MODULES/g, JSON.stringify(modules))
  459. .replace(/REQUIRE_NS/g, requireBase)
  460. .replace(/NS/g, ns)
  461. .slice(13, -1)
  462. );
  463. };
  464. }
  465. function updateModes() {
  466. modeList().forEach(function(m) {
  467. var filepath = __dirname + "/lib/ace/mode/" + m + ".js";
  468. var source = fs.readFileSync(filepath, "utf8");
  469. if (!/this.\$id\s*=\s*"/.test(source))
  470. source = source.replace(/\n([ \t]*)(\}\).call\(\w*Mode.prototype\))/, '\n$1 this.$id = "";\n$1$2');
  471. source = source.replace(/(this.\$id\s*=\s*)"[^"]*"/, '$1"ace/mode/' + m + '"');
  472. fs.writeFileSync(filepath, source, "utf8");
  473. });
  474. }
  475. function generateThemesModule(themes) {
  476. var themelist = [
  477. 'define(function(require, exports, module) {',
  478. '\n\nmodule.exports.themes = ' + JSON.stringify(themes, null, ' '),
  479. ';\n\n});'
  480. ].join('');
  481. fs.writeFileSync(__dirname + '/lib/ace/ext/themelist_utils/themes.js', themelist, 'utf8');
  482. }
  483. function addSnippetFile(modeName) {
  484. var snippetFilePath = ACE_HOME + "/lib/ace/snippets/" + modeName;
  485. if (!fs.existsSync(snippetFilePath + ".js")) {
  486. copy.file(ACE_HOME + "/tool/templates/snippets.js", snippetFilePath + ".js", function(t) {
  487. return t.replace(/%modeName%/g, modeName);
  488. });
  489. }
  490. if (!fs.existsSync(snippetFilePath + ".snippets")) {
  491. fs.writeFileSync(snippetFilePath + ".snippets", "");
  492. }
  493. }
  494. function compress(text) {
  495. var ujs = require("dryice").copy.filter.uglifyjs;
  496. ujs.options.mangle_toplevel = {except: ["ACE_NAMESPACE", "requirejs"]};
  497. ujs.options.beautify = {ascii_only: true, inline_script: true}
  498. return ujs(text);
  499. }
  500. function extend(base, extra) {
  501. Object.keys(extra).forEach(function(k) {
  502. base[k] = extra[k];
  503. });
  504. return base;
  505. }
  506. if (!module.parent)
  507. main(process.argv);
  508. else
  509. exports.buildAce = buildAce;