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.
312 lines
7.7 KiB
312 lines
7.7 KiB
5 years ago
|
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;
|