You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
7.7 KiB
311 lines
7.7 KiB
let path = require('path'); |
|
let fs = require('fs'); |
|
let Task = require('./task/task').Task; |
|
|
|
// Split a task to two parts, name space and task name. |
|
// For example, given 'foo:bin/a%.c', return an object with |
|
// - 'ns' : foo |
|
// - 'name' : bin/a%.c |
|
function splitNs(task) { |
|
let parts = task.split(':'); |
|
let name = parts.pop(); |
|
let ns = resolveNs(parts); |
|
return { |
|
'name' : name, |
|
'ns' : ns |
|
}; |
|
} |
|
|
|
// Return the namespace based on an array of names. |
|
// For example, given ['foo', 'baz' ], return the namespace |
|
// |
|
// default -> foo -> baz |
|
// |
|
// where default is the global root namespace |
|
// and -> means child namespace. |
|
function resolveNs(parts) { |
|
let ns = jake.defaultNamespace; |
|
for(let i = 0, l = parts.length; ns && i < l; i++) { |
|
ns = ns.childNamespaces[parts[i]]; |
|
} |
|
return ns; |
|
} |
|
|
|
// Given a pattern p, say 'foo:bin/a%.c' |
|
// Return an object with |
|
// - 'ns' : foo |
|
// - 'dir' : bin |
|
// - 'prefix' : a |
|
// - 'suffix' : .c |
|
function resolve(p) { |
|
let task = splitNs(p); |
|
let name = task.name; |
|
let ns = task.ns; |
|
let split = path.basename(name).split('%'); |
|
return { |
|
ns: ns, |
|
dir: path.dirname(name), |
|
prefix: split[0], |
|
suffix: split[1] |
|
}; |
|
} |
|
|
|
// Test whether string a is a suffix of string b |
|
function stringEndWith(a, b) { |
|
let l; |
|
return (l = b.lastIndexOf(a)) == -1 ? false : l + a.length == b.length; |
|
} |
|
|
|
// Replace the suffix a of the string s with b. |
|
// Note that, it is assumed a is a suffix of s. |
|
function stringReplaceSuffix(s, a, b) { |
|
return s.slice(0, s.lastIndexOf(a)) + b; |
|
} |
|
|
|
class Rule { |
|
constructor(opts) { |
|
this.pattern = opts.pattern; |
|
this.source = opts.source; |
|
this.prereqs = opts.prereqs; |
|
this.action = opts.action; |
|
this.opts = opts.opts; |
|
this.desc = opts.desc; |
|
this.ns = opts.ns; |
|
} |
|
|
|
// Create a file task based on this rule for the specified |
|
// task-name |
|
// ====== |
|
// FIXME: Right now this just throws away any passed-in args |
|
// for the synthsized task (taskArgs param) |
|
// ====== |
|
createTask(fullName, level) { |
|
let self = this; |
|
let pattern; |
|
let source; |
|
let action; |
|
let opts; |
|
let prereqs; |
|
let valid; |
|
let src; |
|
let tNs; |
|
let createdTask; |
|
let name = Task.getBaseTaskName(fullName); |
|
let nsPath = Task.getBaseNamespacePath(fullName); |
|
let ns = this.ns.resolveNamespace(nsPath); |
|
|
|
pattern = this.pattern; |
|
source = this.source; |
|
|
|
if (typeof source == 'string') { |
|
src = Rule.getSource(name, pattern, source); |
|
} |
|
else { |
|
src = source(name); |
|
} |
|
|
|
// TODO: Write a utility function that appends a |
|
// taskname to a namespace path |
|
src = nsPath.split(':').filter(function (item) { |
|
return !!item; |
|
}).concat(src).join(':'); |
|
|
|
// Generate the prerequisite for the matching task. |
|
// It is the original prerequisites plus the prerequisite |
|
// representing source file, i.e., |
|
// |
|
// rule( '%.o', '%.c', ['some.h'] ... |
|
// |
|
// If the objective is main.o, then new task should be |
|
// |
|
// file( 'main.o', ['main.c', 'some.h' ] ... |
|
prereqs = this.prereqs.slice(); // Get a copy to work with |
|
prereqs.unshift(src); |
|
|
|
// Prereq should be: |
|
// 1. an existing task |
|
// 2. an existing file on disk |
|
// 3. a valid rule (i.e., not at too deep a level) |
|
valid = prereqs.some(function (p) { |
|
let ns = self.ns; |
|
return ns.resolveTask(p) || |
|
fs.existsSync(Task.getBaseTaskName(p)) || |
|
jake.attemptRule(p, ns, level + 1); |
|
}); |
|
|
|
// If any of the prereqs aren't valid, the rule isn't valid |
|
if (!valid) { |
|
return null; |
|
} |
|
// Otherwise, hunky-dory, finish creating the task for the rule |
|
else { |
|
// Create the action for the task |
|
action = function () { |
|
let task = this; |
|
self.action.apply(task); |
|
}; |
|
|
|
opts = this.opts; |
|
|
|
// Insert the file task into Jake |
|
// |
|
// Since createTask function stores the task as a child task |
|
// of currentNamespace. Here we temporariliy switch the namespace. |
|
// FIXME: Should allow optional ns passed in instead of this hack |
|
tNs = jake.currentNamespace; |
|
jake.currentNamespace = ns; |
|
createdTask = jake.createTask('file', name, prereqs, action, opts); |
|
createdTask.source = src.split(':').pop(); |
|
jake.currentNamespace = tNs; |
|
|
|
return createdTask; |
|
} |
|
} |
|
|
|
match(name) { |
|
return Rule.match(this.pattern, name); |
|
} |
|
|
|
// Test wether the a prerequisite matchs the pattern. |
|
// The arg 'pattern' does not have namespace as prefix. |
|
// For example, the following tests are true |
|
// |
|
// pattern | name |
|
// bin/%.o | bin/main.o |
|
// bin/%.o | foo:bin/main.o |
|
// |
|
// The following tests are false (trivally) |
|
// |
|
// pattern | name |
|
// bin/%.o | foobin/main.o |
|
// bin/%.o | bin/main.oo |
|
static match(pattern, name) { |
|
let p; |
|
let task; |
|
let obj; |
|
let filename; |
|
|
|
if (pattern instanceof RegExp) { |
|
return pattern.test(name); |
|
} |
|
else if (pattern.indexOf('%') == -1) { |
|
// No Pattern. No Folder. No Namespace. |
|
// A Simple Suffix Rule. Just test suffix |
|
return stringEndWith(pattern, name); |
|
} |
|
else { |
|
// Resolve the dir, prefix and suffix of pattern |
|
p = resolve(pattern); |
|
|
|
// Resolve the namespace and task-name |
|
task = splitNs(name); |
|
name = task.name; |
|
|
|
// Set the objective as the task-name |
|
obj = name; |
|
|
|
// Namespace is already matched. |
|
|
|
// Check dir |
|
if (path.dirname(obj) != p.dir) { |
|
return false; |
|
} |
|
|
|
filename = path.basename(obj); |
|
|
|
// Check file name length |
|
if ((p.prefix.length + p.suffix.length + 1) > filename.length) { |
|
// Length does not match. |
|
return false; |
|
} |
|
|
|
// Check prefix |
|
if (filename.indexOf(p.prefix) !== 0) { |
|
return false; |
|
} |
|
|
|
// Check suffix |
|
if (!stringEndWith(p.suffix, filename)) { |
|
return false; |
|
} |
|
|
|
// OK. Find a match. |
|
return true; |
|
} |
|
} |
|
|
|
// Generate the source based on |
|
// - name name for the synthesized task |
|
// - pattern pattern for the objective |
|
// - source pattern for the source |
|
// |
|
// Return the source with properties |
|
// - dep the prerequisite of source |
|
// (with the namespace) |
|
// |
|
// - file the file name of source |
|
// (without the namespace) |
|
// |
|
// For example, given |
|
// |
|
// - name foo:bin/main.o |
|
// - pattern bin/%.o |
|
// - source src/%.c |
|
// |
|
// return 'foo:src/main.c', |
|
// |
|
static getSource(name, pattern, source) { |
|
let dep; |
|
let pat; |
|
let match; |
|
let file; |
|
let src; |
|
|
|
// Regex pattern -- use to look up the extension |
|
if (pattern instanceof RegExp) { |
|
match = pattern.exec(name); |
|
if (match) { |
|
if (typeof source == 'function') { |
|
src = source(name); |
|
} |
|
else { |
|
src = stringReplaceSuffix(name, match[0], source); |
|
} |
|
} |
|
} |
|
// Assume string |
|
else { |
|
// Simple string suffix replacement |
|
if (pattern.indexOf('%') == -1) { |
|
if (typeof source == 'function') { |
|
src = source(name); |
|
} |
|
else { |
|
src = stringReplaceSuffix(name, pattern, source); |
|
} |
|
} |
|
// Percent-based substitution |
|
else { |
|
pat = pattern.replace('%', '(.*?)'); |
|
pat = new RegExp(pat); |
|
match = pat.exec(name); |
|
if (match) { |
|
if (typeof source == 'function') { |
|
src = source(name); |
|
} |
|
else { |
|
file = match[1]; |
|
file = source.replace('%', file); |
|
dep = match[0]; |
|
src = name.replace(dep, file); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return src; |
|
} |
|
} |
|
|
|
|
|
exports.Rule = Rule;
|
|
|