Skip to content

Commit

Permalink
shell: add 'exit' builtin command (#9705)
Browse files Browse the repository at this point in the history
* shell: add 'exit' builtin command

* remove loop here

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <[email protected]>
  • Loading branch information
3 people authored Mar 30, 2024
1 parent 7172013 commit b8389f3
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 52 deletions.
146 changes: 94 additions & 52 deletions src/shell/interpreter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! use undefined memory.
const std = @import("std");
const builtin = @import("builtin");
const string = []const u8;
const bun = @import("root").bun;
const os = std.os;
const Arena = std.heap.ArenaAllocator;
Expand Down Expand Up @@ -504,6 +505,7 @@ pub const CowEnvMap = Cow(EnvMap, struct {

pub const EnvMap = struct {
map: MapType,

pub const Iterator = MapType.Iterator;

const MapType = std.ArrayHashMap(EnvStr, EnvStr, struct {
Expand Down Expand Up @@ -3909,7 +3911,9 @@ pub const Interpreter = struct {
args_slice: ?[]const [:0]const u8 = null,
cwd: bun.FileDescriptor,

impl: union(Kind) {
impl: RealImpl,

const RealImpl = union(Kind) {
cat: Cat,
touch: Touch,
mkdir: Mkdir,
Expand All @@ -3921,7 +3925,8 @@ pub const Interpreter = struct {
rm: Rm,
mv: Mv,
ls: Ls,
},
exit: Exit,
};

const Result = @import("../result.zig").Result;

Expand All @@ -3937,6 +3942,7 @@ pub const Interpreter = struct {
rm,
mv,
ls,
exit,

pub fn parentType(this: Kind) type {
_ = this;
Expand All @@ -3955,6 +3961,7 @@ pub const Interpreter = struct {
.rm => "usage: rm [-f | -i] [-dIPRrvWx] file ...\n unlink [--] file\n",
.mv => "usage: mv [-f | -i | -n] [-hv] source target\n mv [-f | -i | -n] [-v] source ... directory\n",
.ls => "usage: ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]\n",
.exit => "usage: exit [n]\n",
};
}

Expand All @@ -3971,6 +3978,7 @@ pub const Interpreter = struct {
.rm => "rm",
.mv => "mv",
.ls => "ls",
.exit => "exit",
};
}

Expand Down Expand Up @@ -4132,6 +4140,7 @@ pub const Interpreter = struct {
.pwd => this.callImplWithType(Pwd, Ret, "pwd", field, args_),
.mv => this.callImplWithType(Mv, Ret, "mv", field, args_),
.ls => this.callImplWithType(Ls, Ret, "ls", field, args_),
.exit => this.callImplWithType(Exit, Ret, "exit", field, args_),
};
}

Expand Down Expand Up @@ -4201,26 +4210,6 @@ pub const Interpreter = struct {
};

switch (kind) {
.cat => {
cmd.exec.bltn.impl = .{
.cat = Cat{ .bltn = &cmd.exec.bltn },
};
},
.touch => {
cmd.exec.bltn.impl = .{
.touch = Touch{ .bltn = &cmd.exec.bltn },
};
},
.mkdir => {
cmd.exec.bltn.impl = .{
.mkdir = Mkdir{ .bltn = &cmd.exec.bltn },
};
},
.@"export" => {
cmd.exec.bltn.impl = .{
.@"export" = Export{ .bltn = &cmd.exec.bltn },
};
},
.rm => {
cmd.exec.bltn.impl = .{
.rm = Rm{
Expand All @@ -4237,36 +4226,10 @@ pub const Interpreter = struct {
},
};
},
.cd => {
cmd.exec.bltn.impl = .{
.cd = Cd{
.bltn = &cmd.exec.bltn,
},
};
},
.which => {
cmd.exec.bltn.impl = .{
.which = Which{
.bltn = &cmd.exec.bltn,
},
};
},
.pwd => {
cmd.exec.bltn.impl = .{
.pwd = Pwd{ .bltn = &cmd.exec.bltn },
};
},
.mv => {
cmd.exec.bltn.impl = .{
.mv = Mv{ .bltn = &cmd.exec.bltn },
};
},
.ls => {
cmd.exec.bltn.impl = .{
.ls = Ls{
.bltn = &cmd.exec.bltn,
},
};
inline else => |tag| {
cmd.exec.bltn.impl = @unionInit(RealImpl, @tagName(tag), .{
.bltn = &cmd.exec.bltn,
});
},
}

Expand Down Expand Up @@ -8669,6 +8632,84 @@ pub const Interpreter = struct {
}
};
};

pub const Exit = struct {
bltn: *Builtin,
state: enum {
idle,
waiting_io,
err,
done,
} = .idle,

pub fn start(this: *Exit) Maybe(void) {
const args = this.bltn.argsSlice();
switch (args.len) {
0 => {
this.bltn.done(0);
return Maybe(void).success;
},
1 => {
const first_arg = args[0][0..std.mem.len(args[0]) :0];
const exit_code: ExitCode = std.fmt.parseInt(u8, first_arg, 10) catch |err| switch (err) {
error.Overflow => @intCast((std.fmt.parseInt(usize, first_arg, 10) catch return this.fail("exit: numeric argument required")) % 256),
error.InvalidCharacter => return this.fail("exit: numeric argument required"),
};
this.bltn.done(exit_code);
return Maybe(void).success;
},
else => {
return this.fail("exit: too many arguments");
},
}
}

fn fail(this: *Exit, msg: string) Maybe(void) {
if (this.bltn.stderr.needsIO()) {
this.state = .waiting_io;
this.bltn.stderr.enqueue(this, msg);
return Maybe(void).success;
}
_ = this.bltn.writeNoIO(.stderr, msg);
this.bltn.done(1);
return Maybe(void).success;
}

pub fn next(this: *Exit) void {
switch (this.state) {
.idle => unreachable,
.waiting_io => {
return;
},
.err => {
this.bltn.done(1);
return;
},
.done => {
this.bltn.done(1);
return;
},
}
}

pub fn onIOWriterChunk(this: *Exit, _: usize, maybe_e: ?JSC.SystemError) void {
if (comptime bun.Environment.allow_assert) {
std.debug.assert(this.state == .waiting_io);
}
if (maybe_e) |e| {
defer e.deref();
this.state = .err;
this.next();
return;
}
this.state = .done;
this.next();
}

pub fn deinit(this: *Exit) void {
_ = this;
}
};
};

/// This type is reference counted, but deinitialization is queued onto the event loop
Expand Down Expand Up @@ -9677,6 +9718,7 @@ pub const IOWriterChildPtr = struct {
Interpreter.Builtin.Touch,
Interpreter.Builtin.Touch.ShellTouchOutputTask,
Interpreter.Builtin.Cat,
Interpreter.Builtin.Exit,
shell.subproc.PipeReader.CapturedWriter,
});

Expand Down
22 changes: 22 additions & 0 deletions test/js/bun/shell/commands/exit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { $ } from "bun";
import { describe, test, expect } from "bun:test";
import { TestBuilder } from "../test_builder";
import { sortedShellOutput } from "../util";
import { join } from "path";

describe("exit", async () => {
TestBuilder.command`exit`.exitCode(0).runAsTest("works");

describe("argument sets exit code", async () => {
for (const arg of [0, 2, 11]) {
TestBuilder.command`exit ${arg}`.exitCode(arg).runAsTest(`${arg}`);
}
});

TestBuilder.command`exit 3 5`.exitCode(1).stderr("exit: too many arguments").runAsTest("too many arguments");

TestBuilder.command`exit 62757836`.exitCode(204).runAsTest("exit code wraps u8");

// prettier-ignore
TestBuilder.command`exit abc`.exitCode(1).stderr("exit: numeric argument required").runAsTest("numeric argument required");
});

0 comments on commit b8389f3

Please sign in to comment.