rule.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. let path = require('path');
  2. let fs = require('fs');
  3. let Task = require('./task/task').Task;
  4. // Split a task to two parts, name space and task name.
  5. // For example, given 'foo:bin/a%.c', return an object with
  6. // - 'ns' : foo
  7. // - 'name' : bin/a%.c
  8. function splitNs(task) {
  9. let parts = task.split(':');
  10. let name = parts.pop();
  11. let ns = resolveNs(parts);
  12. return {
  13. 'name' : name,
  14. 'ns' : ns
  15. };
  16. }
  17. // Return the namespace based on an array of names.
  18. // For example, given ['foo', 'baz' ], return the namespace
  19. //
  20. // default -> foo -> baz
  21. //
  22. // where default is the global root namespace
  23. // and -> means child namespace.
  24. function resolveNs(parts) {
  25. let ns = jake.defaultNamespace;
  26. for(let i = 0, l = parts.length; ns && i < l; i++) {
  27. ns = ns.childNamespaces[parts[i]];
  28. }
  29. return ns;
  30. }
  31. // Given a pattern p, say 'foo:bin/a%.c'
  32. // Return an object with
  33. // - 'ns' : foo
  34. // - 'dir' : bin
  35. // - 'prefix' : a
  36. // - 'suffix' : .c
  37. function resolve(p) {
  38. let task = splitNs(p);
  39. let name = task.name;
  40. let ns = task.ns;
  41. let split = path.basename(name).split('%');
  42. return {
  43. ns: ns,
  44. dir: path.dirname(name),
  45. prefix: split[0],
  46. suffix: split[1]
  47. };
  48. }
  49. // Test whether string a is a suffix of string b
  50. function stringEndWith(a, b) {
  51. let l;
  52. return (l = b.lastIndexOf(a)) == -1 ? false : l + a.length == b.length;
  53. }
  54. // Replace the suffix a of the string s with b.
  55. // Note that, it is assumed a is a suffix of s.
  56. function stringReplaceSuffix(s, a, b) {
  57. return s.slice(0, s.lastIndexOf(a)) + b;
  58. }
  59. class Rule {
  60. constructor(opts) {
  61. this.pattern = opts.pattern;
  62. this.source = opts.source;
  63. this.prereqs = opts.prereqs;
  64. this.action = opts.action;
  65. this.opts = opts.opts;
  66. this.desc = opts.desc;
  67. this.ns = opts.ns;
  68. }
  69. // Create a file task based on this rule for the specified
  70. // task-name
  71. // ======
  72. // FIXME: Right now this just throws away any passed-in args
  73. // for the synthsized task (taskArgs param)
  74. // ======
  75. createTask(fullName, level) {
  76. let self = this;
  77. let pattern;
  78. let source;
  79. let action;
  80. let opts;
  81. let prereqs;
  82. let valid;
  83. let src;
  84. let tNs;
  85. let createdTask;
  86. let name = Task.getBaseTaskName(fullName);
  87. let nsPath = Task.getBaseNamespacePath(fullName);
  88. let ns = this.ns.resolveNamespace(nsPath);
  89. pattern = this.pattern;
  90. source = this.source;
  91. if (typeof source == 'string') {
  92. src = Rule.getSource(name, pattern, source);
  93. }
  94. else {
  95. src = source(name);
  96. }
  97. // TODO: Write a utility function that appends a
  98. // taskname to a namespace path
  99. src = nsPath.split(':').filter(function (item) {
  100. return !!item;
  101. }).concat(src).join(':');
  102. // Generate the prerequisite for the matching task.
  103. // It is the original prerequisites plus the prerequisite
  104. // representing source file, i.e.,
  105. //
  106. // rule( '%.o', '%.c', ['some.h'] ...
  107. //
  108. // If the objective is main.o, then new task should be
  109. //
  110. // file( 'main.o', ['main.c', 'some.h' ] ...
  111. prereqs = this.prereqs.slice(); // Get a copy to work with
  112. prereqs.unshift(src);
  113. // Prereq should be:
  114. // 1. an existing task
  115. // 2. an existing file on disk
  116. // 3. a valid rule (i.e., not at too deep a level)
  117. valid = prereqs.some(function (p) {
  118. let ns = self.ns;
  119. return ns.resolveTask(p) ||
  120. fs.existsSync(Task.getBaseTaskName(p)) ||
  121. jake.attemptRule(p, ns, level + 1);
  122. });
  123. // If any of the prereqs aren't valid, the rule isn't valid
  124. if (!valid) {
  125. return null;
  126. }
  127. // Otherwise, hunky-dory, finish creating the task for the rule
  128. else {
  129. // Create the action for the task
  130. action = function () {
  131. let task = this;
  132. self.action.apply(task);
  133. };
  134. opts = this.opts;
  135. // Insert the file task into Jake
  136. //
  137. // Since createTask function stores the task as a child task
  138. // of currentNamespace. Here we temporariliy switch the namespace.
  139. // FIXME: Should allow optional ns passed in instead of this hack
  140. tNs = jake.currentNamespace;
  141. jake.currentNamespace = ns;
  142. createdTask = jake.createTask('file', name, prereqs, action, opts);
  143. createdTask.source = src.split(':').pop();
  144. jake.currentNamespace = tNs;
  145. return createdTask;
  146. }
  147. }
  148. match(name) {
  149. return Rule.match(this.pattern, name);
  150. }
  151. // Test wether the a prerequisite matchs the pattern.
  152. // The arg 'pattern' does not have namespace as prefix.
  153. // For example, the following tests are true
  154. //
  155. // pattern | name
  156. // bin/%.o | bin/main.o
  157. // bin/%.o | foo:bin/main.o
  158. //
  159. // The following tests are false (trivally)
  160. //
  161. // pattern | name
  162. // bin/%.o | foobin/main.o
  163. // bin/%.o | bin/main.oo
  164. static match(pattern, name) {
  165. let p;
  166. let task;
  167. let obj;
  168. let filename;
  169. if (pattern instanceof RegExp) {
  170. return pattern.test(name);
  171. }
  172. else if (pattern.indexOf('%') == -1) {
  173. // No Pattern. No Folder. No Namespace.
  174. // A Simple Suffix Rule. Just test suffix
  175. return stringEndWith(pattern, name);
  176. }
  177. else {
  178. // Resolve the dir, prefix and suffix of pattern
  179. p = resolve(pattern);
  180. // Resolve the namespace and task-name
  181. task = splitNs(name);
  182. name = task.name;
  183. // Set the objective as the task-name
  184. obj = name;
  185. // Namespace is already matched.
  186. // Check dir
  187. if (path.dirname(obj) != p.dir) {
  188. return false;
  189. }
  190. filename = path.basename(obj);
  191. // Check file name length
  192. if ((p.prefix.length + p.suffix.length + 1) > filename.length) {
  193. // Length does not match.
  194. return false;
  195. }
  196. // Check prefix
  197. if (filename.indexOf(p.prefix) !== 0) {
  198. return false;
  199. }
  200. // Check suffix
  201. if (!stringEndWith(p.suffix, filename)) {
  202. return false;
  203. }
  204. // OK. Find a match.
  205. return true;
  206. }
  207. }
  208. // Generate the source based on
  209. // - name name for the synthesized task
  210. // - pattern pattern for the objective
  211. // - source pattern for the source
  212. //
  213. // Return the source with properties
  214. // - dep the prerequisite of source
  215. // (with the namespace)
  216. //
  217. // - file the file name of source
  218. // (without the namespace)
  219. //
  220. // For example, given
  221. //
  222. // - name foo:bin/main.o
  223. // - pattern bin/%.o
  224. // - source src/%.c
  225. //
  226. // return 'foo:src/main.c',
  227. //
  228. static getSource(name, pattern, source) {
  229. let dep;
  230. let pat;
  231. let match;
  232. let file;
  233. let src;
  234. // Regex pattern -- use to look up the extension
  235. if (pattern instanceof RegExp) {
  236. match = pattern.exec(name);
  237. if (match) {
  238. if (typeof source == 'function') {
  239. src = source(name);
  240. }
  241. else {
  242. src = stringReplaceSuffix(name, match[0], source);
  243. }
  244. }
  245. }
  246. // Assume string
  247. else {
  248. // Simple string suffix replacement
  249. if (pattern.indexOf('%') == -1) {
  250. if (typeof source == 'function') {
  251. src = source(name);
  252. }
  253. else {
  254. src = stringReplaceSuffix(name, pattern, source);
  255. }
  256. }
  257. // Percent-based substitution
  258. else {
  259. pat = pattern.replace('%', '(.*?)');
  260. pat = new RegExp(pat);
  261. match = pat.exec(name);
  262. if (match) {
  263. if (typeof source == 'function') {
  264. src = source(name);
  265. }
  266. else {
  267. file = match[1];
  268. file = source.replace('%', file);
  269. dep = match[0];
  270. src = name.replace(dep, file);
  271. }
  272. }
  273. }
  274. }
  275. return src;
  276. }
  277. }
  278. exports.Rule = Rule;