+454 -21 +/-8 browse
1 | diff --git a/Makefile b/Makefile |
2 | new file mode 100644 |
3 | index 0000000..90f1f99 |
4 | --- /dev/null |
5 | +++ b/Makefile |
6 | @@ -0,0 +1,15 @@ |
7 | + |
8 | + .PHONY: \ |
9 | + all |
10 | + |
11 | + all: bin/pomo |
12 | + |
13 | + clean: |
14 | + rm -v bin/pomo bindata.go |
15 | + |
16 | + bindata.go: |
17 | + go-bindata -pkg main -o $@ tomato-icon.png |
18 | + |
19 | + bin/pomo: bindata.go |
20 | + mkdir bin 2>/dev/null |
21 | + go build -o bin/pomo |
22 | diff --git a/bindata.go b/bindata.go |
23 | new file mode 100644 |
24 | index 0000000..8f4bcd9 |
25 | --- /dev/null |
26 | +++ b/bindata.go |
27 | @@ -0,0 +1,235 @@ |
28 | + // Code generated by go-bindata. |
29 | + // sources: |
30 | + // tomato-icon.png |
31 | + // DO NOT EDIT! |
32 | + |
33 | + package main |
34 | + |
35 | + import ( |
36 | + "bytes" |
37 | + "compress/gzip" |
38 | + "fmt" |
39 | + "io" |
40 | + "io/ioutil" |
41 | + "os" |
42 | + "path/filepath" |
43 | + "strings" |
44 | + "time" |
45 | + ) |
46 | + |
47 | + func bindataRead(data []byte, name string) ([]byte, error) { |
48 | + gz, err := gzip.NewReader(bytes.NewBuffer(data)) |
49 | + if err != nil { |
50 | + return nil, fmt.Errorf("Read %q: %v", name, err) |
51 | + } |
52 | + |
53 | + var buf bytes.Buffer |
54 | + _, err = io.Copy(&buf, gz) |
55 | + clErr := gz.Close() |
56 | + |
57 | + if err != nil { |
58 | + return nil, fmt.Errorf("Read %q: %v", name, err) |
59 | + } |
60 | + if clErr != nil { |
61 | + return nil, err |
62 | + } |
63 | + |
64 | + return buf.Bytes(), nil |
65 | + } |
66 | + |
67 | + type asset struct { |
68 | + bytes []byte |
69 | + info os.FileInfo |
70 | + } |
71 | + |
72 | + type bindataFileInfo struct { |
73 | + name string |
74 | + size int64 |
75 | + mode os.FileMode |
76 | + modTime time.Time |
77 | + } |
78 | + |
79 | + func (fi bindataFileInfo) Name() string { |
80 | + return fi.name |
81 | + } |
82 | + func (fi bindataFileInfo) Size() int64 { |
83 | + return fi.size |
84 | + } |
85 | + func (fi bindataFileInfo) Mode() os.FileMode { |
86 | + return fi.mode |
87 | + } |
88 | + func (fi bindataFileInfo) ModTime() time.Time { |
89 | + return fi.modTime |
90 | + } |
91 | + func (fi bindataFileInfo) IsDir() bool { |
92 | + return false |
93 | + } |
94 | + func (fi bindataFileInfo) Sys() interface{} { |
95 | + return nil |
96 | + } |
97 | + |
98 | + var _tomatoIconPng = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x00\x5c\x0f\xa3\xf0\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x30\x00\x00\x00\x2b\x08\x06\x00\x00\x00\x3e\x13\x0b\xdf\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x21\x38\x00\x00\x21\x38\x01\x45\x96\x31\x60\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x01\x15\x08\x10\x11\xe3\x9e\xfd\x2f\x00\x00\x0e\xe9\x49\x44\x41\x54\x68\xde\xbd\x99\x7b\xb0\x5f\x55\x75\xc7\x3f\x6b\xef\xf3\xf8\x3d\xee\xcd\xcd\xbd\x24\x04\x12\xc2\x0d\x24\x24\x40\x54\x10\x82\x48\x40\x8c\x3c\x1a\xc1\x57\xcb\x8c\xd8\x8e\x56\xb1\x6a\x47\xc4\x57\xa1\x0e\x95\x69\xad\x45\x6a\xd5\xce\x50\xc5\x0a\x4c\x61\x46\x45\xab\xad\x45\xda\x29\x1d\x6d\xb4\xf1\x0f\xd4\x0a\x02\xa6\x3c\x42\x0c\x90\xc7\x0d\x49\x48\xb8\x79\xdd\xdc\xdf\xeb\x3c\xf6\x5e\xfd\x63\x9f\xdf\xbd\x37\x0f\x2c\x81\xb6\x67\xe6\xcc\x39\x73\xce\xde\xfb\xac\xef\x7a\x7c\xd7\x5a\xfb\xc0\xcb\x38\xde\xf9\xe1\xf3\xb8\xfe\xf3\x97\xcb\xe1\xcf\xef\xbe\xff\xa3\xe7\xf2\xff\x7c\x98\x97\x33\xe9\xf4\xe5\xc7\x9f\x76\xeb\x4d\x3f\xd6\x99\xcf\xee\xfc\x97\x3f\x34\xe0\xdf\x75\xf8\xd8\xb3\x2e\x58\xf4\x92\xd6\xfc\xe2\x5d\x57\xfd\xdf\x03\xb8\xe5\x8e\x77\xbc\xfa\x73\x77\xbe\xfd\xc2\xa7\x37\xec\xd9\xf4\x27\xb7\xbe\xf9\xd0\x97\x4e\x4f\x2c\x8b\xe2\x75\x33\x1f\x5d\x7c\xe5\x19\x3c\xf6\x8b\xad\x9c\x7f\xc9\xd2\x5b\x56\xbd\x75\xf9\xd6\x37\xbe\xfd\xcc\xe3\x01\x3e\xfd\x57\x57\x02\xf0\xf9\xdb\x7f\xc7\xdc\x72\xfb\x6f\x37\xbe\xf8\x77\x57\x99\x1b\x3f\x74\xdf\x51\xbf\x79\xdd\x9f\xad\xb2\xbf\x49\x26\x79\xa9\xc2\x7f\xe5\xdb\xef\xe2\xc0\xfe\xee\x95\xdb\x77\x1c\x58\x70\xd7\x17\x1e\xb8\xeb\x9a\x1b\x56\x32\x7f\xeb\x04\x9f\xff\xfe\x7a\x00\xbe\xfc\xed\xf7\xcd\x01\xc6\xbd\xf3\x0b\xaf\x7f\xdf\xb7\xb6\xf7\xe7\x5d\xf4\xe6\xd3\x3f\x10\x27\xe6\xee\xa4\x66\x37\xad\xf9\xde\x13\x4b\xfa\xcf\x6f\xfa\xc2\x95\x75\x1b\x9b\xd1\xcf\xdd\xf0\x6f\xbf\x3e\xda\xf7\x3e\x70\xc3\x1b\x68\x34\x92\x18\xc1\x7c\xf5\xe6\xb5\xd9\x2b\x06\xd0\x3f\x6e\xf8\xdc\xe5\xef\x9f\x68\xf5\xce\xb9\xfb\x8b\x3f\xfd\x18\xc0\x37\x41\x46\x41\x57\x01\xb7\x7c\xed\xea\xcd\xde\xf9\x9d\x9f\xf9\xf8\xbd\x17\x01\x5c\x79\xd5\xab\x86\x34\x36\x9b\xe3\xd4\x8e\x44\xc2\x79\xf7\x7d\x73\xdd\x23\x00\x9f\xf8\xf4\xa5\x57\xa7\xa9\xdd\xf3\xa5\xcf\xfe\xe8\x27\x3b\x41\x9e\x04\x3d\x01\x78\x4d\xf5\x8d\x3b\x2e\x3d\x95\x6b\xd7\x6e\xe6\x83\x9f\xba\x78\x71\x6c\xec\x58\x5c\x33\xe5\x6d\x7f\xb1\xf6\xe5\x03\x78\x16\x58\x02\xdc\xfe\xee\x73\xf9\xc8\xdf\x3f\xca\x87\x6f\x5c\xf5\xd5\xdc\x95\xe7\x0d\xb5\xf3\x2b\x6f\xbd\xe3\x97\xfb\x5f\x58\x7e\x72\x74\xfc\xfa\x6d\xee\xcf\xff\xf4\x8a\xd7\xb7\x2d\x3f\xf7\x5e\x9f\x59\xb0\xa3\x75\xfe\x83\xae\x7d\x95\x8f\xcc\xdd\xd6\xc8\xee\x8b\xc6\xf3\xa5\x1f\xff\xe1\xee\xce\x27\x7e\x7f\xf1\xf9\x79\x2d\xba\xf6\x8e\xbf\x7d\xe0\xbd\xe3\xe7\x2e\x4b\xf0\x28\xbe\x54\x2d\x32\xcf\xf8\x7e\x9d\x9c\x2c\x74\x49\x2f\xf7\xef\xbd\x6e\xe5\x1c\x63\xcd\xaa\x6f\xdc\xf6\xb3\x7b\x5f\x91\x0b\x3d\x0d\x2c\x05\x9e\x02\x99\x9d\x20\x66\xe9\x22\x99\xf7\x64\xce\x87\xaf\x3b\xe5\x33\x45\x5d\xae\x1f\x2c\xf4\xfa\x2f\x7f\x65\xc3\x77\x38\x7f\xc4\x72\xa0\xe7\x3f\xf0\xc6\xd1\x3b\x4a\x2f\xef\x51\xef\x77\x76\x6d\x9e\x02\xc7\x59\xe5\xf1\x9b\x9f\x9e\xbc\xec\x9e\x39\x03\xaf\xdf\xd4\x8c\xef\x3b\x2d\x2f\xdf\x72\xf3\xd8\xbe\x87\x11\x51\xac\xf7\xa8\x78\xd4\x78\x9e\xee\x79\xd9\xb3\xbd\xf7\xbb\xef\x7d\xdd\xb5\x02\xbb\xbe\x7b\xcf\x2f\xff\xd9\x01\x7b\x80\x79\xc7\x0a\x60\x0c\x58\x87\xe1\xf5\x78\xca\xe3\x1a\x26\x9e\x3b\x62\xa2\xfa\x40\x34\x62\x0b\xf3\x5c\xcf\xcc\x6e\x77\x38\xf5\xde\xd3\x9a\x5f\xda\xbc\xa4\x7e\x41\x33\x36\x0f\x5d\xf2\xd0\x44\xf7\xe4\xdd\x45\xbd\x1d\x61\x6f\x5b\x3e\xb0\xc2\xc5\x10\xcd\x16\x50\x18\xee\xfa\xbd\xef\x79\xb8\xb3\xee\x2b\x4b\xea\x17\x78\xd1\xe6\x35\x5b\x32\xd2\xd2\x33\x52\x82\xa8\xd2\xa9\xd9\xf1\x9d\x0d\xf3\xf8\xd8\x70\xf4\xd8\x84\x70\xe2\x85\xe3\xdd\x4f\xbe\x75\xdd\x96\x3d\xcf\xbf\xe6\x24\x6b\x77\xee\x76\xcf\xef\xe9\xf8\xb3\x81\xfd\xc0\xf0\xb1\x58\x60\x1b\x48\xed\xec\x53\x2c\x3e\x8e\x53\xa3\xf1\xbe\x82\x39\xbb\x7a\xe6\x26\x2f\xbc\x33\x86\xba\x15\xa2\x71\x8b\xdc\x73\x46\x9d\xc6\x69\x03\x0c\xf4\x3c\xbf\xf5\xab\x83\xfc\xa2\x9b\xf3\xc8\x29\x4d\x9a\x23\x36\x00\xe8\x79\x0e\x3e\x97\xf3\x42\x04\x83\x85\x52\x02\xef\x9e\x74\x14\x0b\xea\xac\x9b\x93\xb0\x6f\xbc\xc7\x3b\xc6\x32\x16\x64\x0a\x8e\xd2\x29\x7b\x53\xab\x1f\x5b\x31\xe2\x7e\x30\xde\xb5\x4e\x7d\xa7\x48\x9e\xdc\xee\x87\x41\x5f\x92\x05\x36\x01\xb5\xb9\x75\x13\x9f\x30\x3f\x9a\xbb\x70\x48\x1e\xdf\xd4\x3e\xbb\x5d\x72\x3d\xc2\xd5\xb1\x80\xb5\x50\xa0\x6c\x3a\xb1\xce\x9e\x0b\x47\x71\x93\x05\x3f\xd8\xb0\x03\xaf\x8a\x4b\x84\x46\xe6\x29\x13\x43\x63\x24\x22\xaa\x1b\xbc\x42\xb6\xcf\x91\xd6\x2c\xc9\x40\x00\x35\x99\x7b\x16\xef\xca\x58\xb5\x3b\x63\x45\xe6\x29\x10\x72\x85\x42\x21\xf7\x42\xee\xc1\x2b\x0f\xa7\x46\x6f\x5c\xb1\x32\x7b\xe0\x85\x47\x0d\xf1\xfa\x2d\x7e\x04\xf4\x39\x60\xe1\x8b\x01\xd8\x04\xd2\x9c\x9d\x4a\xf4\x9a\xb3\x63\xe6\xcf\x1f\xda\xbc\x61\xec\x8f\xcb\x4e\xf7\x53\x89\x78\x62\x03\xa9\x35\x48\x22\xfc\xe4\xcc\x59\x8c\xc4\x09\xe7\x3f\x33\xc1\x3f\xfa\x92\x35\x0d\xa1\x4f\xd8\x3a\xe3\x3a\x70\x42\x42\x3a\x68\xa9\xa5\x09\x07\x77\x75\x69\x8f\x67\x24\xc0\x15\x07\x4a\xce\x29\x94\x93\x44\xb0\x0a\x8a\xe2\x14\x4a\x85\xc2\x43\xee\xa1\xe7\x85\xdc\x81\x08\x37\x9e\xd6\x74\x5f\xc3\xb9\x2c\x5b\xbf\xc5\x2d\x9c\x61\x89\x29\x00\x7b\x80\x39\xc0\xce\xe1\xc4\xd4\xce\x59\x91\x74\x47\x97\x2c\xd8\xf1\xeb\xb1\xef\x49\xb7\x7d\x4e\xdc\x6b\x93\x88\x52\x4b\x0c\x71\x2d\xc6\x0d\x24\x34\xa2\x88\x24\xb6\x6c\x68\x75\x69\x95\x9e\x8e\x01\xa7\x50\xa8\x67\x20\x89\x88\x54\x69\xe7\x05\x59\x59\x82\x15\x4c\xcf\x31\x0b\x38\x4e\x84\x05\x6a\x49\xa3\x88\xae\x31\xf4\xbc\xc7\xbb\x92\x86\x73\x44\x4e\xf1\xaa\x53\x20\x32\x0f\x5d\x17\x80\x78\xe5\x6f\x2e\xde\xbc\xf1\xfa\x17\xce\x9c\x6f\xf7\x3f\xb5\xd3\x0d\x02\xf3\x81\x08\xe0\xd1\x4a\xf8\xf1\x54\x24\x7a\xd5\xab\xa3\xf6\xc9\x4b\x16\xee\x1a\xdb\xfd\xa3\xb4\x16\x2d\xb2\xed\x82\xd8\x28\xb5\x48\x88\x1b\x31\xf1\xec\x26\x76\x78\x00\x33\x50\x47\x93\x88\xe1\x76\x97\x7a\x5e\x52\x38\x8f\xa8\xb2\x70\x78\x90\xba\x2a\xae\xd5\x41\xf7\xb7\xd1\x89\x36\xda\xcd\xd0\x52\x41\x04\x92\x04\x33\x38\x08\x8d\x06\xcd\x24\xa1\xe9\x3c\xf4\xba\xb8\xc9\x49\xb4\xdb\xc1\x38\x87\xf1\x60\x6c\x28\x13\xa4\xb2\x65\xcf\xc9\x1f\xfd\x6c\xf1\xb2\x2d\x26\xd2\xbb\x66\xcd\xad\xeb\x83\xe3\x3d\x0f\x1a\x00\x9c\x0b\x6c\x04\xf1\xcb\x4e\xb5\x43\x97\x5c\xd2\x7c\xf6\xe7\x8f\xff\xb0\xd6\xac\x2f\xb2\xe3\xbb\x88\xcb\x8c\xc4\x28\x49\x64\x89\x6a\x31\x76\x56\x03\x3b\x32\x80\x0c\x35\x21\x8d\x39\x29\x1a\xe1\x60\xa7\x47\xb7\x93\xd3\xa8\x27\x34\x6a\x31\xda\x2b\x90\xd8\x42\xe9\xf1\xdd\x1c\xcd\x0a\x54\x14\xac\xc5\xd4\xeb\xc8\xf0\x30\x32\x34\x8c\x24\x09\x78\x87\xb6\x5b\x18\x55\x7c\x51\x80\xf7\x58\xf1\xa0\x20\x36\x38\x89\x56\xee\xd8\x75\xfc\x75\xcf\xf9\xef\xd6\x8e\x9f\x3b\xb1\x60\x7c\x9b\x02\x6a\x00\x7e\x88\x30\x30\xb7\x26\xb5\xa5\x67\xd4\xd6\x3d\xf8\xd4\x5f\xd6\x46\x86\x17\x47\x79\x46\xdc\x69\x91\xe2\x49\x8c\x62\xc4\x23\xde\x43\x51\xa0\x79\x81\xf6\x72\xc8\xc3\xfd\x60\x1c\x33\x54\x4f\x68\xc6\x16\xcd\x0a\xc8\x8a\xf0\xae\x70\xe0\x3d\x68\xe5\xb2\xaa\xe0\x3d\x5a\x3a\x70\x05\x94\x05\x94\x25\x5a\x3a\x54\x3d\x33\x49\xc6\x08\x58\x20\xb6\x90\x1a\x48\x8d\x12\x0b\xe9\xa6\xae\xfd\xce\x71\xcb\x22\x3f\xda\x40\x66\xf7\x2d\xf4\x04\xc8\x82\xb3\x17\x47\xf9\x05\x97\x8e\x3e\xbf\x73\xef\x33\x8d\x81\x3a\x66\xf3\x33\x24\x93\xfb\x89\x45\xb1\x22\x88\x15\x24\x89\x91\x66\x0d\x33\x58\x47\x1a\x29\x92\x44\x10\x9b\x60\x6f\xd1\x10\x04\xa5\x43\xb3\x12\xdf\xe9\xa1\xad\x2e\xda\xca\xd0\xbc\x0c\x40\x44\x90\x28\x42\x1a\x4d\xa4\xd1\x40\xe2\x18\xf5\x1e\xcd\x72\xb4\xdb\x41\x7b\x3d\x70\x65\x00\x5a\x81\x76\x0a\xb9\x0a\x5d\x07\xad\x12\xba\x4e\x7c\x62\x58\xb1\x28\xe9\x3d\xd1\x79\x6a\xac\x8c\x6e\x03\x86\x07\x90\xe1\x0f\xbd\x8b\x47\xee\x7d\xe4\xeb\x03\x27\x2f\xc0\xec\xdf\x43\xd4\x6d\x13\x09\x58\xa9\x8c\xe8\x09\x9a\x57\x8f\x66\x05\x12\x9b\xe0\x26\x91\x0d\xea\x12\xc0\x87\xe8\xd3\xa2\x44\x0b\x0f\x45\x19\xac\xa0\x0a\x62\x40\x40\xbd\x42\xa7\x83\xe6\x19\x12\xc5\x60\x4d\xa5\x78\x45\x22\x0b\x28\xea\xfa\xd6\x50\x0c\x1a\x2c\x61\xc2\x99\x2b\xa6\xf0\xac\xaa\xc5\xc9\xfa\x6d\x31\x12\x5d\x0a\xd8\x79\xf3\xcc\x53\xf7\xff\xea\x55\x76\x70\x60\x65\x64\x0d\x32\x71\x80\xc8\x15\xd8\x99\x34\xa5\x01\x04\x85\x47\x5d\x06\x85\x81\x58\x82\x8d\x8d\x09\x1f\xec\x03\x28\xc3\x89\x56\x0b\x88\x41\x4c\x7f\x5c\xdf\x3f\x22\x88\x63\x48\x12\xc4\x58\xc4\x3b\x34\xcf\x03\xb0\xbc\x40\xbd\x03\xaf\x88\x2a\x56\x15\xeb\x95\xb8\x72\x2b\x27\x5c\x36\x6b\x56\xfe\xd5\xfa\xec\x21\x89\xa2\x08\x89\x06\x87\x4d\xe6\xe5\xa2\x74\x68\xd0\x48\xd6\xc3\x76\x5a\x18\x14\x11\x3d\x32\xcb\xa9\x0f\x52\x59\x50\x6b\x90\x58\x90\x48\x40\x25\x08\xee\x05\xdc\x34\x4b\x8b\x11\x88\x62\x24\x4e\x20\x8a\x10\x63\x02\x1b\x45\x31\x34\xea\x98\x7a\x13\x92\x38\xb8\x5e\xb7\x8d\xb6\x5a\x68\xaf\x87\x14\x39\xea\x1c\x38\x1f\x98\x49\x3d\x46\x35\x18\xac\x64\x05\xc3\xc2\x70\x14\x49\x64\x63\x2b\x73\xce\x38\xc5\x3c\xb7\xdf\xbe\x31\xaa\xd5\x90\xf1\xe7\xb1\x65\x5e\x79\x85\x4c\x07\x96\x4c\x47\x97\x44\xc1\x9e\xa6\x6e\x91\x7a\x15\x07\x0a\xe4\x0e\x28\x50\x27\x88\x17\x54\x4d\x10\x34\xad\x05\xbf\x4f\x93\x90\xc6\xc5\x20\x49\x82\x0c\xce\x0a\x94\x9a\xa4\x50\x16\xf8\xc9\x49\x34\x9d\xc0\xb7\x26\x21\xcf\x2a\xc2\xc8\x91\x3c\xc7\x6a\x81\xf5\x0e\x83\x82\x70\x3c\x13\xc6\x26\x43\x49\x19\xc5\xd6\x08\xe7\x9e\x25\xfc\xe4\xa9\x73\x8d\x11\xa4\xd3\x01\xe7\x2a\xf2\x92\x43\xab\x0e\x21\x04\x73\x64\x20\xb5\x48\x33\x42\x9a\x31\x92\x58\xd4\x29\x74\xa5\x4a\xa5\x8a\x3a\x10\x0c\x92\xa4\x48\xb3\x89\x99\x35\x04\xf5\x06\x12\x47\x60\x0c\x52\xab\x23\x43\x43\x98\xa1\xd9\x48\x92\xa2\x65\x89\x1c\x9c\xc0\xa7\x29\xd2\xa8\xa3\xdd\x2e\xe4\x19\xda\xe9\xe0\xda\xed\x40\xab\x4e\x11\xe3\x31\xa5\xb2\x7e\x57\x7c\xd2\xe2\x86\xdf\x1a\xd5\xe6\x0c\xc0\xbc\x13\x2c\xb2\x61\x91\x38\x07\x59\x2f\xb8\xcf\x4c\xed\x4f\x61\x90\xe0\xbf\x91\x41\x52\x8b\xd4\x63\xa4\x19\x43\x62\x90\x22\x14\x2f\xf4\x4a\xd4\x84\x71\x82\x85\x24\x46\x1a\x0d\x18\x1c\xc4\x0c\x0c\x42\x1c\x57\x4c\xd4\x40\x66\x0f\x07\x00\x69\x0d\x2d\x0b\x7c\x9a\x62\xa2\x08\xad\xd5\xd0\x6e\x17\xed\x75\x21\x4e\x30\x08\xb6\xf4\x98\xdc\x05\xb7\x16\xa5\xe7\x19\xb6\xea\xc7\xa2\x7a\xa3\x2e\x3b\xfe\xe3\x91\xe3\xc5\x5a\x28\x4b\x28\xf2\xa9\xe0\x3b\x02\x82\x04\x10\x81\x52\x6d\x00\x51\xb3\x15\x95\x0a\xf4\x1c\xbe\x02\x29\xc6\xa0\x62\x31\x71\x8c\xa4\x35\x4c\xbd\x01\x8d\x3a\x92\xd4\x30\x49\x02\x8d\x66\x10\x7e\xd6\x10\x52\xaf\x23\x79\x0e\x22\x78\xf5\x61\xad\x34\x85\x76\x1c\x34\x5f\xe4\xd0\xed\x81\xcd\x10\x09\xc5\x91\xaa\x46\x46\x3d\x51\x1a\x45\x3a\xb1\xf7\xe0\x5c\x13\xa7\x68\x59\x4c\xb9\xcf\x51\x85\xef\x5f\x6d\xc5\xaf\x91\x40\x6c\x90\xc8\x86\xd8\xb6\x21\x68\xa7\xe6\x19\x83\x58\x8b\xc4\x31\x12\xc7\x10\xa7\x21\x0e\xd2\x5a\x10\xba\x56\x0b\x96\x48\x6b\x68\x14\x21\x45\x81\xa9\xd5\xf0\x79\x11\x58\xcf\x79\xc8\xf3\x30\x2f\x9a\xc1\x62\x80\x35\xb4\xbd\x58\x35\xd6\x1a\xf1\xae\xac\x85\xac\x51\x65\xce\x17\x2b\x55\x65\xe6\x29\xd3\x8c\x22\x87\x0f\x96\xca\xe3\x24\xf0\xbf\x39\xda\x69\x43\x40\x57\x6b\x48\xf5\x5c\x4d\x3f\xaf\x54\x56\xad\xd6\xf7\x48\xc8\x6f\xd5\xda\x23\x91\xdf\xdd\x2d\x55\x0c\x06\x35\x51\xd4\x53\xf5\xa8\x57\x54\xa7\xf5\x7e\x04\x89\xf6\x8b\x12\x1f\x92\x96\x7a\x5f\x5d\xab\x1c\xa0\x87\xce\xd4\x2a\xa3\xaa\xf7\xa8\x2a\xd2\x1f\xe7\x5d\xc8\xb8\x45\x28\x25\x70\x25\x5a\x84\xd2\x42\x8a\x02\xf2\xbc\xca\x09\x39\x5a\xe4\x68\x59\x82\x73\xa8\x06\xf9\xbc\xc2\xe8\x68\xb1\x3f\xcb\x32\x8d\x26\x5b\x6d\xe6\x2e\x3c\x61\xfb\xf6\xb1\xdd\x68\x62\x0f\x91\xd1\x1c\x92\xc8\x2a\xdd\x6a\xc8\xa6\x52\x7a\xb4\xf0\x48\xee\x10\x05\x5f\x56\x0a\xf0\xd3\xdd\x80\x54\xc2\x8b\x73\xa1\xe6\x29\xf2\xe0\x62\x55\x66\xf6\x69\x82\x89\x93\x00\x20\x2f\xd0\x76\x0b\xdf\x9a\x44\x5b\x93\x68\xbb\x0d\xdd\x0e\xda\x6e\xa3\xdd\x2e\x3e\x2f\x70\xce\xe1\x54\x91\xd0\xd3\xd0\x9d\xc8\x88\x5a\x7b\xbb\x9c\xf4\x96\x0b\xf6\x6d\xbb\xed\xde\xcc\xf9\x34\x15\x31\x95\x32\x15\x3d\xdc\x87\x7c\xf0\x4b\x29\x3c\x9a\x39\xe8\x96\x78\x2b\x48\xaa\x50\x7a\x28\x3c\xe2\x74\xda\x92\xce\x05\x2d\x67\x59\xa0\x45\x6b\x43\x01\x67\x2d\x9a\xe7\x18\x25\x58\x27\x4d\x21\x2f\xf0\x13\x07\xd0\xbd\x7b\xf1\x13\x07\xf0\x9d\x36\x74\xbb\x68\xbb\x8d\x6b\xb5\x70\x59\x86\x2f\x1d\x2e\x78\xf8\x63\x4c\x2a\xde\xab\x46\x9d\x89\x96\xf2\xc4\x13\x1e\xe7\x1f\xf5\xce\xaf\x74\x18\x4a\x95\x6a\xcb\x4e\x31\x32\x03\x85\x0a\xea\x41\x4a\x45\x72\x8f\x74\xca\xc0\xac\x55\xff\xe7\x7b\x65\xc8\xc6\xce\x87\x2a\x0c\x87\xe6\x19\xbe\xd3\x09\x81\xec\x1c\x12\x45\x28\x60\xe2\x18\xdf\xeb\xa2\xdd\x4e\x95\x07\x0a\x74\xe2\x00\x7e\xdf\x5e\x74\x62\x02\xed\x74\xf0\x79\x86\xf6\x32\x7c\x2f\xa3\xcc\x73\x4a\xe7\xf1\xde\x63\xe0\xa7\x93\xad\xc4\xec\x9e\x98\x28\xa3\x7d\x05\xda\x5e\xfb\xa0\xa2\x83\x3f\x2a\xb2\x6c\xa5\x8a\xc5\x68\x70\x1f\x45\xa6\xdd\xa8\x9f\xd7\x9c\x22\xa5\x43\x7a\xa1\xc8\x13\x07\x3e\xae\x8a\xaf\xdc\xa1\x3d\x87\x96\x5a\xd5\x42\x82\x98\x1c\x6d\xb7\x43\x9c\xf7\x7a\xa1\x26\xc2\xe3\x4d\x84\x69\xb5\x60\xb2\x85\x24\x31\x14\x21\x13\xfb\x89\x03\x68\x6b\x32\x58\xad\x28\x70\xa5\xc3\x15\x25\x65\xe1\x29\x4a\xa5\x54\x7c\x04\x0f\x75\xf3\xd2\x0d\x17\x68\x34\x0f\x74\x72\xeb\x33\x3e\x1a\x7d\xed\xda\x7c\xb2\xfd\x59\x05\xf0\x02\xa2\x81\xde\x75\x06\x83\x1a\x05\x27\x53\x5d\x92\x78\x17\xac\x61\x2b\x7a\x2b\xb5\xaa\x42\x83\x05\x14\x8f\x64\x05\xe2\x3b\x48\xe9\x42\x2d\xd4\xb7\xa8\x18\x24\x9d\xc4\xd4\x0e\x06\x1a\x76\x2e\x24\xaf\x4e\x3b\x94\xd5\x45\x81\xf7\x0e\xe7\x94\xdc\x29\x59\xa9\x14\x1e\xbc\x67\xcf\xeb\x8e\x73\x0f\xbd\xb0\x7d\x97\xdf\x0e\xc8\xfd\xc0\x59\x20\x0b\x55\xe5\xe7\xaf\x3d\xff\x09\xab\xee\xcc\xb8\xdb\x21\x15\x37\x95\x9f\xa6\x58\xb2\x4f\x9f\x80\xa9\x72\x81\x58\x99\xe6\xe7\x2a\x46\x70\x15\xdb\x54\x33\xc5\x9a\xd0\x8d\x19\x13\xe2\x4a\x41\x4c\xe8\x0d\x88\xe2\x90\xf4\x34\x34\x4b\xfd\xfa\x27\x30\x17\x94\x5e\xe9\x39\xe8\x39\xa5\x1b\x94\xf7\x7b\x2b\x17\xb4\xfe\x69\xec\xa7\x3b\x74\x11\xf8\xe8\x6d\xc0\x66\x40\x2f\x5c\x2e\x43\x83\xc7\xbd\x7d\xdf\xae\x7d\xcf\x7a\xaf\xa8\x11\x1c\x60\xd1\x29\x10\x7d\x2b\x08\x02\x1a\xdc\x47\x44\xc1\xf8\x69\x86\xd2\x50\x7a\x8b\x07\x24\xb8\x51\x18\xeb\xa7\xb5\x4f\xe8\x8f\x45\x0a\x30\x59\xb8\x57\x42\x7b\xe9\xdd\x94\xf0\xde\x2b\x85\x42\xcf\x43\xe6\x05\x07\xeb\xce\x1b\x2a\xff\x75\xf7\xd6\x8c\x6b\x02\x51\x22\x5b\x80\x53\x80\xed\x35\x64\xf6\xf2\x65\xd1\xc3\xfb\xe5\x56\x54\x3f\x1a\x87\x92\x07\x2b\x15\x9d\x4a\x00\x32\x6d\x88\xca\x95\x4c\xbf\x6a\x0d\x31\xa1\x3a\xdd\x88\x33\xc3\xfd\x10\x39\x2c\x39\x56\xc5\xa1\xc8\x94\x55\x50\x0d\xec\xe7\x15\xad\xba\xb1\xb0\xc5\x12\xf6\x8c\x2c\x5c\xb1\x74\x96\x5f\xdb\xfe\xaf\x67\xca\x53\x2b\x62\x97\xfe\x46\x56\x01\xcc\x1e\xb0\x66\xde\xaa\x45\xac\x7d\x32\x7a\xce\x08\xf3\xfb\xc2\xf7\x2d\x30\xe5\x4e\x87\xb9\xd5\x21\xd7\xbe\x82\x5f\x6c\xf7\xec\x45\xf6\x02\x75\x1a\x73\xc8\x75\x15\x00\xe7\xa1\x0c\xd3\x3e\x79\xf1\x65\x93\xb7\xbd\xf0\xf5\xe7\x65\x7d\xa1\x7e\x3e\x70\xfa\xe1\xcb\x1d\x00\x86\x46\x90\xff\x1c\x5e\x36\xdc\x29\x59\x2f\x86\x13\xcc\x74\x37\x18\xa8\x55\x42\x60\x73\x58\x05\x71\x48\x9c\x4c\x2b\xfc\x48\xb9\x95\x23\x32\xbd\xce\x00\xd0\x6f\x26\xfb\x89\xdd\x85\x41\xdf\x7a\xd3\x3c\xff\x07\x3b\xb6\xed\xf5\xed\x9d\xfb\xfc\xb2\x17\xd3\xc7\x58\xb5\x95\x7e\x16\xc8\x63\xa3\x4b\xe6\x3a\xb1\xcf\x09\x24\x1c\x5a\x02\xcd\x2c\x4c\x8f\xa8\xf3\x8e\x26\x7c\xff\x9d\xfe\x06\xad\xeb\x8c\xce\x55\xa7\x3d\x0a\xe0\x1b\x97\x6d\xdd\xf8\xfe\x5d\x16\x53\x73\xa1\x52\x1b\x79\xa9\x9b\xbb\x3f\x3e\x79\xe9\x5c\x2f\xb2\x16\x78\xf5\x4c\xa1\x84\xa3\xbb\x88\x1c\xe3\x6f\x13\x7d\x11\x6b\xcc\x48\x3b\xd7\x5d\xbe\x75\xe3\xed\xbf\x8c\x22\x29\xcb\x52\x57\xbe\xdc\x1f\x1c\x6b\x46\x97\xdd\x0d\x5c\x53\x15\xd2\x47\xcc\xfc\x9f\x16\x39\x9a\xf6\x8f\x1a\x03\xd3\x37\x5b\x54\x79\xdb\xea\x6d\x1b\xd7\xf7\x1f\x3f\x00\x5c\x7c\xac\x3f\xf9\xd6\x8c\x2e\x63\xcd\xe8\x32\x56\x8f\x6d\xfc\x20\xb0\x1c\x78\x76\xa6\x79\xfb\xa7\x3f\xca\x79\xf8\xfb\xdf\x34\xf6\x90\xf1\xf0\x6e\x0f\x67\xae\xde\xb6\x71\xfd\xbf\xbf\xed\xfd\xfc\xb8\xff\xc3\xf0\x95\xfc\x23\xab\x40\xf4\xef\xaf\x03\x3e\x02\x9c\xf9\xbf\xf8\xbb\x77\x03\xf0\x0f\xab\xc7\x36\xde\x7c\xf8\xf7\x78\x09\xd6\x3d\xe6\x63\xcd\xe8\x32\x53\xfd\x36\xbb\x13\x78\xd3\x2b\x10\xfc\xfb\xc0\x4d\xc0\xa6\xd5\x63\x1b\xdd\xb1\x0a\x4f\x7f\x77\xfa\x65\x1c\x7e\xf5\xd8\xc6\xa7\x81\x4b\xd6\x8c\x2e\x9b\x05\xbc\x01\x78\x6d\x45\xcd\x27\x03\x73\x81\x05\x40\x13\xd8\x05\x8c\x03\xbb\x81\x2d\x95\xb6\xd7\x01\xbf\x58\x3d\xb6\xb1\xe8\x0b\xdd\x3f\x8e\x45\x78\x80\xff\x06\x26\x17\xf9\x29\x2d\x26\x91\x99\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\x01\x00\x00\xff\xff\x8e\x30\x0d\x01\x5c\x0f\x00\x00") |
99 | + |
100 | + func tomatoIconPngBytes() ([]byte, error) { |
101 | + return bindataRead( |
102 | + _tomatoIconPng, |
103 | + "tomato-icon.png", |
104 | + ) |
105 | + } |
106 | + |
107 | + func tomatoIconPng() (*asset, error) { |
108 | + bytes, err := tomatoIconPngBytes() |
109 | + if err != nil { |
110 | + return nil, err |
111 | + } |
112 | + |
113 | + info := bindataFileInfo{name: "tomato-icon.png", size: 3932, mode: os.FileMode(420), modTime: time.Unix(1516522577, 0)} |
114 | + a := &asset{bytes: bytes, info: info} |
115 | + return a, nil |
116 | + } |
117 | + |
118 | + // Asset loads and returns the asset for the given name. |
119 | + // It returns an error if the asset could not be found or |
120 | + // could not be loaded. |
121 | + func Asset(name string) ([]byte, error) { |
122 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
123 | + if f, ok := _bindata[cannonicalName]; ok { |
124 | + a, err := f() |
125 | + if err != nil { |
126 | + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) |
127 | + } |
128 | + return a.bytes, nil |
129 | + } |
130 | + return nil, fmt.Errorf("Asset %s not found", name) |
131 | + } |
132 | + |
133 | + // MustAsset is like Asset but panics when Asset would return an error. |
134 | + // It simplifies safe initialization of global variables. |
135 | + func MustAsset(name string) []byte { |
136 | + a, err := Asset(name) |
137 | + if err != nil { |
138 | + panic("asset: Asset(" + name + "): " + err.Error()) |
139 | + } |
140 | + |
141 | + return a |
142 | + } |
143 | + |
144 | + // AssetInfo loads and returns the asset info for the given name. |
145 | + // It returns an error if the asset could not be found or |
146 | + // could not be loaded. |
147 | + func AssetInfo(name string) (os.FileInfo, error) { |
148 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
149 | + if f, ok := _bindata[cannonicalName]; ok { |
150 | + a, err := f() |
151 | + if err != nil { |
152 | + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) |
153 | + } |
154 | + return a.info, nil |
155 | + } |
156 | + return nil, fmt.Errorf("AssetInfo %s not found", name) |
157 | + } |
158 | + |
159 | + // AssetNames returns the names of the assets. |
160 | + func AssetNames() []string { |
161 | + names := make([]string, 0, len(_bindata)) |
162 | + for name := range _bindata { |
163 | + names = append(names, name) |
164 | + } |
165 | + return names |
166 | + } |
167 | + |
168 | + // _bindata is a table, holding each asset generator, mapped to its name. |
169 | + var _bindata = map[string]func() (*asset, error){ |
170 | + "tomato-icon.png": tomatoIconPng, |
171 | + } |
172 | + |
173 | + // AssetDir returns the file names below a certain |
174 | + // directory embedded in the file by go-bindata. |
175 | + // For example if you run go-bindata on data/... and data contains the |
176 | + // following hierarchy: |
177 | + // data/ |
178 | + // foo.txt |
179 | + // img/ |
180 | + // a.png |
181 | + // b.png |
182 | + // then AssetDir("data") would return []string{"foo.txt", "img"} |
183 | + // AssetDir("data/img") would return []string{"a.png", "b.png"} |
184 | + // AssetDir("foo.txt") and AssetDir("notexist") would return an error |
185 | + // AssetDir("") will return []string{"data"}. |
186 | + func AssetDir(name string) ([]string, error) { |
187 | + node := _bintree |
188 | + if len(name) != 0 { |
189 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
190 | + pathList := strings.Split(cannonicalName, "/") |
191 | + for _, p := range pathList { |
192 | + node = node.Children[p] |
193 | + if node == nil { |
194 | + return nil, fmt.Errorf("Asset %s not found", name) |
195 | + } |
196 | + } |
197 | + } |
198 | + if node.Func != nil { |
199 | + return nil, fmt.Errorf("Asset %s not found", name) |
200 | + } |
201 | + rv := make([]string, 0, len(node.Children)) |
202 | + for childName := range node.Children { |
203 | + rv = append(rv, childName) |
204 | + } |
205 | + return rv, nil |
206 | + } |
207 | + |
208 | + type bintree struct { |
209 | + Func func() (*asset, error) |
210 | + Children map[string]*bintree |
211 | + } |
212 | + var _bintree = &bintree{nil, map[string]*bintree{ |
213 | + "tomato-icon.png": &bintree{tomatoIconPng, map[string]*bintree{}}, |
214 | + }} |
215 | + |
216 | + // RestoreAsset restores an asset under the given directory |
217 | + func RestoreAsset(dir, name string) error { |
218 | + data, err := Asset(name) |
219 | + if err != nil { |
220 | + return err |
221 | + } |
222 | + info, err := AssetInfo(name) |
223 | + if err != nil { |
224 | + return err |
225 | + } |
226 | + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) |
227 | + if err != nil { |
228 | + return err |
229 | + } |
230 | + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) |
231 | + if err != nil { |
232 | + return err |
233 | + } |
234 | + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) |
235 | + if err != nil { |
236 | + return err |
237 | + } |
238 | + return nil |
239 | + } |
240 | + |
241 | + // RestoreAssets restores an asset under the given directory recursively |
242 | + func RestoreAssets(dir, name string) error { |
243 | + children, err := AssetDir(name) |
244 | + // File |
245 | + if err != nil { |
246 | + return RestoreAsset(dir, name) |
247 | + } |
248 | + // Dir |
249 | + for _, child := range children { |
250 | + err = RestoreAssets(dir, filepath.Join(name, child)) |
251 | + if err != nil { |
252 | + return err |
253 | + } |
254 | + } |
255 | + return nil |
256 | + } |
257 | + |
258 | + func _filePath(dir, name string) string { |
259 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
260 | + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) |
261 | + } |
262 | + |
263 | diff --git a/libnotify/libnotify.go b/libnotify/libnotify.go |
264 | new file mode 100644 |
265 | index 0000000..60d91fd |
266 | --- /dev/null |
267 | +++ b/libnotify/libnotify.go |
268 | @@ -0,0 +1,47 @@ |
269 | + /* |
270 | + libnotify is a lightweight client for libnotify https://developer.gnome.org/notification-spec/. |
271 | + For now this just shells out to "notify-send". |
272 | + TODO: Move this into it's own repository as time permits. |
273 | + */ |
274 | + package libnotify |
275 | + |
276 | + import ( |
277 | + "fmt" |
278 | + "os/exec" |
279 | + "time" |
280 | + ) |
281 | + |
282 | + type Notification struct { |
283 | + Urgency string |
284 | + Expire time.Duration |
285 | + Title string |
286 | + Body string |
287 | + Icon string |
288 | + } |
289 | + |
290 | + type Client struct { |
291 | + Path string |
292 | + } |
293 | + |
294 | + func NewClient() *Client { |
295 | + return &Client{ |
296 | + Path: "/bin/notify-send", |
297 | + } |
298 | + } |
299 | + |
300 | + func (c Client) Notify(n Notification) error { |
301 | + var args []string |
302 | + if n.Urgency != "" { |
303 | + args = append(args, fmt.Sprintf("--urgency=%s", n.Urgency)) |
304 | + } |
305 | + if n.Icon != "" { |
306 | + args = append(args, fmt.Sprintf("--icon=%s", n.Icon)) |
307 | + } |
308 | + if n.Expire > 0 { |
309 | + args = append(args, fmt.Sprintf("--expire=%s", n.Expire.Truncate(time.Millisecond))) |
310 | + } |
311 | + args = append(args, n.Title) |
312 | + args = append(args, n.Body) |
313 | + _, err := exec.Command(c.Path, args...).Output() |
314 | + return err |
315 | + } |
316 | diff --git a/main.go b/main.go |
317 | index 0b2cd8c..519d1ec 100644 |
318 | --- a/main.go |
319 | +++ b/main.go |
320 | @@ -23,13 +23,15 @@ func start(path *string) func(*cli.Cmd) { |
321 | db, err := NewStore(*path) |
322 | maybe(err) |
323 | defer db.Close() |
324 | - task := Task{ |
325 | + task := &Task{ |
326 | Message: *message, |
327 | Tags: *tags, |
328 | NPomodoros: *pomodoros, |
329 | Duration: parsed, |
330 | } |
331 | - run(task, &I3{}, db) |
332 | + runner, err := NewTaskRunner(task, db) |
333 | + maybe(err) |
334 | + maybe(runner.Run()) |
335 | } |
336 | } |
337 | } |
338 | diff --git a/task.go b/task.go |
339 | index 685717c..c3f8bfe 100644 |
340 | --- a/task.go |
341 | +++ b/task.go |
342 | @@ -3,10 +3,98 @@ package main |
343 | import ( |
344 | "fmt" |
345 | "github.com/gosuri/uilive" |
346 | - "io" |
347 | "time" |
348 | ) |
349 | |
350 | + type TaskRunner struct { |
351 | + count int |
352 | + task *Task |
353 | + store *Store |
354 | + writer *uilive.Writer |
355 | + timer *time.Timer |
356 | + ticker *time.Ticker |
357 | + notifier Notifier |
358 | + } |
359 | + |
360 | + func NewTaskRunner(task *Task, store *Store) (*TaskRunner, error) { |
361 | + taskID, err := store.CreateTask(*task) |
362 | + if err != nil { |
363 | + return nil, err |
364 | + } |
365 | + task.ID = taskID |
366 | + tr := &TaskRunner{ |
367 | + task: task, |
368 | + store: store, |
369 | + notifier: NewLibNotifier(), |
370 | + writer: uilive.New(), |
371 | + timer: time.NewTimer(task.Duration), |
372 | + ticker: time.NewTicker(RefreshInterval), |
373 | + } |
374 | + tr.writer.Start() |
375 | + return tr, nil |
376 | + } |
377 | + |
378 | + func (t *TaskRunner) Run() error { |
379 | + for t.count < t.task.NPomodoros { |
380 | + // ASCII spinner |
381 | + wheel := &Wheel{} |
382 | + // This pomodoro |
383 | + pomodoro := &Pomodoro{} |
384 | + prompt("press enter to begin") |
385 | + // Emit a desktop notification |
386 | + // that the task is beginning. |
387 | + t.notifier.Begin(t.count, *t.task) |
388 | + // Record task as started |
389 | + pomodoro.Start = time.Now() |
390 | + // Reset the timer |
391 | + t.timer.Reset(t.task.Duration) |
392 | + // Wait for either a tick to update |
393 | + // the UI for the timer to complete |
394 | + loop: |
395 | + select { |
396 | + case <-t.ticker.C: |
397 | + t.updateUI(Message{ |
398 | + Start: pomodoro.Start, |
399 | + Duration: t.task.Duration, |
400 | + Pomodoros: t.task.NPomodoros, |
401 | + Wheel: wheel, |
402 | + CurrentPomodoro: t.count, |
403 | + }) |
404 | + goto loop |
405 | + case <-t.timer.C: |
406 | + // Send a notification for the |
407 | + // user to take a break. We record |
408 | + // how long it actually takes for |
409 | + // them to initiate the break. |
410 | + t.notifier.Break(*t.task) |
411 | + prompt("press enter to take a break") |
412 | + // Record the task as complete |
413 | + pomodoro.End = time.Now() |
414 | + // Record the session in the db |
415 | + err := t.store.CreatePomodoro(t.task.ID, *pomodoro) |
416 | + if err != nil { |
417 | + return err |
418 | + } |
419 | + // Increment the count of completed pomodoros |
420 | + t.count++ |
421 | + } |
422 | + } |
423 | + return nil |
424 | + } |
425 | + |
426 | + func (t *TaskRunner) updateUI(msg Message) { |
427 | + fmt.Fprintf( |
428 | + t.writer, |
429 | + "%s %s remaining [ pomodoro %d/%d ]\n", |
430 | + msg.Wheel, |
431 | + (msg.Duration - time.Since(msg.Start)).Truncate(time.Second), |
432 | + msg.CurrentPomodoro, |
433 | + msg.Pomodoros, |
434 | + ) |
435 | + } |
436 | + |
437 | + /* |
438 | + |
439 | // Task Starting.. |
440 | // |
441 | // 20min remaning [pomodoro 1/4] |
442 | @@ -25,7 +113,7 @@ func display(writer io.Writer, msg Message) { |
443 | ) |
444 | } |
445 | |
446 | - func run(task Task, prompter Prompter, db *Store) { |
447 | + func run(task Task, notifier Notifier, db *Store) { |
448 | taskID, err := db.CreateTask(task) |
449 | maybe(err) |
450 | writer := uilive.New() |
451 | @@ -36,7 +124,7 @@ func run(task Task, prompter Prompter, db *Store) { |
452 | var p int |
453 | for p < task.NPomodoros { |
454 | pomodoro := &Pomodoro{} |
455 | - maybe(prompter.Prompt("Begin working!")) |
456 | + maybe(notifier.Begin(task)) |
457 | pomodoro.Start = time.Now() |
458 | timer.Reset(task.Duration) |
459 | loop: |
460 | @@ -51,11 +139,14 @@ func run(task Task, prompter Prompter, db *Store) { |
461 | }) |
462 | goto loop |
463 | case <-timer.C: |
464 | - maybe(prompter.Prompt("Take a break!")) |
465 | + maybe(notifier.Break(task)) |
466 | + fmt.Println("press enter to take a break") |
467 | pomodoro.End = time.Now() |
468 | maybe(db.CreatePomodoro(taskID, *pomodoro)) |
469 | p++ |
470 | } |
471 | } |
472 | + maybe(notifier.Finish(task)) |
473 | writer.Stop() |
474 | } |
475 | + */ |
476 | diff --git a/tomato-icon.png b/tomato-icon.png |
477 | new file mode 100644 |
478 | index 0000000..a4ed47e |
479 | Binary files /dev/null and b/tomato-icon.png differ |
480 | diff --git a/types.go b/types.go |
481 | index 60c2961..6fdbf84 100644 |
482 | --- a/types.go |
483 | +++ b/types.go |
484 | @@ -5,8 +5,9 @@ import ( |
485 | "fmt" |
486 | "github.com/fatih/color" |
487 | "io/ioutil" |
488 | - "os/exec" |
489 | "time" |
490 | + |
491 | + "github.com/kevinschoon/pomo/libnotify" |
492 | ) |
493 | |
494 | // RefreshInterval is the frequency at which |
495 | @@ -115,22 +116,57 @@ type Pomodoro struct { |
496 | End time.Time `json:"end"` |
497 | } |
498 | |
499 | - // Prompter prompts a user with a message. |
500 | - type Prompter interface { |
501 | - Prompt(string) error |
502 | + // Notifier implements a system specific |
503 | + // notification. On Linux this libnotify. |
504 | + // TODO: OSX, Windows(?) |
505 | + type Notifier interface { |
506 | + Begin(int, Task) error |
507 | + Break(Task) error |
508 | + Finish(Task) error |
509 | } |
510 | |
511 | - // I3 implements a prompter for i3 |
512 | - type I3 struct{} |
513 | + // LibNotifier implements a Linux |
514 | + // notifier client. |
515 | + type LibNotifier struct { |
516 | + client *libnotify.Client |
517 | + iconPath string |
518 | + } |
519 | |
520 | - func (i *I3) Prompt(message string) error { |
521 | - _, err := exec.Command( |
522 | - "/bin/i3-nagbar", |
523 | - "-m", |
524 | - message, |
525 | - ).Output() |
526 | - if err != nil { |
527 | - return err |
528 | + func NewLibNotifier() Notifier { |
529 | + ln := &LibNotifier{ |
530 | + client: libnotify.NewClient(), |
531 | } |
532 | - return nil |
533 | + // Write the tomato icon to a temp path |
534 | + raw := MustAsset("tomato-icon.png") |
535 | + fp, _ := ioutil.TempFile("", "pomo") |
536 | + fp.Write(raw) |
537 | + ln.iconPath = fp.Name() |
538 | + fp.Close() |
539 | + return ln |
540 | + } |
541 | + |
542 | + func (ln LibNotifier) Begin(count int, t Task) error { |
543 | + return ln.client.Notify(libnotify.Notification{ |
544 | + Title: t.Message, |
545 | + Body: fmt.Sprintf("Task is starting (%d/%d pomodoros)", count, t.NPomodoros), |
546 | + Icon: ln.iconPath, |
547 | + }) |
548 | + } |
549 | + |
550 | + func (ln LibNotifier) Break(t Task) error { |
551 | + return ln.client.Notify(libnotify.Notification{ |
552 | + Title: t.Message, |
553 | + Urgency: "critical", |
554 | + Body: fmt.Sprintf("Time to take a break!\nPress enter at the console to initiate the break."), |
555 | + Icon: ln.iconPath, |
556 | + }) |
557 | + } |
558 | + |
559 | + func (ln LibNotifier) Finish(t Task) error { |
560 | + return ln.client.Notify(libnotify.Notification{ |
561 | + Title: t.Message, |
562 | + Urgency: "critical", |
563 | + Body: fmt.Sprintf("This task session is complete!"), |
564 | + Icon: ln.iconPath, |
565 | + }) |
566 | } |
567 | diff --git a/util.go b/util.go |
568 | index 99aa249..6ff64eb 100644 |
569 | --- a/util.go |
570 | +++ b/util.go |
571 | @@ -1,6 +1,7 @@ |
572 | package main |
573 | |
574 | import ( |
575 | + "bufio" |
576 | "fmt" |
577 | "github.com/fatih/color" |
578 | "os" |
579 | @@ -21,6 +22,12 @@ func defaultConfigPath() string { |
580 | return u.HomeDir + "/.pomo" |
581 | } |
582 | |
583 | + func prompt(text string) { |
584 | + reader := bufio.NewReader(os.Stdin) |
585 | + fmt.Println(text) |
586 | + reader.ReadString('\n') |
587 | + } |
588 | + |
589 | func summerizeTasks(config *Config, tasks []*Task) { |
590 | for _, task := range tasks { |
591 | fmt.Printf("%d: [%s] ", task.ID, task.Duration.Truncate(time.Second)) |