A virtual jailed shell environment for Go apps backed by an io/fs#FS.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(command): add clear command

Emit the standard ANSI screen-clear sequence (ESC[2J ESC[H) on
stdout. Useful inside the kefka shell where the host terminal's
own clear binary is not available, and matches the behavior users
expect from a coreutils-style clear.

Signed-off-by: Xe Iaso <me@xeiaso.net>
Assisted-by: Claude Opus 4.7 via Claude Code

Xe Iaso 860bc831 76550c55

+146
+56
command/internal/clear/clear.go
··· 1 + package clear 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "fmt" 7 + "io" 8 + 9 + "github.com/pborman/getopt/v2" 10 + "mvdan.cc/sh/v3/interp" 11 + "tangled.org/xeiaso.net/kefka/command" 12 + ) 13 + 14 + type Impl struct{} 15 + 16 + func (Impl) Exec(ctx context.Context, ec *command.ExecContext, args []string) error { 17 + if ec == nil { 18 + return errors.New("clear: nil ExecContext") 19 + } 20 + 21 + stdout := ec.Stdout 22 + if stdout == nil { 23 + stdout = io.Discard 24 + } 25 + stderr := ec.Stderr 26 + if stderr == nil { 27 + stderr = io.Discard 28 + } 29 + 30 + set := getopt.New() 31 + set.SetProgram("clear") 32 + set.SetParameters("") 33 + 34 + usage := func() { 35 + fmt.Fprint(stderr, "Usage: clear [OPTIONS]\n") 36 + fmt.Fprint(stderr, "Clear the terminal screen.\n\n") 37 + fmt.Fprint(stderr, " --help display this help and exit\n") 38 + } 39 + set.SetUsage(usage) 40 + 41 + help := set.BoolLong("help", 0, "display this help and exit") 42 + 43 + if err := set.Getopt(append([]string{"clear"}, args...), nil); err != nil { 44 + fmt.Fprintf(stderr, "clear: %s\n", err) 45 + usage() 46 + return interp.ExitStatus(1) 47 + } 48 + 49 + if *help { 50 + usage() 51 + return nil 52 + } 53 + 54 + io.WriteString(stdout, "\x1b[2J\x1b[H") 55 + return nil 56 + }
+88
command/internal/clear/clear_test.go
··· 1 + package clear 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "strings" 7 + "testing" 8 + 9 + "tangled.org/xeiaso.net/kefka/command" 10 + ) 11 + 12 + func run(t *testing.T, args []string) (string, string, error) { 13 + t.Helper() 14 + var stdout, stderr bytes.Buffer 15 + ec := &command.ExecContext{ 16 + Stdout: &stdout, 17 + Stderr: &stderr, 18 + Dir: ".", 19 + } 20 + err := Impl{}.Exec(context.Background(), ec, args) 21 + return stdout.String(), stderr.String(), err 22 + } 23 + 24 + func TestClear(t *testing.T) { 25 + tests := []struct { 26 + name string 27 + args []string 28 + wantStdout string 29 + wantErr bool 30 + }{ 31 + { 32 + name: "no args writes ANSI clear sequence", 33 + args: []string{}, 34 + wantStdout: "\x1b[2J\x1b[H", 35 + }, 36 + { 37 + name: "extra positional args are ignored", 38 + args: []string{"foo", "bar"}, 39 + wantStdout: "\x1b[2J\x1b[H", 40 + }, 41 + { 42 + name: "unknown flag returns error", 43 + args: []string{"--no-such-flag"}, 44 + wantErr: true, 45 + }, 46 + } 47 + 48 + for _, tt := range tests { 49 + t.Run(tt.name, func(t *testing.T) { 50 + stdout, stderr, err := run(t, tt.args) 51 + if tt.wantErr { 52 + if err == nil { 53 + t.Fatalf("expected error, got nil; stdout=%q stderr=%q", stdout, stderr) 54 + } 55 + return 56 + } 57 + if err != nil { 58 + t.Fatalf("unexpected error: %v; stderr=%q", err, stderr) 59 + } 60 + if stdout != tt.wantStdout { 61 + t.Errorf("stdout = %q, want %q", stdout, tt.wantStdout) 62 + } 63 + }) 64 + } 65 + } 66 + 67 + func TestHelp(t *testing.T) { 68 + stdout, stderr, err := run(t, []string{"--help"}) 69 + if err != nil { 70 + t.Fatalf("unexpected error: %v", err) 71 + } 72 + if stdout != "" { 73 + t.Errorf("expected empty stdout, got %q", stdout) 74 + } 75 + if !strings.Contains(stderr, "Usage: clear [OPTIONS]") { 76 + t.Errorf("usage line missing from stderr: %q", stderr) 77 + } 78 + if !strings.Contains(stderr, "--help") { 79 + t.Errorf("help flag missing from help text: %q", stderr) 80 + } 81 + } 82 + 83 + func TestNilStdout(t *testing.T) { 84 + ec := &command.ExecContext{Dir: "."} 85 + if err := (Impl{}).Exec(context.Background(), ec, nil); err != nil { 86 + t.Fatalf("unexpected error with nil stdout/stderr: %v", err) 87 + } 88 + }
+2
command/registry/coreutils/coreutils.go
··· 3 3 import ( 4 4 "tangled.org/xeiaso.net/kefka/command/internal/base64" 5 5 "tangled.org/xeiaso.net/kefka/command/internal/basename" 6 + "tangled.org/xeiaso.net/kefka/command/internal/clear" 6 7 "tangled.org/xeiaso.net/kefka/command/internal/falsecmd" 7 8 "tangled.org/xeiaso.net/kefka/command/internal/hostname" 8 9 "tangled.org/xeiaso.net/kefka/command/internal/ls" ··· 13 14 func Register(reg *registry.Impl) { 14 15 reg.Register("base64", base64.Impl{}) 15 16 reg.Register("basename", basename.Impl{}) 17 + reg.Register("clear", clear.Impl{}) 16 18 reg.Register("false", falsecmd.Impl{}) 17 19 reg.Register("hostname", hostname.Impl{}) 18 20 reg.Register("ls", ls.Impl{})