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

feat: add unset command and fix VAR= behaviour #134

Merged
merged 3 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
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
35 changes: 35 additions & 0 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,41 @@ Deno.test("exporting env should modify real environment when something changed v
}
});

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

Deno.test("unsetting env var should work", 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 should work", 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("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;
}
}
dsherret marked this conversation as resolved.
Show resolved Hide resolved
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
5 changes: 4 additions & 1 deletion 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,9 @@ export class Context {
case "shellvar":
this.setShellVar(change.name, change.value);
break;
case "unsetvar":
this.setShellVar(change.name, undefined);
break;
default: {
const _assertNever: never = change;
throw new Error(`Not implemented env change: ${change}`);
Expand Down