Skip to content

Commit

Permalink
feat: add unset command and fix VAR= behaviour (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Apr 5, 2023
1 parent 9374eab commit 8af3199
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ Currently implemented (though not every option is supported):
- [`sleep`](https://man7.org/linux/man-pages/man1/sleep.1.html) - Sleep command.
- [`test`](https://man7.org/linux/man-pages/man1/test.1.html) - Test command.
- [`touch`](https://man7.org/linux/man-pages/man1/touch.1.html) - Creates a file (note: flags have not been implemented yet).
- [`unset`](https://man7.org/linux/man-pages/man1/unset.1p.html) - Unsets an environment variable.
- More to come. Will try to get a similar list as https://deno.land/manual/tools/task_runner#built-in-commands

You can also register your own commands with the shell parser (see below).
Expand Down
53 changes: 53 additions & 0 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,59 @@ Deno.test("exporting env should modify real environment when something changed v
}
});

Deno.test("setting an empty env var", async () => {
const text = await $`VAR= deno eval 'console.log("VAR: " + Deno.env.get("VAR"))'`.text();
assertEquals(text, "VAR: ");
});

Deno.test("unsetting env var", async () => {
const text = await $`unset VAR && deno eval 'console.log("VAR: " + Deno.env.get("VAR"))'`
.env("VAR", "1")
.text();
assertEquals(text, "VAR: undefined");
});

Deno.test("unsetting multiple env vars", async () => {
const text =
await $`unset VAR1 VAR2 && deno eval 'console.log("VAR: " + Deno.env.get("VAR1") + Deno.env.get("VAR2") + Deno.env.get("VAR3"))'`
.env({
"VAR1": "test",
"VAR2": "test",
"VAR3": "test",
})
.text();
assertEquals(text, "VAR: undefinedundefinedtest");
});

Deno.test("unsetting multiple shell vars", async () => {
const text = await $`VAR1=1 && VAR2=2 && VAR3=3 && VAR4=4 && unset VAR1 VAR4 && echo $VAR1 $VAR2 $VAR3 $VAR4`
.text();
assertEquals(text, "2 3");
});

Deno.test("unsetting shell var with -v", async () => {
const text = await $`VAR1=1 && unset -v VAR1 && echo $VAR1 test`
.text();
assertEquals(text, "test");
});

Deno.test("unsetting with no args", async () => {
const text = await $`unset && echo test`
.text();
assertEquals(text, "test");
});

Deno.test("unset with -f should error", async () => {
const result = await $`unset -f VAR1 && echo $VAR1`
.env({ "VAR1": "test" })
.stdout("piped")
.stderr("piped")
.noThrow();
assertEquals(result.code, 1);
assertEquals(result.stderr, "unset: unsupported flag: -f\n");
assertEquals(result.stdout, "");
});

Deno.test("cwd should be resolved based on cwd at time of method call and not execution", async () => {
const previousCwd = Deno.cwd();
try {
Expand Down
6 changes: 4 additions & 2 deletions src/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CommandHandler } from "./command_handler.ts";
import { cdCommand } from "./commands/cd.ts";
import { cpCommand, mvCommand } from "./commands/cp_mv.ts";
import { echoCommand } from "./commands/echo.ts";
import { exitCommand } from "./commands/exit.ts";
import { exportCommand } from "./commands/export.ts";
Expand All @@ -8,6 +9,8 @@ import { rmCommand } from "./commands/rm.ts";
import { pwdCommand } from "./commands/pwd.ts";
import { sleepCommand } from "./commands/sleep.ts";
import { testCommand } from "./commands/test.ts";
import { touchCommand } from "./commands/touch.ts";
import { unsetCommand } from "./commands/unset.ts";
import { Box, delayToMs, LoggerTreeBox } from "./common.ts";
import { Delay } from "./common.ts";
import { Buffer, colors, path, readerFromStreamReader } from "./deps.ts";
Expand All @@ -21,9 +24,7 @@ import {
ShellPipeWriterKind,
} from "./pipes.ts";
import { parseCommand, spawn } from "./shell.ts";
import { cpCommand, mvCommand } from "./commands/cp_mv.ts";
import { isShowingProgressBars } from "./console/progress/interval.ts";
import { touchCommand } from "./commands/touch.ts";
import { PathRef } from "./path.ts";

type BufferStdio = "inherit" | "null" | "streamed" | Buffer;
Expand Down Expand Up @@ -59,6 +60,7 @@ const builtInCommands = {
mv: mvCommand,
pwd: pwdCommand,
touch: touchCommand,
unset: unsetCommand,
};

/** @internal */
Expand Down
2 changes: 1 addition & 1 deletion src/commands/cd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function cdCommand(context: CommandContext): Promise<ExecuteResult>
}],
};
} catch (err) {
await context.stderr.writeLine(`cd: ${err?.message ?? err}`);
context.stderr.writeLine(`cd: ${err?.message ?? err}`);
return resultFromCode(1);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/echo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CommandContext } from "../command_handler.ts";
import { ExecuteResult, resultFromCode } from "../result.ts";

export async function echoCommand(context: CommandContext): Promise<ExecuteResult> {
await context.stdout.writeLine(context.args.join(" "));
export function echoCommand(context: CommandContext): ExecuteResult {
context.stdout.writeLine(context.args.join(" "));
return resultFromCode(0);
}
4 changes: 2 additions & 2 deletions src/commands/exit.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { CommandContext } from "../command_handler.ts";
import { ExitExecuteResult } from "../result.ts";

export async function exitCommand(context: CommandContext): Promise<ExitExecuteResult> {
export function exitCommand(context: CommandContext): ExitExecuteResult {
try {
const code = parseArgs(context.args);
return {
kind: "exit",
code,
};
} catch (err) {
await context.stderr.writeLine(`exit: ${err?.message ?? err}`);
context.stderr.writeLine(`exit: ${err?.message ?? err}`);
// impl. note: bash returns 2 on exit parse failure, deno_task_shell returns 1
return {
kind: "exit",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/sleep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function sleepCommand(context: CommandContext): Promise<ExecuteResu
}
return resultFromCode(0);
} catch (err) {
await context.stderr.writeLine(`sleep: ${err?.message ?? err}`);
context.stderr.writeLine(`sleep: ${err?.message ?? err}`);
return resultFromCode(1);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function testCommand(context: CommandContext): Promise<ExecuteResul
}
return resultFromCode(result ? 0 : 1);
} catch (err) {
await context.stderr.writeLine(`test: ${err?.message ?? err}`);
context.stderr.writeLine(`test: ${err?.message ?? err}`);
// bash test returns 2 on error, e.g. -bash: test: -8: unary operator expected
return resultFromCode(2);
}
Expand Down
27 changes: 27 additions & 0 deletions src/commands/unset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ExecuteResult } from "../../mod.ts";
import { CommandContext } from "../command_handler.ts";
import { resultFromCode } from "../result.ts";

export function unsetCommand(context: CommandContext): ExecuteResult {
try {
return {
kind: "continue",
code: 0,
changes: parseNames(context.args).map((name) => ({ kind: "unsetvar", name })),
};
} catch (err) {
context.stderr.writeLine(`unset: ${err?.message ?? err}`);
return resultFromCode(1);
}
}

function parseNames(args: string[]) {
if (args[0] === "-f") {
// we don't support the -f (function) flag
throw Error(`unsupported flag: -f`);
} else if (args[0] === "-v") {
return args.slice(1);
} else {
return args;
}
}
11 changes: 10 additions & 1 deletion src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface ContinueExecuteResult {
*
* Used for registering custom commands.
*/
export type EnvChange = SetEnvVarChange | SetShellVarChange | CdChange;
export type EnvChange = SetEnvVarChange | SetShellVarChange | UnsetVarChange | CdChange;

/** Change that sets an environment variable (ex. `export ENV_VAR=VALUE`)
*
Expand All @@ -54,6 +54,15 @@ export interface SetShellVarChange {
value: string;
}

/** Change that deletes the environment variable (ex. `unset ENV_VAR`).
*
* Used for registering custom commands.
*/
export interface UnsetVarChange {
kind: "unsetvar";
name: string;
}

/** Change that alters the current working directory.
*
* Used for registering custom commands.
Expand Down
14 changes: 8 additions & 6 deletions src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class ShellEnv implements Env {
if (Deno.build.os === "windows") {
key = key.toUpperCase();
}
if (value == null || value.length === 0) {
if (value == null) {
delete this.#envVars[key];
} else {
this.#envVars[key] = value;
Expand Down Expand Up @@ -244,6 +244,10 @@ export class Context {
case "shellvar":
this.setShellVar(change.name, change.value);
break;
case "unsetvar":
this.setShellVar(change.name, undefined);
this.setEnvVar(change.name, undefined);
break;
default: {
const _assertNever: never = change;
throw new Error(`Not implemented env change: ${change}`);
Expand Down Expand Up @@ -272,12 +276,10 @@ export class Context {
}
if (this.#env.getEnvVar(key) != null || key === "PWD") {
this.setEnvVar(key, value);
} else if (value == null) {
delete this.#shellVars[key];
} else {
if (value == null || value.length === 0) {
delete this.#shellVars[key];
} else {
this.#shellVars[key] = value;
}
this.#shellVars[key] = value;
}
}

Expand Down

0 comments on commit 8af3199

Please sign in to comment.