import { bold, green, yellow } from "https://deno.land/std@v0.34.0/fmt/colors.ts";import { existsSync } from "https://deno.land/std@v0.34.0/fs/mod.ts";import { Env } from "./cli.ts";import { abort, isFileTask, normalizePrereqs, normalizeTaskName } from "./utils.ts";
export type Action = (this: Task) => any;
export class Task { name: string; desc: string; prereqs: string[]; action?: Action;
constructor(name: string, desc: string, prereqs: string[], action?: Action) { name = normalizeTaskName(name); this.name = name; this.desc = desc; this.prereqs = normalizePrereqs(prereqs); if (action) { this.action = action.bind(this); } }
isUpToDate(): boolean { if (!isFileTask(this.name)) { return false; } for (const name of this.prereqs) { if (!isFileTask(name)) { continue; } if (!existsSync(name)) { abort( `task: ${this.name}: missing prerequisite path: ${name}` ); } } if (!existsSync(this.name)) { return false; } const targetStat = Deno.statSync(this.name); for (const prereq of this.prereqs) { if (!isFileTask(prereq)) { continue; } const prereqStat = Deno.statSync(prereq); if (!targetStat.modified || !prereqStat.modified) { continue; } if (targetStat.modified < prereqStat.modified) { return false; } } return true; }}
export class TaskRegistry extends Map<string, Task> { env: Env; lastDesc: string;
constructor(env: Env) { super(); this.env = env; this.lastDesc = ""; }
get(name: string): Task { name = normalizeTaskName(name); if (!this.has(name)) { abort(`missing task: ${name}`); } return super.get(name) as Task; }
set(name: string, task: Task) { name = normalizeTaskName(name); if (this.has(name)) { abort(`task already exists: ${name}`); } return super.set(name, task); }
desc(description: string): void { this.lastDesc = description; }
register(name: string, prereqs: string[], action?: Action): void { this.set(name, new Task(name, this.lastDesc, prereqs, action)); this.lastDesc = ""; }
log(message: string): void { if (!this.env["--quiet"]) { console.log(message); } }
list(): void { const keys = Array.from(this.keys()); const maxLen = keys.reduce(function(a, b) { return a.length > b.length ? a : b; }).length; for (const k of keys.sort()) { const task = this.get(k); console.log( `${green(bold(task.name.padEnd(maxLen)))} ${task.desc} ${yellow( `[${task.prereqs}]` )}` ); } }
private expand(names: string[]): Task[] { let result: Task[] = []; names = names.slice(); names.reverse(); for (const name of names) { if (isFileTask(name) && !this.has(name)) { continue; } const task = this.get(name); result.unshift(task); result = this.resolveDependencies(task.prereqs).concat(result); } return result; }
resolveDependencies(names: string[]): Task[] { names = names.map(name => normalizeTaskName(name)); const result: Task[] = []; for (const task of this.expand(names)) { if (result.find(t => t.name === task.name)) { continue; } result.push(task); } return result; }
async run(names: string[]) { const tasks = this.resolveDependencies(names); this.log(`${green(bold("task queue"))}: ${tasks.map(t => t.name)}`); for (const task of tasks) { if (!task.action) { continue; } if (!this.env["--always-make"] && task.isUpToDate()) { this.log(yellow(`${task.name} skipped`) + " (up to date)"); continue; } await this.execute(task.name); } }
async execute(name: string) { const task = this.get(name); if (!task.action) { return; } const startTime = new Date().getTime(); if (!this.env["--dry-run"]) { this.log(green(bold(`${task.name} started`))); if (task.action.constructor.name === "AsyncFunction") { await task.action(); } else { task.action(); } const endTime = new Date().getTime(); this.log( green(bold(`${task.name} finished`)) + ` in ${((endTime - startTime) / 1000).toFixed(2)} seconds` ); } else { this.log(yellow(`${task.name} skipped`) + " (dry run)"); } }}