Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] task as a process #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ install:
script:
- deno -v
- mkdir tmp
- deno --allow-run --allow-env test/test-task.ts --cwd=test | tee tmp/result
- deno --allow-run --allow-env test/test-task.ts --cwd=test all | tee tmp/result
- deno --allow-run --allow-env test/test.ts
- deno --allow-run --allow-env example/example.ts --cwd=example all
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ install:
test_script:
- deno -v
- mkdir tmp
- deno --allow-run --allow-env test/test-task.ts --cwd=test | tee tmp/result
- deno --allow-run --allow-env test/test-task.ts --cwd=test all | tee tmp/result
- deno --allow-run --allow-env test/test.ts
- deno --allow-run --allow-env example/example.ts --cwd=example all

Expand Down
3 changes: 2 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ new Promise(resolve => setTimeout(resolve, 0))
.then(async () => {
const parsedArgs = flags.parse(args);
const cwd = parsedArgs.cwd || ".";
const taskFile = parsedArgs._[0];
const taskName = parsedArgs._[1];
const taskArgs = parsedArgs._.splice(2);
if (!taskName) {
throw new Error("Usage: task_file.ts task_name [--cwd]");
}
await globalRunner.run(taskName, taskArgs, { cwd });
await globalRunner.run(taskName, taskFile, taskArgs, { cwd });
})
.catch(e => {
console.error(e);
Expand Down
115 changes: 44 additions & 71 deletions runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import * as path from "https://deno.land/x/fs/path.ts"; // should fix later

type Tasks = { [name: string]: Command };
interface ResolveContext {
interface ValidateContext {
checked: Set<string>;
hasWatcher: boolean;
}
Expand All @@ -22,17 +22,26 @@ class ProcessError extends Error {
}
}
interface Command {
resolveRef(tasks: Tasks, context: ResolveContext): Command;
validate(tasks: Tasks, context: ValidateContext): void;
run(args: string[], context: RunContext): Promise<void>;
}
class Single implements Command {
constructor(public script: string) {}
resolveRef(tasks: Tasks, _: ResolveContext) {
return this;
}
async run(args: string[], { cwd, shell, resources }: RunContext) {
validate(tasks: Tasks, _: ValidateContext) {}
async run(
args: string[],
{ taskFile, cwd, tasks, shell, resources }: RunContext
) {
let script = this.script;
for (let taskName of Object.keys(tasks)) {
const regex = new RegExp(`\\$${taskName}`, "g");
script = script.replace(
regex,
`deno -A ${path.relative(cwd, taskFile)} ${taskName}`
);
}
const allArgs = shell
? [...getShellCommand(), [this.script, ...args].join(" ")]
? [...getShellCommand(), [script, ...args].join(" ")]
: [...this.script.split(/\s/), ...args];
const p = deno.run({
args: allArgs,
Expand Down Expand Up @@ -65,6 +74,7 @@ function getShellCommand(): string[] {
}

async function kill(p: Process) {
console.log("kill", p.pid);
const k = deno.run({
args: ["kill", `${p.pid}`],
stdout: "inherit",
Expand All @@ -74,46 +84,13 @@ async function kill(p: Process) {
k.close();
}

class Ref implements Command {
constructor(public script: string) {}
resolveRef(tasks: Tasks, context: ResolveContext) {
const splitted = this.script.split(/\s/);
const name = splitted[0].slice(1);
const args = splitted.slice(1);
if (!name.length) {
throw new Error("Task name should not be empty.");
}

let command = tasks[name];
if (!command) {
throw new Error(`Task "${name}" is not defined.`);
}
if (context.checked.has(name)) {
throw new Error(`Task "${name}" is in a reference loop.`);
}
if (command instanceof Single) {
command = new Single([command.script, ...args].join(" "));
}
return command.resolveRef(tasks, {
...context,
checked: new Set(context.checked).add(name)
});
}
async run(args: string[], context: RunContext) {
throw new Error("Ref should be resolved before running.");
}
}
class Sequence implements Command {
commands: Command[];
constructor(commands: Command[]) {
this.commands = commands;
}
resolveRef(tasks: Tasks, context: ResolveContext) {
return new Sequence(
this.commands.map(c => {
return c.resolveRef(tasks, context);
})
);
validate(tasks: Tasks, context: ValidateContext) {
this.commands.forEach(c => c.validate(tasks, context));
}
async run(args: string[], context: RunContext) {
if (args.length) {
Expand All @@ -129,12 +106,8 @@ class Parallel implements Command {
constructor(commands: Command[]) {
this.commands = commands;
}
resolveRef(tasks: Tasks, context: ResolveContext) {
return new Parallel(
this.commands.map(c => {
return c.resolveRef(tasks, context);
})
);
validate(tasks: Tasks, context: ValidateContext) {
this.commands.forEach(c => c.validate(tasks, context));
}
async run(args: string[], context: RunContext) {
if (args.length) {
Expand All @@ -149,15 +122,11 @@ class SyncWatcher implements Command {
public watchOptions: WatchOptions,
public command: Command
) {}
resolveRef(tasks: Tasks, context: ResolveContext) {
validate(tasks: Tasks, context: ValidateContext) {
if (context.hasWatcher) {
throw new Error("Nested watchers not supported.");
}
return new SyncWatcher(
this.dirs,
this.watchOptions,
this.command.resolveRef(tasks, { ...context, hasWatcher: true })
);
this.command.validate(tasks, { ...context, hasWatcher: true });
}
async run(args: string[], context: RunContext) {
const dirs_ = this.dirs.map(d => {
Expand All @@ -181,15 +150,11 @@ class AsyncWatcher implements Command {
public watchOptions: WatchOptions,
public command: Command
) {}
resolveRef(tasks: Tasks, context: ResolveContext) {
validate(tasks: Tasks, context: ValidateContext) {
if (context.hasWatcher) {
throw new Error("Nested watchers not supported.");
}
return new AsyncWatcher(
this.dirs,
this.watchOptions,
this.command.resolveRef(tasks, { ...context, hasWatcher: true })
);
this.command.validate(tasks, { ...context, hasWatcher: true });
}
async run(args: string[], context: RunContext) {
const dirs_ = this.dirs.map(d => {
Expand Down Expand Up @@ -250,9 +215,11 @@ interface RunOptions {
shell?: boolean;
}
interface RunContext {
taskFile: string;
cwd: string;
shell: boolean;
resources: Set<Closer>;
tasks: Tasks;
}
export class TaskRunner {
tasks: Tasks = {};
Expand All @@ -266,20 +233,29 @@ export class TaskRunner {
this.tasks[name] = makeCommand(rawCommands);
return new TaskDecorator(this.tasks, name);
}
async run(taskName: string, args: string[] = [], options: RunOptions = {}) {
options = { cwd: ".", shell: true, ...options };
async validate(taskName: string) {
let command = this.tasks[taskName];
if (!command) {
throw new Error(`Task "${taskName}" not found.`);
}
const resolveContext = { checked: new Set(), hasWatcher: false };
const context = {
command.validate(this.tasks, { checked: new Set(), hasWatcher: false });
}
async run(
taskName: string,
taskFile: string,
args: string[] = [],
options: RunOptions = {}
) {
options = { cwd: ".", shell: true, ...options };
this.validate(taskName);
let command = this.tasks[taskName];
await command.run(args, {
taskFile,
cwd: options.cwd,
shell: options.shell,
resources: new Set()
};
const resolvedCommand = command.resolveRef(this.tasks, resolveContext);
await resolvedCommand.run(args, context);
resources: new Set(),
tasks: this.tasks
});
}
}

Expand All @@ -303,8 +279,5 @@ function makeSingleCommand(script: string) {
if (!script.trim()) {
throw new Error("Command should not be empty.");
}
if (script.charAt(0) === "$") {
return new Ref(script);
}
return new Single(script);
}
46 changes: 22 additions & 24 deletions test/test-task.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import { TaskRunner } from "../runner.ts";
import { task } from "../mod.ts";

const runner = new TaskRunner();
runner.task("hello", "echo hello");
runner.task("hello2", "$hello alice", "$hello bob");
runner.task("c", "deno count.ts");
runner.task("count", "$c start", ["$c foo 1 3 5", "$c bar 2 4"], "$c end");
runner.task("hello-watch", "echo hello").watch(".");
runner.task("touch", "touch test.ts");
runner.task(
task("hello", "echo hello");
task("hello2", "$hello alice", "$hello bob");
task("123456", "echo 123 && echo 456");
task("c", "deno count.ts");
task("count", "$c start", ["$c foo 1 3 5", "$c bar 2 4"], "$c end");
task("hello-watch", "echo hello").watch(".");
task("touch", "touch test.ts");
task(
"shell",
`echo hello > ../tmp/result-from-shell`,
`echo world >> ../tmp/result-from-shell`
`echo world >> ../tmp/result-from-shell`,
`$123456 >> ../tmp/result-from-shell`
);
task(
"all",
"$hello world",
"echo ====",
"$hello2",
"echo ====",
"$count",
"echo ====",
"$shell",
"echo ===="
);

(async () => {
await runner.run("hello", ["world"], { cwd: "test" });
console.log("====");
await runner.run("hello2", [], { cwd: "test" });
console.log("====");
await runner.run("count", [], { cwd: "test" });
console.log("====");
await runner.run("shell", [], { cwd: "test" });
// console.log("====");
// await runner.run("hello-watch", [],{ cwd: "test" });
// await new Promise(resolve => setTimeout(resolve, 1000));
// await runner.run("touch", [],{ cwd: "test" });
})();
77 changes: 40 additions & 37 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ bar
foo
end
====
====
`
.replace(/\r\n/g, "\n")
.trim();
Expand All @@ -39,6 +40,8 @@ test(async function shell() {
const expectation = `
hello
world
123
456
`
.replace(/\r\n/g, "\n")
.trim();
Expand All @@ -62,44 +65,44 @@ test(async function errors() {
await throws(async () => {
const runner = new TaskRunner();
runner.task("hello", "echo hello");
await runner.run("hell");
});
await throws(async () => {
const runner = new TaskRunner();
runner.task("hello", "$echo hello");
await runner.run("hello");
});
await throws(async () => {
const runner = new TaskRunner();
runner.task("hello", "$hello");
await runner.run("hello");
});
await throws(async () => {
const runner = new TaskRunner();
runner.task("greeting", "echo hello", "echo bye");
await runner.run("greeting", ["x"]);
});
await throws(async () => {
const runner = new TaskRunner();
runner.task("greeting", ["echo hello", "echo bye"]);
await runner.run("greeting", ["x"]);
});
await throws(async () => {
const runner = new TaskRunner();
runner.task("child", ["echo hello"]).watch(".");
runner.task("parent", ["$child"]).watch(".");
await runner.run("parent", []);
});
await throws(async () => {
const runner = new TaskRunner();
runner.task(
"failure",
"echo start",
["deno test/failure.ts", "echo another"],
"echo end"
);
await runner.run("failure");
await runner.validate("hell");
});
// await throws(async () => {
// const runner = new TaskRunner();
// runner.task("hello", "$echo hello");
// await runner.validate("hello");
// });
// await throws(async () => {
// const runner = new TaskRunner();
// runner.task("hello", "$hello");
// await runner.validate("hello");
// });
// await throws(async () => {
// const runner = new TaskRunner();
// runner.task("greeting", "echo hello", "echo bye");
// await runner.run("greeting", ["x"]);
// });
// await throws(async () => {
// const runner = new TaskRunner();
// runner.task("greeting", ["echo hello", "echo bye"]);
// await runner.run("greeting", ["x"]);
// });
// await throws(async () => {
// const runner = new TaskRunner();
// runner.task("child", ["echo hello"]).watch(".");
// runner.task("parent", ["$child"]).watch(".");
// await runner.run("parent", []);
// });
// await throws(async () => {
// const runner = new TaskRunner();
// runner.task(
// "failure",
// "echo start",
// ["deno test/failure.ts", "echo another"],
// "echo end"
// );
// await runner.run("failure");
// });
});

export async function throws(
Expand Down