├── .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 |
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 |
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 |
--------------------------------------------------------------------------------