├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── watch.go ├── watch_unix.go └── watch_windows.go /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | archive: 5 | replacements: 6 | darwin: Darwin 7 | linux: Linux 8 | windows: Windows 9 | 386: i386 10 | amd64: x86_64 11 | checksum: 12 | name_template: 'checksums.txt' 13 | snapshot: 14 | name_template: "{{ .Tag }}-next" 15 | changelog: 16 | sort: asc 17 | filters: 18 | exclude: 19 | - '^docs:' 20 | - '^test:' 21 | - '^fix:' 22 | - 'readme' 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anton Medvedev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | watch 2 | 3 | # ⏰ watch 4 | 5 | _watch_ tool rewritten in go. 6 | 7 | ## Features 8 | 9 | * working aliases 10 | * configurable shell 11 | * windows support 12 | 13 | Webpod - deploy JavaScript apps 14 | 15 | ## Usage 16 | 17 | ```bash 18 | watch [command] 19 | ``` 20 | 21 | Specify command for _watch_ by setting `WATCH_COMMAND` (`bash -cli` by default). 22 | 23 | ```bash 24 | export WATCH_COMMAND=`fish -c` 25 | ``` 26 | 27 | ## Example 28 | 29 | ```bash 30 | watch git status 31 | ``` 32 | 33 | ```bash 34 | watch curl wttr.in 35 | ``` 36 | 37 | ```bash 38 | watch 'll | grep .go' 39 | ``` 40 | 41 | ## Install 42 | 43 | ```bash 44 | go install github.com/antonmedv/watch@master 45 | ``` 46 | 47 | ## License 48 | 49 | MIT 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/antonmedv/watch 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/creack/pty v1.1.11 7 | github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 8 | github.com/iamacarpet/go-winpty v1.0.2 9 | github.com/rivo/tview v0.0.0-20201204190810-5406288b8e4e 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= 2 | github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 3 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 4 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 5 | github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4= 6 | github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA= 7 | github.com/iamacarpet/go-winpty v1.0.2 h1:jwPVTYrjAHZx6Mcm6K5i9G4opMp5TblEHH5EQCl/Gzw= 8 | github.com/iamacarpet/go-winpty v1.0.2/go.mod h1:/GHKJicG/EVRQIK1IQikMYBakBkhj/3hTjLgdzYsmpI= 9 | github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= 10 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 11 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 12 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 13 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 14 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 15 | github.com/rivo/tview v0.0.0-20201204190810-5406288b8e4e h1:eP1XZiExUPO/FjS2q/PBo3CYbEtVvoMi8b7IpCBDWSo= 16 | github.com/rivo/tview v0.0.0-20201204190810-5406288b8e4e/go.mod h1:0ha5CGekam8ZV1kxkBxSlh7gfQ7YolUj2P/VruwH0QY= 17 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 18 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 19 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 h1:XtNJkfEjb4zR3q20BBBcYUykVOEMgZeIUOpBPfNYgxg= 21 | golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 23 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 24 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 25 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 26 | -------------------------------------------------------------------------------- /watch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | 12 | "github.com/gdamore/tcell/v2" 13 | "github.com/rivo/tview" 14 | ) 15 | 16 | var version = "1.0.0" 17 | 18 | func main() { 19 | // Command to watch can come from stdin or arguments. 20 | var command string 21 | 22 | if len(os.Args) <= 1 { 23 | fmt.Fprintf(os.Stdout, "Welcome to watch %v.\nType command to watch.\n> ", version) 24 | scanner := bufio.NewScanner(os.Stdin) 25 | for scanner.Scan() { 26 | command = scanner.Text() 27 | break 28 | } 29 | } else { 30 | command = strings.Join(os.Args[1:], " ") 31 | } 32 | 33 | startTime := time.Now() 34 | sleep := 1 * time.Second 35 | shell := defaultShell 36 | if s, ok := os.LookupEnv("WATCH_COMMAND"); ok { 37 | shell = s 38 | } 39 | sh := strings.Split(shell, " ") 40 | sh = append(sh, command) 41 | 42 | screen, err := tcell.NewScreen() 43 | if err != nil { 44 | panic(err) 45 | } 46 | err = screen.Init() 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | app := tview.NewApplication() 52 | app.SetScreen(screen) 53 | viewer := tview.NewTextView(). 54 | SetDynamicColors(true). 55 | SetScrollable(true). 56 | SetTextColor(tcell.ColorDefault) 57 | viewer. 58 | SetBackgroundColor(tcell.ColorDefault) 59 | elapsed := tview.NewTextView(). 60 | SetTextColor(tcell.ColorBlack). 61 | SetTextAlign(tview.AlignRight). 62 | SetText("0s") 63 | elapsed. 64 | SetBackgroundColor(tcell.ColorLightCyan) 65 | title := tview.NewTextView(). 66 | SetTextColor(tcell.ColorBlack). 67 | SetText(command) 68 | title. 69 | SetBackgroundColor(tcell.ColorLightCyan) 70 | statusBar := tview.NewFlex(). 71 | AddItem(title, 0, 1, false). 72 | AddItem(elapsed, 7, 1, false) 73 | flex := tview.NewFlex(). 74 | SetDirection(tview.FlexRow) 75 | flex.AddItem(viewer, 0, 1, true) 76 | flex.AddItem(statusBar, 1, 1, false) 77 | app.SetRoot(flex, true) 78 | 79 | go func() { 80 | for { 81 | cmd := exec.Command(sh[0], sh[1:]...) 82 | 83 | var buf bytes.Buffer 84 | err := cmdOutput(cmd, &buf) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | app.QueueUpdateDraw(func() { 90 | screen.Clear() 91 | viewer.SetText(tview.TranslateANSI(buf.String())) 92 | elapsed.SetText(fmt.Sprintf("%v", time.Since(startTime).Round(time.Second))) 93 | }) 94 | time.Sleep(sleep) 95 | } 96 | }() 97 | 98 | err = app.Run() 99 | if err != nil { 100 | panic(err) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /watch_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "syscall" 11 | 12 | "github.com/creack/pty" 13 | ) 14 | 15 | const defaultShell = "bash -cli" 16 | 17 | func cmdOutput(cmd *exec.Cmd, buf *bytes.Buffer) error { 18 | ptmx, err := pty.Start(cmd) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = pty.InheritSize(os.Stdin, ptmx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | _, err = io.Copy(buf, ptmx) 29 | if err != nil { 30 | // Linux kernel return EIO when attempting to read from a master pseudo 31 | // terminal which no longer has an open slave. So ignore error here. 32 | // See https://github.com/kr/pty/issues/21 33 | if pathErr, ok := err.(*os.PathError); !ok || pathErr.Err != syscall.EIO { 34 | return err 35 | } 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /watch_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "os" 9 | "os/exec" 10 | 11 | pty "github.com/iamacarpet/go-winpty" 12 | ) 13 | 14 | const defaultShell = "cmd /c" 15 | 16 | func cmdOutput(cmd *exec.Cmd, buf *bytes.Buffer) error { 17 | ptmx, err := pty.OpenWithOptions(pty.Options{ 18 | Command: cmd.String(), 19 | Env: os.Environ(), 20 | }) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | _, err = io.Copy(buf, ptmx.StdOut) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | ptmx.Close() 31 | 32 | return nil 33 | } 34 | --------------------------------------------------------------------------------