Constnium/node_modules/jake/lib/rule.js
2022-06-22 19:41:32 +02:00

311 lines
7.7 KiB
JavaScript

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;