From 6066f8aa557d8ed2ec993636658ad95b989339b0 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 19 Nov 2024 12:04:56 -0500 Subject: [PATCH] feat(example): implement a faketty example This implements a faketty executable that can be used to run a command in a fake terminal. This is useful to force escape sequences to be printed to the terminal while piping the output to another command like `sequin`. It also takes the `rows` and `cols` flags to set the terminal size and uses 80x24 as the default. --- examples/faketty/main.go | 87 ++++++++++++++++++++++++++++++++++++++++ examples/go.mod | 4 ++ examples/go.sum | 8 ++++ 3 files changed, 99 insertions(+) create mode 100644 examples/faketty/main.go diff --git a/examples/faketty/main.go b/examples/faketty/main.go new file mode 100644 index 00000000..268e9004 --- /dev/null +++ b/examples/faketty/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "flag" + "fmt" + "io" + "os" + "os/exec" + "syscall" + + "github.com/creack/pty" +) + +var ( + rows int + cols int +) + +func init() { + flag.IntVar(&rows, "rows", 24, "number of rows") + flag.IntVar(&cols, "cols", 80, "number of columns") + flag.Parse() +} + +func main() { + if len(os.Args) < 2 { + fmt.Fprintf(os.Stderr, "usage: %s [command] [args...]\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s -cols=80 -rows=24 [command] [args...]\n", os.Args[0]) + + os.Exit(1) + } + + newStdin := os.Stdin + newStderr := os.Stderr + + winsize := pty.Winsize{ + Rows: uint16(rows), //nolint:gosec + Cols: uint16(cols), //nolint:gosec + } + + ptm1, pts1, err := pty.Open() + if err != nil { + fmt.Fprintf(os.Stderr, "error creating pty: %v\n", err) + os.Exit(1) + } + if err := pty.Setsize(ptm1, &winsize); err != nil { + fmt.Fprintf(os.Stderr, "error setting pty size: %v\n", err) + os.Exit(1) + } + + go io.Copy(os.Stdout, ptm1) //nolint:errcheck + + newStdout := os.Stdout + + ptm2, pts2, err := pty.Open() + if err != nil { + fmt.Fprintf(os.Stderr, "error creating pty: %v\n", err) + os.Exit(1) + } + + if err := pty.Setsize(ptm2, &winsize); err != nil { + fmt.Fprintf(os.Stderr, "error setting pty size: %v\n", err) + os.Exit(1) + } + + go io.Copy(newStderr, ptm2) //nolint:errcheck + + if err := syscall.Dup2(int(newStdin.Fd()), int(os.Stdin.Fd())); err != nil { + fmt.Fprintf(os.Stderr, "error duplicating stdin file descriptor: %v\n", err) + os.Exit(1) + } + if err := syscall.Dup2(int(newStdout.Fd()), int(os.Stdout.Fd())); err != nil { + fmt.Fprintf(os.Stderr, "error duplicating stdout file descriptor: %v\n", err) + os.Exit(1) + } + + n := flag.NFlag() + c := exec.Command(os.Args[n+1], os.Args[n+2:]...) //nolint:gosec + c.Stdout = pts1 + c.Stderr = pts2 + c.Stdin = newStdin + + if err := c.Run(); err != nil { + fmt.Fprintf(os.Stderr, "error running command: %v\n", err) + os.Exit(1) + } +} diff --git a/examples/go.mod b/examples/go.mod index eac3a551..3cfbd224 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -8,12 +8,16 @@ require ( github.com/charmbracelet/x/input v0.2.0 github.com/charmbracelet/x/termios v0.1.0 github.com/charmbracelet/x/vt v0.0.0-00010101000000-000000000000 + github.com/charmbracelet/x/xpty v0.1.0 github.com/creack/pty v1.1.24 + github.com/spf13/pflag v1.0.5 golang.org/x/image v0.22.0 ) require ( github.com/charmbracelet/colorprofile v0.1.7 // indirect + github.com/charmbracelet/x/conpty v0.1.0 // indirect + github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 8ebcdf8f..a37ea5a0 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,9 +1,15 @@ github.com/charmbracelet/colorprofile v0.1.7 h1:q7PtMQrRBBnLNE2EbtbNUtouu979EivKcDGGaimhyO8= github.com/charmbracelet/colorprofile v0.1.7/go.mod h1:d3UYToTrNmsD2p9/lbiya16H1WahndM0miDlJWXWf4U= +github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= +github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k= github.com/charmbracelet/x/termios v0.1.0/go.mod h1:H/EVv/KRnrYjz+fCYa9bsKdqF3S8ouDK0AZEbG7r+/U= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 h1:D5OO0lVavz7A+Swdhp62F9gbkibxmz9B2hZ/jVdMPf0= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91/go.mod h1:Ey8PFmYwH+/td9bpiEx07Fdx9ZVkxfIjWXxBluxF4Nw= +github.com/charmbracelet/x/xpty v0.1.0 h1:762t0rQshWYRCzJe7MrUSM/8wBu6rg39HBc6PiMBFcs= +github.com/charmbracelet/x/xpty v0.1.0/go.mod h1:sDG9QjERjtelfbSf2iyZsa5d8Uvi4X9fK3iUqdb9XRw= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -14,6 +20,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=