mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
shell: implement exit builtin
This commit is contained in:
@@ -3788,7 +3788,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,
|
||||
@@ -3800,7 +3802,8 @@ pub const Interpreter = struct {
|
||||
rm: Rm,
|
||||
mv: Mv,
|
||||
ls: Ls,
|
||||
},
|
||||
exit: Exit,
|
||||
};
|
||||
|
||||
const Result = @import("../result.zig").Result;
|
||||
|
||||
@@ -3816,6 +3819,7 @@ pub const Interpreter = struct {
|
||||
rm,
|
||||
mv,
|
||||
ls,
|
||||
exit,
|
||||
|
||||
pub fn parentType(this: Kind) type {
|
||||
_ = this;
|
||||
@@ -3834,6 +3838,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",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3850,6 +3855,7 @@ pub const Interpreter = struct {
|
||||
.rm => "rm",
|
||||
.mv => "mv",
|
||||
.ls => "ls",
|
||||
.exit => "exit",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4011,6 +4017,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_),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4080,26 +4087,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{
|
||||
@@ -4116,36 +4103,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,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8525,6 +8486,80 @@ 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 = std.fmt.parseInt(ExitCode, first_arg, 10) catch {
|
||||
@panic("TODO");
|
||||
};
|
||||
this.bltn.done(exit_code);
|
||||
return Maybe(void).success;
|
||||
},
|
||||
else => {
|
||||
const msg = "exit: too many arguments";
|
||||
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 {
|
||||
while (!(this.state == .err or this.state == .done)) {
|
||||
switch (this.state) {
|
||||
.waiting_io => return,
|
||||
.idle, .done, .err => unreachable,
|
||||
}
|
||||
}
|
||||
if (this.state == .done) {
|
||||
this.bltn.done(1);
|
||||
return;
|
||||
}
|
||||
if (this.state == .err) {
|
||||
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
|
||||
@@ -9519,6 +9554,7 @@ pub const IOWriterChildPtr = struct {
|
||||
Interpreter.Builtin.Touch,
|
||||
Interpreter.Builtin.Touch.ShellTouchOutputTask,
|
||||
Interpreter.Builtin.Cat,
|
||||
Interpreter.Builtin.Exit,
|
||||
shell.subproc.PipeReader.CapturedWriter,
|
||||
});
|
||||
|
||||
|
||||
17
test/js/bun/shell/commands/exit.test.ts
Normal file
17
test/js/bun/shell/commands/exit.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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");
|
||||
});
|
||||
Reference in New Issue
Block a user