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