Commit
+1447 -1552 +/-26 browse
1 | diff --git a/Gopkg.lock b/Gopkg.lock |
2 | deleted file mode 100644 |
3 | index 23d71b1..0000000 |
4 | --- a/Gopkg.lock |
5 | +++ /dev/null |
6 | @@ -1,90 +0,0 @@ |
7 | - # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. |
8 | - |
9 | - |
10 | - [[projects]] |
11 | - branch = "master" |
12 | - name = "github.com/0xAX/notificator" |
13 | - packages = ["."] |
14 | - revision = "d81462e38c2145023f9ecf5414fc84d45d5bfe82" |
15 | - |
16 | - [[projects]] |
17 | - name = "github.com/fatih/color" |
18 | - packages = ["."] |
19 | - revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" |
20 | - version = "v1.7.0" |
21 | - |
22 | - [[projects]] |
23 | - name = "github.com/gizak/termui" |
24 | - packages = ["."] |
25 | - revision = "bf53c5cbea3f372f745e1d8e4660e29f73d41517" |
26 | - version = "v2.3.0" |
27 | - |
28 | - [[projects]] |
29 | - name = "github.com/jawher/mow.cli" |
30 | - packages = [ |
31 | - ".", |
32 | - "internal/container", |
33 | - "internal/flow", |
34 | - "internal/fsm", |
35 | - "internal/lexer", |
36 | - "internal/matcher", |
37 | - "internal/parser", |
38 | - "internal/values" |
39 | - ] |
40 | - revision = "2f22195f169da29d54624afd9eb83ada5c9e4ee9" |
41 | - version = "v1.0.4" |
42 | - |
43 | - [[projects]] |
44 | - branch = "master" |
45 | - name = "github.com/maruel/panicparse" |
46 | - packages = ["stack"] |
47 | - revision = "f20d4c4d746f810c9110e21928d4135e1f2a3efa" |
48 | - |
49 | - [[projects]] |
50 | - name = "github.com/mattn/go-colorable" |
51 | - packages = ["."] |
52 | - revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" |
53 | - version = "v0.0.9" |
54 | - |
55 | - [[projects]] |
56 | - name = "github.com/mattn/go-isatty" |
57 | - packages = ["."] |
58 | - revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" |
59 | - version = "v0.0.4" |
60 | - |
61 | - [[projects]] |
62 | - name = "github.com/mattn/go-runewidth" |
63 | - packages = ["."] |
64 | - revision = "3ee7d812e62a0804a7d0a324e0249ca2db3476d3" |
65 | - version = "v0.0.4" |
66 | - |
67 | - [[projects]] |
68 | - name = "github.com/mattn/go-sqlite3" |
69 | - packages = ["."] |
70 | - revision = "c7c4067b79cc51e6dfdcef5c702e74b1e0fa7c75" |
71 | - version = "v1.10.0" |
72 | - |
73 | - [[projects]] |
74 | - name = "github.com/mitchellh/go-wordwrap" |
75 | - packages = ["."] |
76 | - revision = "9e67c67572bc5dd02aef930e2b0ae3c02a4b5a5c" |
77 | - version = "v1.0.0" |
78 | - |
79 | - [[projects]] |
80 | - branch = "master" |
81 | - name = "github.com/nsf/termbox-go" |
82 | - packages = ["."] |
83 | - revision = "0938b5187e61bb8c4dcac2b0a9cf4047d83784fc" |
84 | - |
85 | - [[projects]] |
86 | - branch = "master" |
87 | - name = "golang.org/x/sys" |
88 | - packages = ["unix"] |
89 | - revision = "11f53e03133963fb11ae0588e08b5e0b85be8be5" |
90 | - |
91 | - [solve-meta] |
92 | - analyzer-name = "dep" |
93 | - analyzer-version = 1 |
94 | - inputs-digest = "5913a16a0927350ebbd5117158473bb0252181f6aa8777cb5a095a18dee8bd40" |
95 | - solver-name = "gps-cdcl" |
96 | - solver-version = 1 |
97 | diff --git a/Gopkg.toml b/Gopkg.toml |
98 | deleted file mode 100644 |
99 | index 329330b..0000000 |
100 | --- a/Gopkg.toml |
101 | +++ /dev/null |
102 | @@ -1,33 +0,0 @@ |
103 | - # Gopkg.toml example |
104 | - # |
105 | - # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md |
106 | - # for detailed Gopkg.toml documentation. |
107 | - # |
108 | - # required = ["github.com/user/thing/cmd/thing"] |
109 | - # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] |
110 | - # |
111 | - # [[constraint]] |
112 | - # name = "github.com/user/project" |
113 | - # version = "1.0.0" |
114 | - # |
115 | - # [[constraint]] |
116 | - # name = "github.com/user/project2" |
117 | - # branch = "dev" |
118 | - # source = "github.com/myfork/project2" |
119 | - # |
120 | - # [[override]] |
121 | - # name = "github.com/x/y" |
122 | - # version = "2.4.0" |
123 | - |
124 | - |
125 | - [[constraint]] |
126 | - name = "github.com/fatih/color" |
127 | - version = "1.5.0" |
128 | - |
129 | - [[constraint]] |
130 | - name = "github.com/jawher/mow.cli" |
131 | - version = "1.0.3" |
132 | - |
133 | - [[constraint]] |
134 | - name = "github.com/mattn/go-sqlite3" |
135 | - version = "1.6.0" |
136 | diff --git a/Makefile b/Makefile |
137 | index 9505092..eb7a2e5 100644 |
138 | --- a/Makefile |
139 | +++ b/Makefile |
140 | @@ -1,10 +1,14 @@ |
141 | DOCKER_CMD=docker run --rm -ti -w /build/pomo -v $$PWD:/build/pomo |
142 | DOCKER_IMAGE=pomo-build |
143 | + |
144 | VERSION ?= $(shell git describe --tags 2>/dev/null) |
145 | ifeq "$(VERSION)" "" |
146 | VERSION := UNKNOWN |
147 | endif |
148 | |
149 | + LDFLAGS=\ |
150 | + -X github.com/kevinschoon/pomo/pkg/internal/version.Version=$(VERSION) |
151 | + |
152 | .PHONY: \ |
153 | test \ |
154 | docs \ |
155 | @@ -14,11 +18,16 @@ endif |
156 | release-linux \ |
157 | release-darwin |
158 | |
159 | + default: |
160 | + cd cmd/pomo && \ |
161 | + go install -ldflags '${LDFLAGS}' |
162 | + |
163 | bin/pomo: test |
164 | - go build -o $@ |
165 | + cd cmd/pomo && \ |
166 | + go build -ldflags '${LDFLAGS}' -o ../../$@ |
167 | |
168 | - bindata.go: tomato-icon.png |
169 | - go-bindata -pkg main -o $@ $^ |
170 | + #bindata.go: tomato-icon.png |
171 | + # go-bindata -pkg main -o $@ $^ |
172 | |
173 | test: |
174 | go test ./... |
175 | @@ -31,16 +40,16 @@ bin/pomo-linux: bin/pomo-$(VERSION)-linux-amd64 |
176 | |
177 | bin/pomo-darwin: bin/pomo-$(VERSION)-darwin-amd64 |
178 | |
179 | - bin/pomo-$(VERSION)-linux-amd64: bin bindata.go |
180 | - $(DOCKER_CMD) --env GOOS=linux --env GOARCH=amd64 $(DOCKER_IMAGE) go build -ldflags "-X main.Version=$(VERSION)" -o $@ |
181 | + bin/pomo-$(VERSION)-linux-amd64: bin |
182 | + $(DOCKER_CMD) --env GOOS=linux --env GOARCH=amd64 $(DOCKER_IMAGE) go build -ldflags "${LDFLAGS}" -o $@ |
183 | |
184 | bin/pomo-$(VERSION)-linux-amd64.md5: |
185 | md5sum bin/pomo-$(VERSION)-linux-amd64 | sed -e 's/bin\///' > $@ |
186 | |
187 | - bin/pomo-$(VERSION)-darwin-amd64: bin bindata.go |
188 | + bin/pomo-$(VERSION)-darwin-amd64: bin |
189 | # This is used to cross-compile a Darwin compatible Mach-O executable |
190 | # on Linux for OSX, you need to install https://github.com/tpoechtrager/osxcross |
191 | - $(DOCKER_CMD) --env GOOS=darwin --env GOARCH=amd64 --env CC=x86_64-apple-darwin15-cc --env CGO_ENABLED=1 $(DOCKER_IMAGE) go build -ldflags "-X main.Version=$(VERSION)" -o $@ |
192 | + $(DOCKER_CMD) --env GOOS=darwin --env GOARCH=amd64 --env CC=x86_64-apple-darwin15-cc --env CGO_ENABLED=1 $(DOCKER_IMAGE) go build -ldflags "${LDFLAGS}" -o $@ |
193 | |
194 | |
195 | bin/pomo-$(VERSION)-darwin-amd64.md5: |
196 | diff --git a/bindata.go b/bindata.go |
197 | deleted file mode 100644 |
198 | index 8f4bcd9..0000000 |
199 | --- a/bindata.go |
200 | +++ /dev/null |
201 | @@ -1,235 +0,0 @@ |
202 | - // Code generated by go-bindata. |
203 | - // sources: |
204 | - // tomato-icon.png |
205 | - // DO NOT EDIT! |
206 | - |
207 | - package main |
208 | - |
209 | - import ( |
210 | - "bytes" |
211 | - "compress/gzip" |
212 | - "fmt" |
213 | - "io" |
214 | - "io/ioutil" |
215 | - "os" |
216 | - "path/filepath" |
217 | - "strings" |
218 | - "time" |
219 | - ) |
220 | - |
221 | - func bindataRead(data []byte, name string) ([]byte, error) { |
222 | - gz, err := gzip.NewReader(bytes.NewBuffer(data)) |
223 | - if err != nil { |
224 | - return nil, fmt.Errorf("Read %q: %v", name, err) |
225 | - } |
226 | - |
227 | - var buf bytes.Buffer |
228 | - _, err = io.Copy(&buf, gz) |
229 | - clErr := gz.Close() |
230 | - |
231 | - if err != nil { |
232 | - return nil, fmt.Errorf("Read %q: %v", name, err) |
233 | - } |
234 | - if clErr != nil { |
235 | - return nil, err |
236 | - } |
237 | - |
238 | - return buf.Bytes(), nil |
239 | - } |
240 | - |
241 | - type asset struct { |
242 | - bytes []byte |
243 | - info os.FileInfo |
244 | - } |
245 | - |
246 | - type bindataFileInfo struct { |
247 | - name string |
248 | - size int64 |
249 | - mode os.FileMode |
250 | - modTime time.Time |
251 | - } |
252 | - |
253 | - func (fi bindataFileInfo) Name() string { |
254 | - return fi.name |
255 | - } |
256 | - func (fi bindataFileInfo) Size() int64 { |
257 | - return fi.size |
258 | - } |
259 | - func (fi bindataFileInfo) Mode() os.FileMode { |
260 | - return fi.mode |
261 | - } |
262 | - func (fi bindataFileInfo) ModTime() time.Time { |
263 | - return fi.modTime |
264 | - } |
265 | - func (fi bindataFileInfo) IsDir() bool { |
266 | - return false |
267 | - } |
268 | - func (fi bindataFileInfo) Sys() interface{} { |
269 | - return nil |
270 | - } |
271 | - |
272 | - 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") |
273 | - |
274 | - func tomatoIconPngBytes() ([]byte, error) { |
275 | - return bindataRead( |
276 | - _tomatoIconPng, |
277 | - "tomato-icon.png", |
278 | - ) |
279 | - } |
280 | - |
281 | - func tomatoIconPng() (*asset, error) { |
282 | - bytes, err := tomatoIconPngBytes() |
283 | - if err != nil { |
284 | - return nil, err |
285 | - } |
286 | - |
287 | - info := bindataFileInfo{name: "tomato-icon.png", size: 3932, mode: os.FileMode(420), modTime: time.Unix(1516522577, 0)} |
288 | - a := &asset{bytes: bytes, info: info} |
289 | - return a, nil |
290 | - } |
291 | - |
292 | - // Asset loads and returns the asset for the given name. |
293 | - // It returns an error if the asset could not be found or |
294 | - // could not be loaded. |
295 | - func Asset(name string) ([]byte, error) { |
296 | - cannonicalName := strings.Replace(name, "\\", "/", -1) |
297 | - if f, ok := _bindata[cannonicalName]; ok { |
298 | - a, err := f() |
299 | - if err != nil { |
300 | - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) |
301 | - } |
302 | - return a.bytes, nil |
303 | - } |
304 | - return nil, fmt.Errorf("Asset %s not found", name) |
305 | - } |
306 | - |
307 | - // MustAsset is like Asset but panics when Asset would return an error. |
308 | - // It simplifies safe initialization of global variables. |
309 | - func MustAsset(name string) []byte { |
310 | - a, err := Asset(name) |
311 | - if err != nil { |
312 | - panic("asset: Asset(" + name + "): " + err.Error()) |
313 | - } |
314 | - |
315 | - return a |
316 | - } |
317 | - |
318 | - // AssetInfo loads and returns the asset info for the given name. |
319 | - // It returns an error if the asset could not be found or |
320 | - // could not be loaded. |
321 | - func AssetInfo(name string) (os.FileInfo, error) { |
322 | - cannonicalName := strings.Replace(name, "\\", "/", -1) |
323 | - if f, ok := _bindata[cannonicalName]; ok { |
324 | - a, err := f() |
325 | - if err != nil { |
326 | - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) |
327 | - } |
328 | - return a.info, nil |
329 | - } |
330 | - return nil, fmt.Errorf("AssetInfo %s not found", name) |
331 | - } |
332 | - |
333 | - // AssetNames returns the names of the assets. |
334 | - func AssetNames() []string { |
335 | - names := make([]string, 0, len(_bindata)) |
336 | - for name := range _bindata { |
337 | - names = append(names, name) |
338 | - } |
339 | - return names |
340 | - } |
341 | - |
342 | - // _bindata is a table, holding each asset generator, mapped to its name. |
343 | - var _bindata = map[string]func() (*asset, error){ |
344 | - "tomato-icon.png": tomatoIconPng, |
345 | - } |
346 | - |
347 | - // AssetDir returns the file names below a certain |
348 | - // directory embedded in the file by go-bindata. |
349 | - // For example if you run go-bindata on data/... and data contains the |
350 | - // following hierarchy: |
351 | - // data/ |
352 | - // foo.txt |
353 | - // img/ |
354 | - // a.png |
355 | - // b.png |
356 | - // then AssetDir("data") would return []string{"foo.txt", "img"} |
357 | - // AssetDir("data/img") would return []string{"a.png", "b.png"} |
358 | - // AssetDir("foo.txt") and AssetDir("notexist") would return an error |
359 | - // AssetDir("") will return []string{"data"}. |
360 | - func AssetDir(name string) ([]string, error) { |
361 | - node := _bintree |
362 | - if len(name) != 0 { |
363 | - cannonicalName := strings.Replace(name, "\\", "/", -1) |
364 | - pathList := strings.Split(cannonicalName, "/") |
365 | - for _, p := range pathList { |
366 | - node = node.Children[p] |
367 | - if node == nil { |
368 | - return nil, fmt.Errorf("Asset %s not found", name) |
369 | - } |
370 | - } |
371 | - } |
372 | - if node.Func != nil { |
373 | - return nil, fmt.Errorf("Asset %s not found", name) |
374 | - } |
375 | - rv := make([]string, 0, len(node.Children)) |
376 | - for childName := range node.Children { |
377 | - rv = append(rv, childName) |
378 | - } |
379 | - return rv, nil |
380 | - } |
381 | - |
382 | - type bintree struct { |
383 | - Func func() (*asset, error) |
384 | - Children map[string]*bintree |
385 | - } |
386 | - var _bintree = &bintree{nil, map[string]*bintree{ |
387 | - "tomato-icon.png": &bintree{tomatoIconPng, map[string]*bintree{}}, |
388 | - }} |
389 | - |
390 | - // RestoreAsset restores an asset under the given directory |
391 | - func RestoreAsset(dir, name string) error { |
392 | - data, err := Asset(name) |
393 | - if err != nil { |
394 | - return err |
395 | - } |
396 | - info, err := AssetInfo(name) |
397 | - if err != nil { |
398 | - return err |
399 | - } |
400 | - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) |
401 | - if err != nil { |
402 | - return err |
403 | - } |
404 | - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) |
405 | - if err != nil { |
406 | - return err |
407 | - } |
408 | - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) |
409 | - if err != nil { |
410 | - return err |
411 | - } |
412 | - return nil |
413 | - } |
414 | - |
415 | - // RestoreAssets restores an asset under the given directory recursively |
416 | - func RestoreAssets(dir, name string) error { |
417 | - children, err := AssetDir(name) |
418 | - // File |
419 | - if err != nil { |
420 | - return RestoreAsset(dir, name) |
421 | - } |
422 | - // Dir |
423 | - for _, child := range children { |
424 | - err = RestoreAssets(dir, filepath.Join(name, child)) |
425 | - if err != nil { |
426 | - return err |
427 | - } |
428 | - } |
429 | - return nil |
430 | - } |
431 | - |
432 | - func _filePath(dir, name string) string { |
433 | - cannonicalName := strings.Replace(name, "\\", "/", -1) |
434 | - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) |
435 | - } |
436 | - |
437 | diff --git a/cmd/pomo/main.go b/cmd/pomo/main.go |
438 | new file mode 100644 |
439 | index 0000000..d8aa4d2 |
440 | --- /dev/null |
441 | +++ b/cmd/pomo/main.go |
442 | @@ -0,0 +1,7 @@ |
443 | + package main |
444 | + |
445 | + import "github.com/kevinschoon/pomo/pkg/cmd" |
446 | + |
447 | + func main() { |
448 | + cmd.Run() |
449 | + } |
450 | diff --git a/config.go b/config.go |
451 | deleted file mode 100644 |
452 | index ec68006..0000000 |
453 | --- a/config.go |
454 | +++ /dev/null |
455 | @@ -1,114 +0,0 @@ |
456 | - package main |
457 | - |
458 | - import ( |
459 | - "encoding/json" |
460 | - "io/ioutil" |
461 | - "os" |
462 | - "path" |
463 | - |
464 | - "github.com/fatih/color" |
465 | - ) |
466 | - |
467 | - const ( |
468 | - defaultDateTimeFmt = "2006-01-02 15:04" |
469 | - ) |
470 | - |
471 | - // Config represents user preferences |
472 | - type Config struct { |
473 | - Colors *ColorMap `json:"colors"` |
474 | - DateTimeFmt string `json:"dateTimeFmt"` |
475 | - BasePath string `json:"basePath"` |
476 | - DBPath string `json:"dbPath"` |
477 | - SocketPath string `json:"socketPath"` |
478 | - IconPath string `json:"iconPath"` |
479 | - } |
480 | - |
481 | - type ColorMap struct { |
482 | - colors map[string]*color.Color |
483 | - tags map[string]string |
484 | - } |
485 | - |
486 | - func (c *ColorMap) Get(name string) *color.Color { |
487 | - if color, ok := c.colors[name]; ok { |
488 | - return color |
489 | - } |
490 | - return nil |
491 | - } |
492 | - |
493 | - func (c *ColorMap) MarshalJSON() ([]byte, error) { |
494 | - return json.Marshal(c.tags) |
495 | - } |
496 | - |
497 | - func (c *ColorMap) UnmarshalJSON(raw []byte) error { |
498 | - lookup := map[string]*color.Color{ |
499 | - "black": color.New(color.FgBlack), |
500 | - "hiblack": color.New(color.FgHiBlack), |
501 | - "blue": color.New(color.FgBlue), |
502 | - "hiblue": color.New(color.FgHiBlue), |
503 | - "cyan": color.New(color.FgCyan), |
504 | - "hicyan": color.New(color.FgHiCyan), |
505 | - "green": color.New(color.FgGreen), |
506 | - "higreen": color.New(color.FgHiGreen), |
507 | - "magenta": color.New(color.FgMagenta), |
508 | - "himagenta": color.New(color.FgHiMagenta), |
509 | - "red": color.New(color.FgRed), |
510 | - "hired": color.New(color.FgHiRed), |
511 | - "white": color.New(color.FgWhite), |
512 | - "hiwrite": color.New(color.FgHiWhite), |
513 | - "yellow": color.New(color.FgYellow), |
514 | - "hiyellow": color.New(color.FgHiYellow), |
515 | - } |
516 | - cm := &ColorMap{ |
517 | - colors: map[string]*color.Color{}, |
518 | - tags: map[string]string{}, |
519 | - } |
520 | - err := json.Unmarshal(raw, &cm.tags) |
521 | - if err != nil { |
522 | - return err |
523 | - } |
524 | - for tag, colorName := range cm.tags { |
525 | - if color, ok := lookup[colorName]; ok { |
526 | - cm.colors[tag] = color |
527 | - } |
528 | - } |
529 | - *c = *cm |
530 | - return nil |
531 | - } |
532 | - |
533 | - func LoadConfig(configPath string, config *Config) error { |
534 | - raw, err := ioutil.ReadFile(configPath) |
535 | - if err != nil { |
536 | - os.MkdirAll(path.Dir(configPath), 0755) |
537 | - // Create an empty config file |
538 | - // if it does not already exist. |
539 | - if os.IsNotExist(err) { |
540 | - raw, _ := json.Marshal(map[string]string{}) |
541 | - err := ioutil.WriteFile(configPath, raw, 0644) |
542 | - if err != nil { |
543 | - return err |
544 | - } |
545 | - return LoadConfig(configPath, config) |
546 | - } |
547 | - return err |
548 | - } |
549 | - err = json.Unmarshal(raw, config) |
550 | - if err != nil { |
551 | - return err |
552 | - } |
553 | - if config.DateTimeFmt == "" { |
554 | - config.DateTimeFmt = defaultDateTimeFmt |
555 | - } |
556 | - if config.BasePath == "" { |
557 | - config.BasePath = path.Dir(configPath) |
558 | - } |
559 | - if config.DBPath == "" { |
560 | - config.DBPath = path.Join(config.BasePath, "/pomo.db") |
561 | - } |
562 | - if config.SocketPath == "" { |
563 | - config.SocketPath = path.Join(config.BasePath, "/pomo.sock") |
564 | - } |
565 | - if config.IconPath == "" { |
566 | - config.IconPath = path.Join(config.BasePath, "/icon.png") |
567 | - } |
568 | - return nil |
569 | - } |
570 | diff --git a/main.go b/main.go |
571 | deleted file mode 100644 |
572 | index e8675d4..0000000 |
573 | --- a/main.go |
574 | +++ /dev/null |
575 | @@ -1,239 +0,0 @@ |
576 | - package main |
577 | - |
578 | - import ( |
579 | - "database/sql" |
580 | - "encoding/json" |
581 | - "fmt" |
582 | - "os" |
583 | - "sort" |
584 | - "time" |
585 | - |
586 | - cli "github.com/jawher/mow.cli" |
587 | - ) |
588 | - |
589 | - func start(config *Config) func(*cli.Cmd) { |
590 | - return func(cmd *cli.Cmd) { |
591 | - cmd.Spec = "[OPTIONS] MESSAGE" |
592 | - var ( |
593 | - duration = cmd.StringOpt("d duration", "25m", "duration of each stent") |
594 | - pomodoros = cmd.IntOpt("p pomodoros", 4, "number of pomodoros") |
595 | - message = cmd.StringArg("MESSAGE", "", "descriptive name of the given task") |
596 | - tags = cmd.StringsOpt("t tag", []string{}, "tags associated with this task") |
597 | - ) |
598 | - cmd.Action = func() { |
599 | - parsed, err := time.ParseDuration(*duration) |
600 | - maybe(err) |
601 | - db, err := NewStore(config.DBPath) |
602 | - maybe(err) |
603 | - defer db.Close() |
604 | - task := &Task{ |
605 | - Message: *message, |
606 | - Tags: *tags, |
607 | - NPomodoros: *pomodoros, |
608 | - Duration: parsed, |
609 | - } |
610 | - maybe(db.With(func(tx *sql.Tx) error { |
611 | - id, err := db.CreateTask(tx, *task) |
612 | - if err != nil { |
613 | - return err |
614 | - } |
615 | - task.ID = id |
616 | - return nil |
617 | - })) |
618 | - runner, err := NewTaskRunner(task, config) |
619 | - maybe(err) |
620 | - server, err := NewServer(runner, config) |
621 | - maybe(err) |
622 | - server.Start() |
623 | - defer server.Stop() |
624 | - runner.Start() |
625 | - startUI(runner) |
626 | - } |
627 | - } |
628 | - } |
629 | - |
630 | - func create(config *Config) func(*cli.Cmd) { |
631 | - return func(cmd *cli.Cmd) { |
632 | - cmd.Spec = "[OPTIONS] MESSAGE" |
633 | - var ( |
634 | - duration = cmd.StringOpt("d duration", "25m", "duration of each stent") |
635 | - pomodoros = cmd.IntOpt("p pomodoros", 4, "number of pomodoros") |
636 | - message = cmd.StringArg("MESSAGE", "", "descriptive name of the given task") |
637 | - tags = cmd.StringsOpt("t tag", []string{}, "tags associated with this task") |
638 | - ) |
639 | - cmd.Action = func() { |
640 | - parsed, err := time.ParseDuration(*duration) |
641 | - maybe(err) |
642 | - db, err := NewStore(config.DBPath) |
643 | - maybe(err) |
644 | - defer db.Close() |
645 | - task := &Task{ |
646 | - Message: *message, |
647 | - Tags: *tags, |
648 | - NPomodoros: *pomodoros, |
649 | - Duration: parsed, |
650 | - } |
651 | - maybe(db.With(func(tx *sql.Tx) error { |
652 | - taskId, err := db.CreateTask(tx, *task) |
653 | - if err != nil { |
654 | - return err |
655 | - } |
656 | - fmt.Println(taskId) |
657 | - return nil |
658 | - })) |
659 | - } |
660 | - } |
661 | - } |
662 | - |
663 | - func begin(config *Config) func(*cli.Cmd) { |
664 | - return func(cmd *cli.Cmd) { |
665 | - cmd.Spec = "[OPTIONS] TASK_ID" |
666 | - var ( |
667 | - taskId = cmd.IntArg("TASK_ID", -1, "ID of Pomodoro to begin") |
668 | - ) |
669 | - |
670 | - cmd.Action = func() { |
671 | - db, err := NewStore(config.DBPath) |
672 | - maybe(err) |
673 | - defer db.Close() |
674 | - var task *Task |
675 | - maybe(db.With(func(tx *sql.Tx) error { |
676 | - read, err := db.ReadTask(tx, *taskId) |
677 | - if err != nil { |
678 | - return err |
679 | - } |
680 | - task = read |
681 | - err = db.DeletePomodoros(tx, *taskId) |
682 | - if err != nil { |
683 | - return err |
684 | - } |
685 | - task.Pomodoros = []*Pomodoro{} |
686 | - return nil |
687 | - })) |
688 | - runner, err := NewTaskRunner(task, config) |
689 | - maybe(err) |
690 | - server, err := NewServer(runner, config) |
691 | - maybe(err) |
692 | - server.Start() |
693 | - defer server.Stop() |
694 | - runner.Start() |
695 | - startUI(runner) |
696 | - } |
697 | - } |
698 | - } |
699 | - |
700 | - func initialize(config *Config) func(*cli.Cmd) { |
701 | - return func(cmd *cli.Cmd) { |
702 | - cmd.Spec = "[OPTIONS]" |
703 | - cmd.Action = func() { |
704 | - db, err := NewStore(config.DBPath) |
705 | - maybe(err) |
706 | - defer db.Close() |
707 | - maybe(initDB(db)) |
708 | - } |
709 | - } |
710 | - } |
711 | - |
712 | - func list(config *Config) func(*cli.Cmd) { |
713 | - return func(cmd *cli.Cmd) { |
714 | - cmd.Spec = "[OPTIONS]" |
715 | - var ( |
716 | - asJSON = cmd.BoolOpt("json", false, "output task history as JSON") |
717 | - assend = cmd.BoolOpt("assend", false, "sort tasks assending in age") |
718 | - all = cmd.BoolOpt("a all", true, "output all tasks") |
719 | - limit = cmd.IntOpt("n limit", 0, "limit the number of results by n") |
720 | - duration = cmd.StringOpt("d duration", "24h", "show tasks within this duration") |
721 | - ) |
722 | - cmd.Action = func() { |
723 | - duration, err := time.ParseDuration(*duration) |
724 | - maybe(err) |
725 | - db, err := NewStore(config.DBPath) |
726 | - maybe(err) |
727 | - defer db.Close() |
728 | - maybe(db.With(func(tx *sql.Tx) error { |
729 | - tasks, err := db.ReadTasks(tx) |
730 | - maybe(err) |
731 | - if *assend { |
732 | - sort.Sort(sort.Reverse(ByID(tasks))) |
733 | - } |
734 | - if !*all { |
735 | - tasks = After(time.Now().Add(-duration), tasks) |
736 | - } |
737 | - if *limit > 0 && (len(tasks) > *limit) { |
738 | - tasks = tasks[0:*limit] |
739 | - } |
740 | - if *asJSON { |
741 | - maybe(json.NewEncoder(os.Stdout).Encode(tasks)) |
742 | - return nil |
743 | - } |
744 | - maybe(err) |
745 | - summerizeTasks(config, tasks) |
746 | - return nil |
747 | - })) |
748 | - } |
749 | - } |
750 | - } |
751 | - |
752 | - func _delete(config *Config) func(*cli.Cmd) { |
753 | - return func(cmd *cli.Cmd) { |
754 | - cmd.Spec = "[OPTIONS] TASK_ID" |
755 | - var taskID = cmd.IntArg("TASK_ID", -1, "task to delete") |
756 | - cmd.Action = func() { |
757 | - db, err := NewStore(config.DBPath) |
758 | - maybe(err) |
759 | - defer db.Close() |
760 | - maybe(db.With(func(tx *sql.Tx) error { |
761 | - return db.DeleteTask(tx, *taskID) |
762 | - })) |
763 | - } |
764 | - } |
765 | - } |
766 | - |
767 | - func _status(config *Config) func(*cli.Cmd) { |
768 | - return func(cmd *cli.Cmd) { |
769 | - cmd.Spec = "[OPTIONS]" |
770 | - cmd.Action = func() { |
771 | - client, err := NewClient(config.SocketPath) |
772 | - if err != nil { |
773 | - outputStatus(Status{}) |
774 | - return |
775 | - } |
776 | - defer client.Close() |
777 | - status, err := client.Status() |
778 | - maybe(err) |
779 | - outputStatus(*status) |
780 | - } |
781 | - } |
782 | - } |
783 | - |
784 | - func _config(config *Config) func(*cli.Cmd) { |
785 | - return func(cmd *cli.Cmd) { |
786 | - cmd.Spec = "[OPTIONS]" |
787 | - cmd.Action = func() { |
788 | - maybe(json.NewEncoder(os.Stdout).Encode(config)) |
789 | - } |
790 | - } |
791 | - } |
792 | - |
793 | - func main() { |
794 | - app := cli.App("pomo", "Pomodoro CLI") |
795 | - app.LongDesc = "Pomo helps you track what you did, how long it took you to do it, and how much effort you expect it to take." |
796 | - app.Spec = "[OPTIONS]" |
797 | - var ( |
798 | - config = &Config{} |
799 | - path = app.StringOpt("p path", defaultConfigPath(), "path to the pomo config directory") |
800 | - ) |
801 | - app.Before = func() { |
802 | - maybe(LoadConfig(*path, config)) |
803 | - } |
804 | - app.Version("v version", Version) |
805 | - app.Command("start s", "start a new task", start(config)) |
806 | - app.Command("init", "initialize the sqlite database", initialize(config)) |
807 | - app.Command("config cf", "display the current configuration", _config(config)) |
808 | - app.Command("create c", "create a new task without starting", create(config)) |
809 | - app.Command("begin b", "begin requested pomodoro", begin(config)) |
810 | - app.Command("list l", "list historical tasks", list(config)) |
811 | - app.Command("delete d", "delete a stored task", _delete(config)) |
812 | - app.Command("status st", "output the current status", _status(config)) |
813 | - app.Run(os.Args) |
814 | - } |
815 | diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go |
816 | new file mode 100644 |
817 | index 0000000..292e428 |
818 | --- /dev/null |
819 | +++ b/pkg/cmd/cmd.go |
820 | @@ -0,0 +1,256 @@ |
821 | + package cmd |
822 | + |
823 | + import ( |
824 | + "database/sql" |
825 | + "encoding/json" |
826 | + "fmt" |
827 | + "os" |
828 | + "os/user" |
829 | + "path" |
830 | + "sort" |
831 | + "time" |
832 | + |
833 | + cli "github.com/jawher/mow.cli" |
834 | + |
835 | + pomo "github.com/kevinschoon/pomo/pkg/internal" |
836 | + ) |
837 | + |
838 | + func maybe(err error) { |
839 | + if err != nil { |
840 | + fmt.Printf("Error:\n%s\n", err) |
841 | + os.Exit(1) |
842 | + } |
843 | + } |
844 | + |
845 | + func defaultConfigPath() string { |
846 | + u, err := user.Current() |
847 | + maybe(err) |
848 | + return path.Join(u.HomeDir, "/.pomo/config.json") |
849 | + } |
850 | + |
851 | + func start(config *pomo.Config) func(*cli.Cmd) { |
852 | + return func(cmd *cli.Cmd) { |
853 | + cmd.Spec = "[OPTIONS] MESSAGE" |
854 | + var ( |
855 | + duration = cmd.StringOpt("d duration", "25m", "duration of each stent") |
856 | + pomodoros = cmd.IntOpt("p pomodoros", 4, "number of pomodoros") |
857 | + message = cmd.StringArg("MESSAGE", "", "descriptive name of the given task") |
858 | + tags = cmd.StringsOpt("t tag", []string{}, "tags associated with this task") |
859 | + ) |
860 | + cmd.Action = func() { |
861 | + parsed, err := time.ParseDuration(*duration) |
862 | + maybe(err) |
863 | + db, err := pomo.NewStore(config.DBPath) |
864 | + maybe(err) |
865 | + defer db.Close() |
866 | + task := &pomo.Task{ |
867 | + Message: *message, |
868 | + Tags: *tags, |
869 | + NPomodoros: *pomodoros, |
870 | + Duration: parsed, |
871 | + } |
872 | + maybe(db.With(func(tx *sql.Tx) error { |
873 | + id, err := db.CreateTask(tx, *task) |
874 | + if err != nil { |
875 | + return err |
876 | + } |
877 | + task.ID = id |
878 | + return nil |
879 | + })) |
880 | + runner, err := pomo.NewTaskRunner(task, config) |
881 | + maybe(err) |
882 | + server, err := pomo.NewServer(runner, config) |
883 | + maybe(err) |
884 | + server.Start() |
885 | + defer server.Stop() |
886 | + runner.Start() |
887 | + pomo.StartUI(runner) |
888 | + } |
889 | + } |
890 | + } |
891 | + |
892 | + func create(config *pomo.Config) func(*cli.Cmd) { |
893 | + return func(cmd *cli.Cmd) { |
894 | + cmd.Spec = "[OPTIONS] MESSAGE" |
895 | + var ( |
896 | + duration = cmd.StringOpt("d duration", "25m", "duration of each stent") |
897 | + pomodoros = cmd.IntOpt("p pomodoros", 4, "number of pomodoros") |
898 | + message = cmd.StringArg("MESSAGE", "", "descriptive name of the given task") |
899 | + tags = cmd.StringsOpt("t tag", []string{}, "tags associated with this task") |
900 | + ) |
901 | + cmd.Action = func() { |
902 | + parsed, err := time.ParseDuration(*duration) |
903 | + maybe(err) |
904 | + db, err := pomo.NewStore(config.DBPath) |
905 | + maybe(err) |
906 | + defer db.Close() |
907 | + task := &pomo.Task{ |
908 | + Message: *message, |
909 | + Tags: *tags, |
910 | + NPomodoros: *pomodoros, |
911 | + Duration: parsed, |
912 | + } |
913 | + maybe(db.With(func(tx *sql.Tx) error { |
914 | + taskId, err := db.CreateTask(tx, *task) |
915 | + if err != nil { |
916 | + return err |
917 | + } |
918 | + fmt.Println(taskId) |
919 | + return nil |
920 | + })) |
921 | + } |
922 | + } |
923 | + } |
924 | + |
925 | + func begin(config *pomo.Config) func(*cli.Cmd) { |
926 | + return func(cmd *cli.Cmd) { |
927 | + cmd.Spec = "[OPTIONS] TASK_ID" |
928 | + var ( |
929 | + taskId = cmd.IntArg("TASK_ID", -1, "ID of Pomodoro to begin") |
930 | + ) |
931 | + |
932 | + cmd.Action = func() { |
933 | + db, err := pomo.NewStore(config.DBPath) |
934 | + maybe(err) |
935 | + defer db.Close() |
936 | + var task *pomo.Task |
937 | + maybe(db.With(func(tx *sql.Tx) error { |
938 | + read, err := db.ReadTask(tx, *taskId) |
939 | + if err != nil { |
940 | + return err |
941 | + } |
942 | + task = read |
943 | + err = db.DeletePomodoros(tx, *taskId) |
944 | + if err != nil { |
945 | + return err |
946 | + } |
947 | + task.Pomodoros = []*pomo.Pomodoro{} |
948 | + return nil |
949 | + })) |
950 | + runner, err := pomo.NewTaskRunner(task, config) |
951 | + maybe(err) |
952 | + server, err := pomo.NewServer(runner, config) |
953 | + maybe(err) |
954 | + server.Start() |
955 | + defer server.Stop() |
956 | + runner.Start() |
957 | + pomo.StartUI(runner) |
958 | + } |
959 | + } |
960 | + } |
961 | + |
962 | + func initialize(config *pomo.Config) func(*cli.Cmd) { |
963 | + return func(cmd *cli.Cmd) { |
964 | + cmd.Spec = "[OPTIONS]" |
965 | + cmd.Action = func() { |
966 | + db, err := pomo.NewStore(config.DBPath) |
967 | + maybe(err) |
968 | + defer db.Close() |
969 | + maybe(pomo.InitDB(db)) |
970 | + } |
971 | + } |
972 | + } |
973 | + |
974 | + func list(config *pomo.Config) func(*cli.Cmd) { |
975 | + return func(cmd *cli.Cmd) { |
976 | + cmd.Spec = "[OPTIONS]" |
977 | + var ( |
978 | + asJSON = cmd.BoolOpt("json", false, "output task history as JSON") |
979 | + assend = cmd.BoolOpt("assend", false, "sort tasks assending in age") |
980 | + all = cmd.BoolOpt("a all", true, "output all tasks") |
981 | + limit = cmd.IntOpt("n limit", 0, "limit the number of results by n") |
982 | + duration = cmd.StringOpt("d duration", "24h", "show tasks within this duration") |
983 | + ) |
984 | + cmd.Action = func() { |
985 | + duration, err := time.ParseDuration(*duration) |
986 | + maybe(err) |
987 | + db, err := pomo.NewStore(config.DBPath) |
988 | + maybe(err) |
989 | + defer db.Close() |
990 | + maybe(db.With(func(tx *sql.Tx) error { |
991 | + tasks, err := db.ReadTasks(tx) |
992 | + maybe(err) |
993 | + if *assend { |
994 | + sort.Sort(sort.Reverse(pomo.ByID(tasks))) |
995 | + } |
996 | + if !*all { |
997 | + tasks = pomo.After(time.Now().Add(-duration), tasks) |
998 | + } |
999 | + if *limit > 0 && (len(tasks) > *limit) { |
1000 | + tasks = tasks[0:*limit] |
1001 | + } |
1002 | + if *asJSON { |
1003 | + maybe(json.NewEncoder(os.Stdout).Encode(tasks)) |
1004 | + return nil |
1005 | + } |
1006 | + maybe(err) |
1007 | + pomo.SummerizeTasks(config, tasks) |
1008 | + return nil |
1009 | + })) |
1010 | + } |
1011 | + } |
1012 | + } |
1013 | + |
1014 | + func _delete(config *pomo.Config) func(*cli.Cmd) { |
1015 | + return func(cmd *cli.Cmd) { |
1016 | + cmd.Spec = "[OPTIONS] TASK_ID" |
1017 | + var taskID = cmd.IntArg("TASK_ID", -1, "task to delete") |
1018 | + cmd.Action = func() { |
1019 | + db, err := pomo.NewStore(config.DBPath) |
1020 | + maybe(err) |
1021 | + defer db.Close() |
1022 | + maybe(db.With(func(tx *sql.Tx) error { |
1023 | + return db.DeleteTask(tx, *taskID) |
1024 | + })) |
1025 | + } |
1026 | + } |
1027 | + } |
1028 | + |
1029 | + func _status(config *pomo.Config) func(*cli.Cmd) { |
1030 | + return func(cmd *cli.Cmd) { |
1031 | + cmd.Spec = "[OPTIONS]" |
1032 | + cmd.Action = func() { |
1033 | + client, err := pomo.NewClient(config.SocketPath) |
1034 | + if err != nil { |
1035 | + pomo.OutputStatus(pomo.Status{}) |
1036 | + return |
1037 | + } |
1038 | + defer client.Close() |
1039 | + status, err := client.Status() |
1040 | + maybe(err) |
1041 | + pomo.OutputStatus(*status) |
1042 | + } |
1043 | + } |
1044 | + } |
1045 | + |
1046 | + func _config(config *pomo.Config) func(*cli.Cmd) { |
1047 | + return func(cmd *cli.Cmd) { |
1048 | + cmd.Spec = "[OPTIONS]" |
1049 | + cmd.Action = func() { |
1050 | + maybe(json.NewEncoder(os.Stdout).Encode(config)) |
1051 | + } |
1052 | + } |
1053 | + } |
1054 | + |
1055 | + func Run() { |
1056 | + app := cli.App("pomo", "Pomodoro CLI") |
1057 | + app.LongDesc = "Pomo helps you track what you did, how long it took you to do it, and how much effort you expect it to take." |
1058 | + app.Spec = "[OPTIONS]" |
1059 | + var ( |
1060 | + config = &pomo.Config{} |
1061 | + path = app.StringOpt("p path", defaultConfigPath(), "path to the pomo config directory") |
1062 | + ) |
1063 | + app.Before = func() { |
1064 | + maybe(pomo.LoadConfig(*path, config)) |
1065 | + } |
1066 | + app.Version("v version", pomo.Version) |
1067 | + app.Command("start s", "start a new task", start(config)) |
1068 | + app.Command("init", "initialize the sqlite database", initialize(config)) |
1069 | + app.Command("config cf", "display the current configuration", _config(config)) |
1070 | + app.Command("create c", "create a new task without starting", create(config)) |
1071 | + app.Command("begin b", "begin requested pomodoro", begin(config)) |
1072 | + app.Command("list l", "list historical tasks", list(config)) |
1073 | + app.Command("delete d", "delete a stored task", _delete(config)) |
1074 | + app.Command("status st", "output the current status", _status(config)) |
1075 | + app.Run(os.Args) |
1076 | + } |
1077 | diff --git a/pkg/internal/bindata.go b/pkg/internal/bindata.go |
1078 | new file mode 100644 |
1079 | index 0000000..48aeb4b |
1080 | --- /dev/null |
1081 | +++ b/pkg/internal/bindata.go |
1082 | @@ -0,0 +1,235 @@ |
1083 | + // Code generated by go-bindata. |
1084 | + // sources: |
1085 | + // tomato-icon.png |
1086 | + // DO NOT EDIT! |
1087 | + |
1088 | + package pomo |
1089 | + |
1090 | + import ( |
1091 | + "bytes" |
1092 | + "compress/gzip" |
1093 | + "fmt" |
1094 | + "io" |
1095 | + "io/ioutil" |
1096 | + "os" |
1097 | + "path/filepath" |
1098 | + "strings" |
1099 | + "time" |
1100 | + ) |
1101 | + |
1102 | + func bindataRead(data []byte, name string) ([]byte, error) { |
1103 | + gz, err := gzip.NewReader(bytes.NewBuffer(data)) |
1104 | + if err != nil { |
1105 | + return nil, fmt.Errorf("Read %q: %v", name, err) |
1106 | + } |
1107 | + |
1108 | + var buf bytes.Buffer |
1109 | + _, err = io.Copy(&buf, gz) |
1110 | + clErr := gz.Close() |
1111 | + |
1112 | + if err != nil { |
1113 | + return nil, fmt.Errorf("Read %q: %v", name, err) |
1114 | + } |
1115 | + if clErr != nil { |
1116 | + return nil, err |
1117 | + } |
1118 | + |
1119 | + return buf.Bytes(), nil |
1120 | + } |
1121 | + |
1122 | + type asset struct { |
1123 | + bytes []byte |
1124 | + info os.FileInfo |
1125 | + } |
1126 | + |
1127 | + type bindataFileInfo struct { |
1128 | + name string |
1129 | + size int64 |
1130 | + mode os.FileMode |
1131 | + modTime time.Time |
1132 | + } |
1133 | + |
1134 | + func (fi bindataFileInfo) Name() string { |
1135 | + return fi.name |
1136 | + } |
1137 | + func (fi bindataFileInfo) Size() int64 { |
1138 | + return fi.size |
1139 | + } |
1140 | + func (fi bindataFileInfo) Mode() os.FileMode { |
1141 | + return fi.mode |
1142 | + } |
1143 | + func (fi bindataFileInfo) ModTime() time.Time { |
1144 | + return fi.modTime |
1145 | + } |
1146 | + func (fi bindataFileInfo) IsDir() bool { |
1147 | + return false |
1148 | + } |
1149 | + func (fi bindataFileInfo) Sys() interface{} { |
1150 | + return nil |
1151 | + } |
1152 | + |
1153 | + 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") |
1154 | + |
1155 | + func tomatoIconPngBytes() ([]byte, error) { |
1156 | + return bindataRead( |
1157 | + _tomatoIconPng, |
1158 | + "tomato-icon.png", |
1159 | + ) |
1160 | + } |
1161 | + |
1162 | + func tomatoIconPng() (*asset, error) { |
1163 | + bytes, err := tomatoIconPngBytes() |
1164 | + if err != nil { |
1165 | + return nil, err |
1166 | + } |
1167 | + |
1168 | + info := bindataFileInfo{name: "tomato-icon.png", size: 3932, mode: os.FileMode(420), modTime: time.Unix(1516522577, 0)} |
1169 | + a := &asset{bytes: bytes, info: info} |
1170 | + return a, nil |
1171 | + } |
1172 | + |
1173 | + // Asset loads and returns the asset for the given name. |
1174 | + // It returns an error if the asset could not be found or |
1175 | + // could not be loaded. |
1176 | + func Asset(name string) ([]byte, error) { |
1177 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
1178 | + if f, ok := _bindata[cannonicalName]; ok { |
1179 | + a, err := f() |
1180 | + if err != nil { |
1181 | + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) |
1182 | + } |
1183 | + return a.bytes, nil |
1184 | + } |
1185 | + return nil, fmt.Errorf("Asset %s not found", name) |
1186 | + } |
1187 | + |
1188 | + // MustAsset is like Asset but panics when Asset would return an error. |
1189 | + // It simplifies safe initialization of global variables. |
1190 | + func MustAsset(name string) []byte { |
1191 | + a, err := Asset(name) |
1192 | + if err != nil { |
1193 | + panic("asset: Asset(" + name + "): " + err.Error()) |
1194 | + } |
1195 | + |
1196 | + return a |
1197 | + } |
1198 | + |
1199 | + // AssetInfo loads and returns the asset info for the given name. |
1200 | + // It returns an error if the asset could not be found or |
1201 | + // could not be loaded. |
1202 | + func AssetInfo(name string) (os.FileInfo, error) { |
1203 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
1204 | + if f, ok := _bindata[cannonicalName]; ok { |
1205 | + a, err := f() |
1206 | + if err != nil { |
1207 | + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) |
1208 | + } |
1209 | + return a.info, nil |
1210 | + } |
1211 | + return nil, fmt.Errorf("AssetInfo %s not found", name) |
1212 | + } |
1213 | + |
1214 | + // AssetNames returns the names of the assets. |
1215 | + func AssetNames() []string { |
1216 | + names := make([]string, 0, len(_bindata)) |
1217 | + for name := range _bindata { |
1218 | + names = append(names, name) |
1219 | + } |
1220 | + return names |
1221 | + } |
1222 | + |
1223 | + // _bindata is a table, holding each asset generator, mapped to its name. |
1224 | + var _bindata = map[string]func() (*asset, error){ |
1225 | + "tomato-icon.png": tomatoIconPng, |
1226 | + } |
1227 | + |
1228 | + // AssetDir returns the file names below a certain |
1229 | + // directory embedded in the file by go-bindata. |
1230 | + // For example if you run go-bindata on data/... and data contains the |
1231 | + // following hierarchy: |
1232 | + // data/ |
1233 | + // foo.txt |
1234 | + // img/ |
1235 | + // a.png |
1236 | + // b.png |
1237 | + // then AssetDir("data") would return []string{"foo.txt", "img"} |
1238 | + // AssetDir("data/img") would return []string{"a.png", "b.png"} |
1239 | + // AssetDir("foo.txt") and AssetDir("notexist") would return an error |
1240 | + // AssetDir("") will return []string{"data"}. |
1241 | + func AssetDir(name string) ([]string, error) { |
1242 | + node := _bintree |
1243 | + if len(name) != 0 { |
1244 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
1245 | + pathList := strings.Split(cannonicalName, "/") |
1246 | + for _, p := range pathList { |
1247 | + node = node.Children[p] |
1248 | + if node == nil { |
1249 | + return nil, fmt.Errorf("Asset %s not found", name) |
1250 | + } |
1251 | + } |
1252 | + } |
1253 | + if node.Func != nil { |
1254 | + return nil, fmt.Errorf("Asset %s not found", name) |
1255 | + } |
1256 | + rv := make([]string, 0, len(node.Children)) |
1257 | + for childName := range node.Children { |
1258 | + rv = append(rv, childName) |
1259 | + } |
1260 | + return rv, nil |
1261 | + } |
1262 | + |
1263 | + type bintree struct { |
1264 | + Func func() (*asset, error) |
1265 | + Children map[string]*bintree |
1266 | + } |
1267 | + var _bintree = &bintree{nil, map[string]*bintree{ |
1268 | + "tomato-icon.png": &bintree{tomatoIconPng, map[string]*bintree{}}, |
1269 | + }} |
1270 | + |
1271 | + // RestoreAsset restores an asset under the given directory |
1272 | + func RestoreAsset(dir, name string) error { |
1273 | + data, err := Asset(name) |
1274 | + if err != nil { |
1275 | + return err |
1276 | + } |
1277 | + info, err := AssetInfo(name) |
1278 | + if err != nil { |
1279 | + return err |
1280 | + } |
1281 | + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) |
1282 | + if err != nil { |
1283 | + return err |
1284 | + } |
1285 | + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) |
1286 | + if err != nil { |
1287 | + return err |
1288 | + } |
1289 | + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) |
1290 | + if err != nil { |
1291 | + return err |
1292 | + } |
1293 | + return nil |
1294 | + } |
1295 | + |
1296 | + // RestoreAssets restores an asset under the given directory recursively |
1297 | + func RestoreAssets(dir, name string) error { |
1298 | + children, err := AssetDir(name) |
1299 | + // File |
1300 | + if err != nil { |
1301 | + return RestoreAsset(dir, name) |
1302 | + } |
1303 | + // Dir |
1304 | + for _, child := range children { |
1305 | + err = RestoreAssets(dir, filepath.Join(name, child)) |
1306 | + if err != nil { |
1307 | + return err |
1308 | + } |
1309 | + } |
1310 | + return nil |
1311 | + } |
1312 | + |
1313 | + func _filePath(dir, name string) string { |
1314 | + cannonicalName := strings.Replace(name, "\\", "/", -1) |
1315 | + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) |
1316 | + } |
1317 | + |
1318 | diff --git a/pkg/internal/config.go b/pkg/internal/config.go |
1319 | new file mode 100644 |
1320 | index 0000000..e5090f1 |
1321 | --- /dev/null |
1322 | +++ b/pkg/internal/config.go |
1323 | @@ -0,0 +1,114 @@ |
1324 | + package pomo |
1325 | + |
1326 | + import ( |
1327 | + "encoding/json" |
1328 | + "io/ioutil" |
1329 | + "os" |
1330 | + "path" |
1331 | + |
1332 | + "github.com/fatih/color" |
1333 | + ) |
1334 | + |
1335 | + const ( |
1336 | + defaultDateTimeFmt = "2006-01-02 15:04" |
1337 | + ) |
1338 | + |
1339 | + // Config represents user preferences |
1340 | + type Config struct { |
1341 | + Colors *ColorMap `json:"colors"` |
1342 | + DateTimeFmt string `json:"dateTimeFmt"` |
1343 | + BasePath string `json:"basePath"` |
1344 | + DBPath string `json:"dbPath"` |
1345 | + SocketPath string `json:"socketPath"` |
1346 | + IconPath string `json:"iconPath"` |
1347 | + } |
1348 | + |
1349 | + type ColorMap struct { |
1350 | + colors map[string]*color.Color |
1351 | + tags map[string]string |
1352 | + } |
1353 | + |
1354 | + func (c *ColorMap) Get(name string) *color.Color { |
1355 | + if color, ok := c.colors[name]; ok { |
1356 | + return color |
1357 | + } |
1358 | + return nil |
1359 | + } |
1360 | + |
1361 | + func (c *ColorMap) MarshalJSON() ([]byte, error) { |
1362 | + return json.Marshal(c.tags) |
1363 | + } |
1364 | + |
1365 | + func (c *ColorMap) UnmarshalJSON(raw []byte) error { |
1366 | + lookup := map[string]*color.Color{ |
1367 | + "black": color.New(color.FgBlack), |
1368 | + "hiblack": color.New(color.FgHiBlack), |
1369 | + "blue": color.New(color.FgBlue), |
1370 | + "hiblue": color.New(color.FgHiBlue), |
1371 | + "cyan": color.New(color.FgCyan), |
1372 | + "hicyan": color.New(color.FgHiCyan), |
1373 | + "green": color.New(color.FgGreen), |
1374 | + "higreen": color.New(color.FgHiGreen), |
1375 | + "magenta": color.New(color.FgMagenta), |
1376 | + "himagenta": color.New(color.FgHiMagenta), |
1377 | + "red": color.New(color.FgRed), |
1378 | + "hired": color.New(color.FgHiRed), |
1379 | + "white": color.New(color.FgWhite), |
1380 | + "hiwrite": color.New(color.FgHiWhite), |
1381 | + "yellow": color.New(color.FgYellow), |
1382 | + "hiyellow": color.New(color.FgHiYellow), |
1383 | + } |
1384 | + cm := &ColorMap{ |
1385 | + colors: map[string]*color.Color{}, |
1386 | + tags: map[string]string{}, |
1387 | + } |
1388 | + err := json.Unmarshal(raw, &cm.tags) |
1389 | + if err != nil { |
1390 | + return err |
1391 | + } |
1392 | + for tag, colorName := range cm.tags { |
1393 | + if color, ok := lookup[colorName]; ok { |
1394 | + cm.colors[tag] = color |
1395 | + } |
1396 | + } |
1397 | + *c = *cm |
1398 | + return nil |
1399 | + } |
1400 | + |
1401 | + func LoadConfig(configPath string, config *Config) error { |
1402 | + raw, err := ioutil.ReadFile(configPath) |
1403 | + if err != nil { |
1404 | + os.MkdirAll(path.Dir(configPath), 0755) |
1405 | + // Create an empty config file |
1406 | + // if it does not already exist. |
1407 | + if os.IsNotExist(err) { |
1408 | + raw, _ := json.Marshal(map[string]string{}) |
1409 | + err := ioutil.WriteFile(configPath, raw, 0644) |
1410 | + if err != nil { |
1411 | + return err |
1412 | + } |
1413 | + return LoadConfig(configPath, config) |
1414 | + } |
1415 | + return err |
1416 | + } |
1417 | + err = json.Unmarshal(raw, config) |
1418 | + if err != nil { |
1419 | + return err |
1420 | + } |
1421 | + if config.DateTimeFmt == "" { |
1422 | + config.DateTimeFmt = defaultDateTimeFmt |
1423 | + } |
1424 | + if config.BasePath == "" { |
1425 | + config.BasePath = path.Dir(configPath) |
1426 | + } |
1427 | + if config.DBPath == "" { |
1428 | + config.DBPath = path.Join(config.BasePath, "/pomo.db") |
1429 | + } |
1430 | + if config.SocketPath == "" { |
1431 | + config.SocketPath = path.Join(config.BasePath, "/pomo.sock") |
1432 | + } |
1433 | + if config.IconPath == "" { |
1434 | + config.IconPath = path.Join(config.BasePath, "/icon.png") |
1435 | + } |
1436 | + return nil |
1437 | + } |
1438 | diff --git a/pkg/internal/runner.go b/pkg/internal/runner.go |
1439 | new file mode 100644 |
1440 | index 0000000..9325c74 |
1441 | --- /dev/null |
1442 | +++ b/pkg/internal/runner.go |
1443 | @@ -0,0 +1,151 @@ |
1444 | + package pomo |
1445 | + |
1446 | + import ( |
1447 | + "database/sql" |
1448 | + "time" |
1449 | + ) |
1450 | + |
1451 | + type TaskRunner struct { |
1452 | + count int |
1453 | + taskID int |
1454 | + taskMessage string |
1455 | + nPomodoros int |
1456 | + origDuration time.Duration |
1457 | + state State |
1458 | + store *Store |
1459 | + started time.Time |
1460 | + pause chan bool |
1461 | + toggle chan bool |
1462 | + notifier Notifier |
1463 | + duration time.Duration |
1464 | + } |
1465 | + |
1466 | + func NewMockedTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) { |
1467 | + tr := &TaskRunner{ |
1468 | + taskID: task.ID, |
1469 | + taskMessage: task.Message, |
1470 | + nPomodoros: task.NPomodoros, |
1471 | + origDuration: task.Duration, |
1472 | + store: store, |
1473 | + state: State(0), |
1474 | + pause: make(chan bool), |
1475 | + toggle: make(chan bool), |
1476 | + notifier: notifier, |
1477 | + duration: task.Duration, |
1478 | + } |
1479 | + return tr, nil |
1480 | + } |
1481 | + func NewTaskRunner(task *Task, config *Config) (*TaskRunner, error) { |
1482 | + store, err := NewStore(config.DBPath) |
1483 | + if err != nil { |
1484 | + return nil, err |
1485 | + } |
1486 | + tr := &TaskRunner{ |
1487 | + taskID: task.ID, |
1488 | + taskMessage: task.Message, |
1489 | + nPomodoros: task.NPomodoros, |
1490 | + origDuration: task.Duration, |
1491 | + store: store, |
1492 | + state: State(0), |
1493 | + pause: make(chan bool), |
1494 | + toggle: make(chan bool), |
1495 | + notifier: NewXnotifier(config.IconPath), |
1496 | + duration: task.Duration, |
1497 | + } |
1498 | + return tr, nil |
1499 | + } |
1500 | + |
1501 | + func (t *TaskRunner) Start() { |
1502 | + go t.run() |
1503 | + } |
1504 | + |
1505 | + func (t *TaskRunner) TimeRemaining() time.Duration { |
1506 | + return (t.duration - time.Since(t.started)).Truncate(time.Second) |
1507 | + } |
1508 | + |
1509 | + func (t *TaskRunner) SetState(state State) { |
1510 | + t.state = state |
1511 | + } |
1512 | + |
1513 | + func (t *TaskRunner) run() error { |
1514 | + for t.count < t.nPomodoros { |
1515 | + // Create a new pomodoro where we |
1516 | + // track the start / end time of |
1517 | + // of this session. |
1518 | + pomodoro := &Pomodoro{} |
1519 | + // Start this pomodoro |
1520 | + pomodoro.Start = time.Now() |
1521 | + // Set state to RUNNIN |
1522 | + t.SetState(RUNNING) |
1523 | + // Create a new timer |
1524 | + timer := time.NewTimer(t.duration) |
1525 | + // Record our started time |
1526 | + t.started = pomodoro.Start |
1527 | + loop: |
1528 | + select { |
1529 | + case <-timer.C: |
1530 | + t.SetState(BREAKING) |
1531 | + t.count++ |
1532 | + case <-t.toggle: |
1533 | + // Catch any toggles when we |
1534 | + // are not expecting them |
1535 | + goto loop |
1536 | + case <-t.pause: |
1537 | + timer.Stop() |
1538 | + // Record the remaining time of the current pomodoro |
1539 | + remaining := t.TimeRemaining() |
1540 | + // Change state to PAUSED |
1541 | + t.SetState(PAUSED) |
1542 | + // Wait for the user to press [p] |
1543 | + <-t.pause |
1544 | + // Resume the timer with previous |
1545 | + // remaining time |
1546 | + timer.Reset(remaining) |
1547 | + // Change duration |
1548 | + t.started = time.Now() |
1549 | + t.duration = remaining |
1550 | + // Restore state to RUNNING |
1551 | + t.SetState(RUNNING) |
1552 | + goto loop |
1553 | + } |
1554 | + pomodoro.End = time.Now() |
1555 | + err := t.store.With(func(tx *sql.Tx) error { |
1556 | + return t.store.CreatePomodoro(tx, t.taskID, *pomodoro) |
1557 | + }) |
1558 | + if err != nil { |
1559 | + return err |
1560 | + } |
1561 | + // All pomodoros completed |
1562 | + if t.count == t.nPomodoros { |
1563 | + break |
1564 | + } |
1565 | + |
1566 | + t.notifier.Notify("Pomo", "It is time to take a break!") |
1567 | + // Reset the duration incase it |
1568 | + // was paused. |
1569 | + t.duration = t.origDuration |
1570 | + // User concludes the break |
1571 | + <-t.toggle |
1572 | + |
1573 | + } |
1574 | + t.notifier.Notify("Pomo", "Pomo session has completed!") |
1575 | + t.SetState(COMPLETE) |
1576 | + return nil |
1577 | + } |
1578 | + |
1579 | + func (t *TaskRunner) Toggle() { |
1580 | + t.toggle <- true |
1581 | + } |
1582 | + |
1583 | + func (t *TaskRunner) Pause() { |
1584 | + t.pause <- true |
1585 | + } |
1586 | + |
1587 | + func (t *TaskRunner) Status() *Status { |
1588 | + return &Status{ |
1589 | + State: t.state, |
1590 | + Count: t.count, |
1591 | + NPomodoros: t.nPomodoros, |
1592 | + Remaining: t.TimeRemaining(), |
1593 | + } |
1594 | + } |
1595 | diff --git a/pkg/internal/runner_test.go b/pkg/internal/runner_test.go |
1596 | new file mode 100644 |
1597 | index 0000000..54a316e |
1598 | --- /dev/null |
1599 | +++ b/pkg/internal/runner_test.go |
1600 | @@ -0,0 +1,37 @@ |
1601 | + package pomo |
1602 | + |
1603 | + import ( |
1604 | + "fmt" |
1605 | + "io/ioutil" |
1606 | + "path" |
1607 | + "testing" |
1608 | + "time" |
1609 | + ) |
1610 | + |
1611 | + func TestTaskRunner(t *testing.T) { |
1612 | + baseDir, _ := ioutil.TempDir("/tmp", "") |
1613 | + store, err := NewStore(path.Join(baseDir, "pomo.db")) |
1614 | + if err != nil { |
1615 | + t.Error(err) |
1616 | + } |
1617 | + err = InitDB(store) |
1618 | + if err != nil { |
1619 | + t.Error(err) |
1620 | + } |
1621 | + runner, err := NewMockedTaskRunner(&Task{ |
1622 | + Duration: time.Second * 2, |
1623 | + NPomodoros: 2, |
1624 | + Message: fmt.Sprint("Test Task"), |
1625 | + }, store, NoopNotifier{}) |
1626 | + if err != nil { |
1627 | + t.Error(err) |
1628 | + } |
1629 | + |
1630 | + runner.Start() |
1631 | + |
1632 | + runner.Toggle() |
1633 | + runner.Toggle() |
1634 | + |
1635 | + runner.Toggle() |
1636 | + runner.Toggle() |
1637 | + } |
1638 | diff --git a/pkg/internal/server.go b/pkg/internal/server.go |
1639 | new file mode 100644 |
1640 | index 0000000..38357c7 |
1641 | --- /dev/null |
1642 | +++ b/pkg/internal/server.go |
1643 | @@ -0,0 +1,93 @@ |
1644 | + package pomo |
1645 | + |
1646 | + import ( |
1647 | + "encoding/json" |
1648 | + "errors" |
1649 | + "fmt" |
1650 | + "net" |
1651 | + "os" |
1652 | + ) |
1653 | + |
1654 | + // Server listens on a Unix domain socket |
1655 | + // for Pomo status requests |
1656 | + type Server struct { |
1657 | + listener net.Listener |
1658 | + runner *TaskRunner |
1659 | + running bool |
1660 | + } |
1661 | + |
1662 | + func (s *Server) listen() { |
1663 | + for s.running { |
1664 | + conn, err := s.listener.Accept() |
1665 | + if err != nil { |
1666 | + break |
1667 | + } |
1668 | + buf := make([]byte, 512) |
1669 | + // Ignore any content |
1670 | + conn.Read(buf) |
1671 | + raw, _ := json.Marshal(s.runner.Status()) |
1672 | + conn.Write(raw) |
1673 | + conn.Close() |
1674 | + } |
1675 | + } |
1676 | + |
1677 | + func (s *Server) Start() { |
1678 | + s.running = true |
1679 | + go s.listen() |
1680 | + } |
1681 | + |
1682 | + func (s *Server) Stop() { |
1683 | + s.running = false |
1684 | + s.listener.Close() |
1685 | + } |
1686 | + |
1687 | + func NewServer(runner *TaskRunner, config *Config) (*Server, error) { |
1688 | + //check if socket file exists |
1689 | + if _, err := os.Stat(config.SocketPath); err == nil { |
1690 | + _, err := net.Dial("unix", config.SocketPath) |
1691 | + //if error then sock file was saved after crash |
1692 | + if err != nil { |
1693 | + os.Remove(config.SocketPath) |
1694 | + } else { |
1695 | + // another instance of pomo is running |
1696 | + return nil, errors.New(fmt.Sprintf("Socket %s is already in use", config.SocketPath)) |
1697 | + } |
1698 | + } |
1699 | + listener, err := net.Listen("unix", config.SocketPath) |
1700 | + if err != nil { |
1701 | + return nil, err |
1702 | + } |
1703 | + return &Server{listener: listener, runner: runner}, nil |
1704 | + } |
1705 | + |
1706 | + // Client makes requests to a listening |
1707 | + // pomo server to check the status of |
1708 | + // any currently running task session. |
1709 | + type Client struct { |
1710 | + conn net.Conn |
1711 | + } |
1712 | + |
1713 | + func (c Client) read(statusCh chan *Status) { |
1714 | + buf := make([]byte, 512) |
1715 | + n, _ := c.conn.Read(buf) |
1716 | + status := &Status{} |
1717 | + json.Unmarshal(buf[0:n], status) |
1718 | + statusCh <- status |
1719 | + } |
1720 | + |
1721 | + func (c Client) Status() (*Status, error) { |
1722 | + statusCh := make(chan *Status) |
1723 | + c.conn.Write([]byte("status")) |
1724 | + go c.read(statusCh) |
1725 | + return <-statusCh, nil |
1726 | + } |
1727 | + |
1728 | + func (c Client) Close() error { return c.conn.Close() } |
1729 | + |
1730 | + func NewClient(path string) (*Client, error) { |
1731 | + conn, err := net.Dial("unix", path) |
1732 | + if err != nil { |
1733 | + return nil, err |
1734 | + } |
1735 | + return &Client{conn: conn}, nil |
1736 | + } |
1737 | diff --git a/pkg/internal/store.go b/pkg/internal/store.go |
1738 | new file mode 100644 |
1739 | index 0000000..76a0906 |
1740 | --- /dev/null |
1741 | +++ b/pkg/internal/store.go |
1742 | @@ -0,0 +1,187 @@ |
1743 | + package pomo |
1744 | + |
1745 | + import ( |
1746 | + "database/sql" |
1747 | + "strings" |
1748 | + "time" |
1749 | + |
1750 | + _ "github.com/mattn/go-sqlite3" |
1751 | + ) |
1752 | + |
1753 | + // 2018-01-16 19:05:21.752851759+08:00 |
1754 | + const datetimeFmt = "2006-01-02 15:04:05.999999999-07:00" |
1755 | + |
1756 | + type StoreFunc func(tx *sql.Tx) error |
1757 | + |
1758 | + type Store struct { |
1759 | + db *sql.DB |
1760 | + } |
1761 | + |
1762 | + func NewStore(path string) (*Store, error) { |
1763 | + db, err := sql.Open("sqlite3", path) |
1764 | + if err != nil { |
1765 | + return nil, err |
1766 | + } |
1767 | + return &Store{db: db}, nil |
1768 | + } |
1769 | + |
1770 | + // With applies all of the given functions with |
1771 | + // a single transaction, rolling back on failure |
1772 | + // and commiting on success. |
1773 | + func (s Store) With(fns ...func(tx *sql.Tx) error) error { |
1774 | + tx, err := s.db.Begin() |
1775 | + if err != nil { |
1776 | + return err |
1777 | + } |
1778 | + for _, fn := range fns { |
1779 | + err = fn(tx) |
1780 | + if err != nil { |
1781 | + tx.Rollback() |
1782 | + return err |
1783 | + } |
1784 | + } |
1785 | + return tx.Commit() |
1786 | + } |
1787 | + |
1788 | + func (s Store) CreateTask(tx *sql.Tx, task Task) (int, error) { |
1789 | + var taskID int |
1790 | + _, err := tx.Exec( |
1791 | + "INSERT INTO task (message,pomodoros,duration,tags) VALUES ($1,$2,$3,$4)", |
1792 | + task.Message, task.NPomodoros, task.Duration.String(), strings.Join(task.Tags, ",")) |
1793 | + if err != nil { |
1794 | + return -1, err |
1795 | + } |
1796 | + err = tx.QueryRow("SELECT last_insert_rowid() FROM task").Scan(&taskID) |
1797 | + if err != nil { |
1798 | + return -1, err |
1799 | + } |
1800 | + err = tx.QueryRow("SELECT last_insert_rowid() FROM task").Scan(&taskID) |
1801 | + if err != nil { |
1802 | + return -1, err |
1803 | + } |
1804 | + return taskID, nil |
1805 | + } |
1806 | + |
1807 | + func (s Store) ReadTasks(tx *sql.Tx) ([]*Task, error) { |
1808 | + rows, err := tx.Query(`SELECT rowid,message,pomodoros,duration,tags FROM task`) |
1809 | + if err != nil { |
1810 | + return nil, err |
1811 | + } |
1812 | + tasks := []*Task{} |
1813 | + for rows.Next() { |
1814 | + var ( |
1815 | + tags string |
1816 | + strDuration string |
1817 | + ) |
1818 | + task := &Task{Pomodoros: []*Pomodoro{}} |
1819 | + err = rows.Scan(&task.ID, &task.Message, &task.NPomodoros, &strDuration, &tags) |
1820 | + if err != nil { |
1821 | + return nil, err |
1822 | + } |
1823 | + duration, _ := time.ParseDuration(strDuration) |
1824 | + task.Duration = duration |
1825 | + if tags != "" { |
1826 | + task.Tags = strings.Split(tags, ",") |
1827 | + } |
1828 | + pomodoros, err := s.ReadPomodoros(tx, task.ID) |
1829 | + if err != nil { |
1830 | + return nil, err |
1831 | + } |
1832 | + for _, pomodoro := range pomodoros { |
1833 | + task.Pomodoros = append(task.Pomodoros, pomodoro) |
1834 | + } |
1835 | + tasks = append(tasks, task) |
1836 | + } |
1837 | + return tasks, nil |
1838 | + } |
1839 | + |
1840 | + func (s Store) DeleteTask(tx *sql.Tx, taskID int) error { |
1841 | + _, err := tx.Exec("DELETE FROM task WHERE rowid = $1", &taskID) |
1842 | + if err != nil { |
1843 | + return err |
1844 | + } |
1845 | + _, err = tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
1846 | + if err != nil { |
1847 | + return err |
1848 | + } |
1849 | + return nil |
1850 | + } |
1851 | + |
1852 | + func (s Store) ReadTask(tx *sql.Tx, taskID int) (*Task, error) { |
1853 | + task := &Task{} |
1854 | + var ( |
1855 | + tags string |
1856 | + strDuration string |
1857 | + ) |
1858 | + err := tx.QueryRow(`SELECT rowid,message,pomodoros,duration,tags FROM task WHERE rowid = $1`, &taskID). |
1859 | + Scan(&task.ID, &task.Message, &task.NPomodoros, &strDuration, &tags) |
1860 | + if err != nil { |
1861 | + return nil, err |
1862 | + } |
1863 | + duration, _ := time.ParseDuration(strDuration) |
1864 | + task.Duration = duration |
1865 | + if tags != "" { |
1866 | + task.Tags = strings.Split(tags, ",") |
1867 | + } |
1868 | + return task, nil |
1869 | + } |
1870 | + |
1871 | + func (s Store) CreatePomodoro(tx *sql.Tx, taskID int, pomodoro Pomodoro) error { |
1872 | + _, err := tx.Exec( |
1873 | + `INSERT INTO pomodoro (task_id, start, end) VALUES ($1, $2, $3)`, |
1874 | + taskID, |
1875 | + pomodoro.Start, |
1876 | + pomodoro.End, |
1877 | + ) |
1878 | + return err |
1879 | + } |
1880 | + |
1881 | + func (s Store) ReadPomodoros(tx *sql.Tx, taskID int) ([]*Pomodoro, error) { |
1882 | + rows, err := tx.Query(`SELECT start,end FROM pomodoro WHERE task_id = $1`, &taskID) |
1883 | + if err != nil { |
1884 | + return nil, err |
1885 | + } |
1886 | + pomodoros := []*Pomodoro{} |
1887 | + for rows.Next() { |
1888 | + var ( |
1889 | + startStr string |
1890 | + endStr string |
1891 | + ) |
1892 | + pomodoro := &Pomodoro{} |
1893 | + err = rows.Scan(&startStr, &endStr) |
1894 | + if err != nil { |
1895 | + return nil, err |
1896 | + } |
1897 | + start, _ := time.Parse(datetimeFmt, startStr) |
1898 | + end, _ := time.Parse(datetimeFmt, endStr) |
1899 | + pomodoro.Start = start |
1900 | + pomodoro.End = end |
1901 | + pomodoros = append(pomodoros, pomodoro) |
1902 | + } |
1903 | + return pomodoros, nil |
1904 | + } |
1905 | + |
1906 | + func (s Store) DeletePomodoros(tx *sql.Tx, taskID int) error { |
1907 | + _, err := tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
1908 | + return err |
1909 | + } |
1910 | + |
1911 | + func (s Store) Close() error { return s.db.Close() } |
1912 | + |
1913 | + func InitDB(db *Store) error { |
1914 | + stmt := ` |
1915 | + CREATE TABLE task ( |
1916 | + message TEXT, |
1917 | + pomodoros INTEGER, |
1918 | + duration TEXT, |
1919 | + tags TEXT |
1920 | + ); |
1921 | + CREATE TABLE pomodoro ( |
1922 | + task_id INTEGER, |
1923 | + start DATETTIME, |
1924 | + end DATETTIME |
1925 | + ); |
1926 | + ` |
1927 | + _, err := db.db.Exec(stmt) |
1928 | + return err |
1929 | + } |
1930 | diff --git a/pkg/internal/types.go b/pkg/internal/types.go |
1931 | new file mode 100644 |
1932 | index 0000000..d52c2e6 |
1933 | --- /dev/null |
1934 | +++ b/pkg/internal/types.go |
1935 | @@ -0,0 +1,144 @@ |
1936 | + package pomo |
1937 | + |
1938 | + import ( |
1939 | + "io/ioutil" |
1940 | + "os" |
1941 | + "time" |
1942 | + |
1943 | + "github.com/0xAX/notificator" |
1944 | + ) |
1945 | + |
1946 | + type State int |
1947 | + |
1948 | + func (s State) String() string { |
1949 | + switch s { |
1950 | + case RUNNING: |
1951 | + return "RUNNING" |
1952 | + case BREAKING: |
1953 | + return "BREAKING" |
1954 | + case COMPLETE: |
1955 | + return "COMPLETE" |
1956 | + case PAUSED: |
1957 | + return "PAUSED" |
1958 | + } |
1959 | + return "" |
1960 | + } |
1961 | + |
1962 | + const ( |
1963 | + RUNNING State = iota + 1 |
1964 | + BREAKING |
1965 | + COMPLETE |
1966 | + PAUSED |
1967 | + ) |
1968 | + |
1969 | + // Wheel keeps track of an ASCII spinner |
1970 | + type Wheel int |
1971 | + |
1972 | + func (w *Wheel) String() string { |
1973 | + switch int(*w) { |
1974 | + case 0: |
1975 | + *w++ |
1976 | + return "|" |
1977 | + case 1: |
1978 | + *w++ |
1979 | + return "/" |
1980 | + case 2: |
1981 | + *w++ |
1982 | + return "-" |
1983 | + case 3: |
1984 | + *w = 0 |
1985 | + return "\\" |
1986 | + } |
1987 | + return "" |
1988 | + } |
1989 | + |
1990 | + // Task describes some activity |
1991 | + type Task struct { |
1992 | + ID int `json:"id"` |
1993 | + Message string `json:"message"` |
1994 | + // Array of completed pomodoros |
1995 | + Pomodoros []*Pomodoro `json:"pomodoros"` |
1996 | + // Free-form tags associated with this task |
1997 | + Tags []string `json:"tags"` |
1998 | + // Number of pomodoros for this task |
1999 | + NPomodoros int `json:"n_pomodoros"` |
2000 | + // Duration of each pomodoro |
2001 | + Duration time.Duration `json:"duration"` |
2002 | + } |
2003 | + |
2004 | + // ByID is a sortable array of tasks |
2005 | + type ByID []*Task |
2006 | + |
2007 | + func (b ByID) Len() int { return len(b) } |
2008 | + func (b ByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
2009 | + func (b ByID) Less(i, j int) bool { return b[i].ID < b[j].ID } |
2010 | + |
2011 | + // After returns tasks that were started after the |
2012 | + // provided start time. |
2013 | + func After(start time.Time, tasks []*Task) []*Task { |
2014 | + filtered := []*Task{} |
2015 | + for _, task := range tasks { |
2016 | + if len(task.Pomodoros) > 0 { |
2017 | + if start.Before(task.Pomodoros[0].Start) { |
2018 | + filtered = append(filtered, task) |
2019 | + } |
2020 | + } |
2021 | + } |
2022 | + return filtered |
2023 | + } |
2024 | + |
2025 | + // Pomodoro is a unit of time to spend working |
2026 | + // on a single task. |
2027 | + type Pomodoro struct { |
2028 | + Start time.Time `json:"start"` |
2029 | + End time.Time `json:"end"` |
2030 | + } |
2031 | + |
2032 | + // Duration returns the runtime of the pomodoro |
2033 | + func (p Pomodoro) Duration() time.Duration { |
2034 | + return (p.End.Sub(p.Start)) |
2035 | + } |
2036 | + |
2037 | + // Status is used to communicate the state |
2038 | + // of a running Pomodoro session |
2039 | + type Status struct { |
2040 | + State State `json:"state"` |
2041 | + Remaining time.Duration `json:"remaining"` |
2042 | + Count int `json:"count"` |
2043 | + NPomodoros int `json:"n_pomodoros"` |
2044 | + } |
2045 | + |
2046 | + // Notifier sends a system notification |
2047 | + type Notifier interface { |
2048 | + Notify(string, string) error |
2049 | + } |
2050 | + |
2051 | + // NoopNotifier does nothing |
2052 | + type NoopNotifier struct{} |
2053 | + |
2054 | + func (n NoopNotifier) Notify(string, string) error { return nil } |
2055 | + |
2056 | + // Xnotifier can push notifications to mac, linux and windows. |
2057 | + type Xnotifier struct { |
2058 | + *notificator.Notificator |
2059 | + iconPath string |
2060 | + } |
2061 | + |
2062 | + func NewXnotifier(iconPath string) Notifier { |
2063 | + // Write the built-in tomato icon if it |
2064 | + // doesn't already exist. |
2065 | + _, err := os.Stat(iconPath) |
2066 | + if os.IsNotExist(err) { |
2067 | + raw := MustAsset("tomato-icon.png") |
2068 | + _ = ioutil.WriteFile(iconPath, raw, 0644) |
2069 | + } |
2070 | + return Xnotifier{ |
2071 | + Notificator: notificator.New(notificator.Options{}), |
2072 | + iconPath: iconPath, |
2073 | + } |
2074 | + } |
2075 | + |
2076 | + // Notify sends a notification to the OS. |
2077 | + func (n Xnotifier) Notify(title, body string) error { |
2078 | + return n.Push(title, body, n.iconPath, notificator.UR_NORMAL) |
2079 | + } |
2080 | diff --git a/pkg/internal/ui.go b/pkg/internal/ui.go |
2081 | new file mode 100644 |
2082 | index 0000000..4aa5f99 |
2083 | --- /dev/null |
2084 | +++ b/pkg/internal/ui.go |
2085 | @@ -0,0 +1,123 @@ |
2086 | + package pomo |
2087 | + |
2088 | + import ( |
2089 | + "fmt" |
2090 | + |
2091 | + "github.com/gizak/termui" |
2092 | + ) |
2093 | + |
2094 | + func render(wheel *Wheel, status *Status) termui.GridBufferer { |
2095 | + var text string |
2096 | + switch status.State { |
2097 | + case RUNNING: |
2098 | + text = fmt.Sprintf( |
2099 | + `[%d/%d] Pomodoros completed |
2100 | + |
2101 | + %s %s remaining |
2102 | + |
2103 | + |
2104 | + [q] - quit [p] - pause |
2105 | + `, |
2106 | + status.Count, |
2107 | + status.NPomodoros, |
2108 | + wheel, |
2109 | + status.Remaining, |
2110 | + ) |
2111 | + case BREAKING: |
2112 | + text = `It is time to take a break! |
2113 | + |
2114 | + Once you are ready, press [enter] |
2115 | + to begin the next Pomodoro. |
2116 | + |
2117 | + [q] - quit [p] - pause |
2118 | + ` |
2119 | + case PAUSED: |
2120 | + text = `Pomo is suspended. |
2121 | + |
2122 | + Press [p] to continue. |
2123 | + |
2124 | + |
2125 | + [q] - quit [p] - unpause |
2126 | + ` |
2127 | + case COMPLETE: |
2128 | + text = `This session has concluded. |
2129 | + |
2130 | + Press [q] to exit. |
2131 | + |
2132 | + |
2133 | + [q] - quit |
2134 | + ` |
2135 | + } |
2136 | + par := termui.NewPar(text) |
2137 | + par.Height = 8 |
2138 | + par.BorderLabel = fmt.Sprintf("Pomo - %s", status.State) |
2139 | + par.BorderLabelFg = termui.ColorWhite |
2140 | + par.BorderFg = termui.ColorRed |
2141 | + if status.State == RUNNING { |
2142 | + par.BorderFg = termui.ColorGreen |
2143 | + } |
2144 | + return par |
2145 | + } |
2146 | + |
2147 | + func newBlk() termui.GridBufferer { |
2148 | + blk := termui.NewBlock() |
2149 | + blk.Height = termui.TermHeight() / 3 |
2150 | + blk.Border = false |
2151 | + return blk |
2152 | + } |
2153 | + |
2154 | + func centered(part termui.GridBufferer) *termui.Grid { |
2155 | + grid := termui.NewGrid( |
2156 | + termui.NewRow( |
2157 | + termui.NewCol(12, 0, newBlk()), |
2158 | + ), |
2159 | + termui.NewRow( |
2160 | + termui.NewCol(3, 0, newBlk()), |
2161 | + termui.NewCol(6, 0, part), |
2162 | + termui.NewCol(3, 0, newBlk()), |
2163 | + ), |
2164 | + termui.NewRow( |
2165 | + termui.NewCol(12, 0, newBlk()), |
2166 | + ), |
2167 | + ) |
2168 | + grid.BgColor = termui.ThemeAttr("bg") |
2169 | + grid.Width = termui.TermWidth() |
2170 | + grid.Align() |
2171 | + return grid |
2172 | + } |
2173 | + |
2174 | + func StartUI(runner *TaskRunner) { |
2175 | + err := termui.Init() |
2176 | + if err != nil { |
2177 | + panic(err) |
2178 | + } |
2179 | + wheel := Wheel(0) |
2180 | + |
2181 | + defer termui.Close() |
2182 | + |
2183 | + termui.Render(centered(render(&wheel, runner.Status()))) |
2184 | + |
2185 | + termui.Handle("/timer/1s", func(termui.Event) { |
2186 | + termui.Render(centered(render(&wheel, runner.Status()))) |
2187 | + }) |
2188 | + |
2189 | + termui.Handle("/sys/wnd/resize", func(termui.Event) { |
2190 | + termui.Render(centered(render(&wheel, runner.Status()))) |
2191 | + }) |
2192 | + |
2193 | + termui.Handle("/sys/kbd/<enter>", func(termui.Event) { |
2194 | + runner.Toggle() |
2195 | + termui.Render(centered(render(&wheel, runner.Status()))) |
2196 | + }) |
2197 | + |
2198 | + termui.Handle("/sys/kbd/p", func(termui.Event) { |
2199 | + runner.Pause() |
2200 | + termui.Render(centered(render(&wheel, runner.Status()))) |
2201 | + }) |
2202 | + |
2203 | + termui.Handle("/sys/kbd/q", func(termui.Event) { |
2204 | + termui.StopLoop() |
2205 | + }) |
2206 | + |
2207 | + termui.Loop() |
2208 | + } |
2209 | diff --git a/pkg/internal/util.go b/pkg/internal/util.go |
2210 | new file mode 100644 |
2211 | index 0000000..70b4433 |
2212 | --- /dev/null |
2213 | +++ b/pkg/internal/util.go |
2214 | @@ -0,0 +1,81 @@ |
2215 | + package pomo |
2216 | + |
2217 | + import ( |
2218 | + "fmt" |
2219 | + "time" |
2220 | + |
2221 | + "github.com/fatih/color" |
2222 | + ) |
2223 | + |
2224 | + |
2225 | + func SummerizeTasks(config *Config, tasks []*Task) { |
2226 | + for _, task := range tasks { |
2227 | + var start string |
2228 | + if len(task.Pomodoros) > 0 { |
2229 | + start = task.Pomodoros[0].Start.Format(config.DateTimeFmt) |
2230 | + } |
2231 | + fmt.Printf("%d: [%s] [%s] ", task.ID, start, task.Duration.Truncate(time.Second)) |
2232 | + // a list of green/yellow/red pomodoros |
2233 | + // green indicates the pomodoro was finished normally |
2234 | + // yellow indicates the break was exceeded by +5minutes |
2235 | + // red indicates the pomodoro was never completed |
2236 | + fmt.Printf("[") |
2237 | + for i, pomodoro := range task.Pomodoros { |
2238 | + if i > 0 { |
2239 | + fmt.Printf(" ") |
2240 | + } |
2241 | + // pomodoro exceeded it's expected duration by more than 5m |
2242 | + if pomodoro.Duration() > task.Duration+5*time.Minute { |
2243 | + color.New(color.FgYellow).Printf("X") |
2244 | + } else { |
2245 | + // pomodoro completed normally |
2246 | + color.New(color.FgGreen).Printf("X") |
2247 | + } |
2248 | + } |
2249 | + // each missed pomodoro |
2250 | + for i := 0; i < task.NPomodoros-len(task.Pomodoros); i++ { |
2251 | + if i > 0 || i == 0 && len(task.Pomodoros) > 0 { |
2252 | + fmt.Printf(" ") |
2253 | + } |
2254 | + color.New(color.FgRed).Printf("X") |
2255 | + } |
2256 | + fmt.Printf("]") |
2257 | + // Tags |
2258 | + if len(task.Tags) > 0 { |
2259 | + fmt.Printf(" [") |
2260 | + for i, tag := range task.Tags { |
2261 | + if i > 0 && i != len(task.Tags) { |
2262 | + fmt.Printf(" ") |
2263 | + } |
2264 | + // user specified color mapping exists |
2265 | + if config.Colors != nil { |
2266 | + if color := config.Colors.Get(tag); color != nil { |
2267 | + color.Printf("%s", tag) |
2268 | + } else { |
2269 | + // no color mapping for tag |
2270 | + fmt.Printf("%s", tag) |
2271 | + } |
2272 | + } else { |
2273 | + // no color mapping |
2274 | + fmt.Printf("%s", tag) |
2275 | + } |
2276 | + |
2277 | + } |
2278 | + fmt.Printf("]") |
2279 | + } |
2280 | + fmt.Printf(" - %s", task.Message) |
2281 | + fmt.Printf("\n") |
2282 | + } |
2283 | + } |
2284 | + |
2285 | + func OutputStatus(status Status) { |
2286 | + state := "?" |
2287 | + if status.State >= RUNNING { |
2288 | + state = string(status.State.String()[0]) |
2289 | + } |
2290 | + if status.State == RUNNING { |
2291 | + fmt.Printf("%s [%d/%d] %s", state, status.Count, status.NPomodoros, status.Remaining) |
2292 | + } else { |
2293 | + fmt.Printf("%s [%d/%d] -", state, status.Count, status.NPomodoros) |
2294 | + } |
2295 | + } |
2296 | diff --git a/pkg/internal/version.go b/pkg/internal/version.go |
2297 | new file mode 100644 |
2298 | index 0000000..aad0e90 |
2299 | --- /dev/null |
2300 | +++ b/pkg/internal/version.go |
2301 | @@ -0,0 +1,3 @@ |
2302 | + package pomo |
2303 | + |
2304 | + var Version = "undefined" |
2305 | diff --git a/runner.go b/runner.go |
2306 | deleted file mode 100644 |
2307 | index c6376e1..0000000 |
2308 | --- a/runner.go |
2309 | +++ /dev/null |
2310 | @@ -1,151 +0,0 @@ |
2311 | - package main |
2312 | - |
2313 | - import ( |
2314 | - "database/sql" |
2315 | - "time" |
2316 | - ) |
2317 | - |
2318 | - type TaskRunner struct { |
2319 | - count int |
2320 | - taskID int |
2321 | - taskMessage string |
2322 | - nPomodoros int |
2323 | - origDuration time.Duration |
2324 | - state State |
2325 | - store *Store |
2326 | - started time.Time |
2327 | - pause chan bool |
2328 | - toggle chan bool |
2329 | - notifier Notifier |
2330 | - duration time.Duration |
2331 | - } |
2332 | - |
2333 | - func NewMockedTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) { |
2334 | - tr := &TaskRunner{ |
2335 | - taskID: task.ID, |
2336 | - taskMessage: task.Message, |
2337 | - nPomodoros: task.NPomodoros, |
2338 | - origDuration: task.Duration, |
2339 | - store: store, |
2340 | - state: State(0), |
2341 | - pause: make(chan bool), |
2342 | - toggle: make(chan bool), |
2343 | - notifier: notifier, |
2344 | - duration: task.Duration, |
2345 | - } |
2346 | - return tr, nil |
2347 | - } |
2348 | - func NewTaskRunner(task *Task, config *Config) (*TaskRunner, error) { |
2349 | - store, err := NewStore(config.DBPath) |
2350 | - if err != nil { |
2351 | - return nil, err |
2352 | - } |
2353 | - tr := &TaskRunner{ |
2354 | - taskID: task.ID, |
2355 | - taskMessage: task.Message, |
2356 | - nPomodoros: task.NPomodoros, |
2357 | - origDuration: task.Duration, |
2358 | - store: store, |
2359 | - state: State(0), |
2360 | - pause: make(chan bool), |
2361 | - toggle: make(chan bool), |
2362 | - notifier: NewXnotifier(config.IconPath), |
2363 | - duration: task.Duration, |
2364 | - } |
2365 | - return tr, nil |
2366 | - } |
2367 | - |
2368 | - func (t *TaskRunner) Start() { |
2369 | - go t.run() |
2370 | - } |
2371 | - |
2372 | - func (t *TaskRunner) TimeRemaining() time.Duration { |
2373 | - return (t.duration - time.Since(t.started)).Truncate(time.Second) |
2374 | - } |
2375 | - |
2376 | - func (t *TaskRunner) SetState(state State) { |
2377 | - t.state = state |
2378 | - } |
2379 | - |
2380 | - func (t *TaskRunner) run() error { |
2381 | - for t.count < t.nPomodoros { |
2382 | - // Create a new pomodoro where we |
2383 | - // track the start / end time of |
2384 | - // of this session. |
2385 | - pomodoro := &Pomodoro{} |
2386 | - // Start this pomodoro |
2387 | - pomodoro.Start = time.Now() |
2388 | - // Set state to RUNNIN |
2389 | - t.SetState(RUNNING) |
2390 | - // Create a new timer |
2391 | - timer := time.NewTimer(t.duration) |
2392 | - // Record our started time |
2393 | - t.started = pomodoro.Start |
2394 | - loop: |
2395 | - select { |
2396 | - case <-timer.C: |
2397 | - t.SetState(BREAKING) |
2398 | - t.count++ |
2399 | - case <-t.toggle: |
2400 | - // Catch any toggles when we |
2401 | - // are not expecting them |
2402 | - goto loop |
2403 | - case <-t.pause: |
2404 | - timer.Stop() |
2405 | - // Record the remaining time of the current pomodoro |
2406 | - remaining := t.TimeRemaining() |
2407 | - // Change state to PAUSED |
2408 | - t.SetState(PAUSED) |
2409 | - // Wait for the user to press [p] |
2410 | - <-t.pause |
2411 | - // Resume the timer with previous |
2412 | - // remaining time |
2413 | - timer.Reset(remaining) |
2414 | - // Change duration |
2415 | - t.started = time.Now() |
2416 | - t.duration = remaining |
2417 | - // Restore state to RUNNING |
2418 | - t.SetState(RUNNING) |
2419 | - goto loop |
2420 | - } |
2421 | - pomodoro.End = time.Now() |
2422 | - err := t.store.With(func(tx *sql.Tx) error { |
2423 | - return t.store.CreatePomodoro(tx, t.taskID, *pomodoro) |
2424 | - }) |
2425 | - if err != nil { |
2426 | - return err |
2427 | - } |
2428 | - // All pomodoros completed |
2429 | - if t.count == t.nPomodoros { |
2430 | - break |
2431 | - } |
2432 | - |
2433 | - t.notifier.Notify("Pomo", "It is time to take a break!") |
2434 | - // Reset the duration incase it |
2435 | - // was paused. |
2436 | - t.duration = t.origDuration |
2437 | - // User concludes the break |
2438 | - <-t.toggle |
2439 | - |
2440 | - } |
2441 | - t.notifier.Notify("Pomo", "Pomo session has completed!") |
2442 | - t.SetState(COMPLETE) |
2443 | - return nil |
2444 | - } |
2445 | - |
2446 | - func (t *TaskRunner) Toggle() { |
2447 | - t.toggle <- true |
2448 | - } |
2449 | - |
2450 | - func (t *TaskRunner) Pause() { |
2451 | - t.pause <- true |
2452 | - } |
2453 | - |
2454 | - func (t *TaskRunner) Status() *Status { |
2455 | - return &Status{ |
2456 | - State: t.state, |
2457 | - Count: t.count, |
2458 | - NPomodoros: t.nPomodoros, |
2459 | - Remaining: t.TimeRemaining(), |
2460 | - } |
2461 | - } |
2462 | diff --git a/runner_test.go b/runner_test.go |
2463 | deleted file mode 100644 |
2464 | index b11c628..0000000 |
2465 | --- a/runner_test.go |
2466 | +++ /dev/null |
2467 | @@ -1,37 +0,0 @@ |
2468 | - package main |
2469 | - |
2470 | - import ( |
2471 | - "fmt" |
2472 | - "io/ioutil" |
2473 | - "path" |
2474 | - "testing" |
2475 | - "time" |
2476 | - ) |
2477 | - |
2478 | - func TestTaskRunner(t *testing.T) { |
2479 | - baseDir, _ := ioutil.TempDir("/tmp", "") |
2480 | - store, err := NewStore(path.Join(baseDir, "pomo.db")) |
2481 | - if err != nil { |
2482 | - t.Error(err) |
2483 | - } |
2484 | - err = initDB(store) |
2485 | - if err != nil { |
2486 | - t.Error(err) |
2487 | - } |
2488 | - runner, err := NewMockedTaskRunner(&Task{ |
2489 | - Duration: time.Second * 2, |
2490 | - NPomodoros: 2, |
2491 | - Message: fmt.Sprint("Test Task"), |
2492 | - }, store, NoopNotifier{}) |
2493 | - if err != nil { |
2494 | - t.Error(err) |
2495 | - } |
2496 | - |
2497 | - runner.Start() |
2498 | - |
2499 | - runner.Toggle() |
2500 | - runner.Toggle() |
2501 | - |
2502 | - runner.Toggle() |
2503 | - runner.Toggle() |
2504 | - } |
2505 | diff --git a/server.go b/server.go |
2506 | deleted file mode 100644 |
2507 | index 87c5c7e..0000000 |
2508 | --- a/server.go |
2509 | +++ /dev/null |
2510 | @@ -1,93 +0,0 @@ |
2511 | - package main |
2512 | - |
2513 | - import ( |
2514 | - "encoding/json" |
2515 | - "errors" |
2516 | - "fmt" |
2517 | - "net" |
2518 | - "os" |
2519 | - ) |
2520 | - |
2521 | - // Server listens on a Unix domain socket |
2522 | - // for Pomo status requests |
2523 | - type Server struct { |
2524 | - listener net.Listener |
2525 | - runner *TaskRunner |
2526 | - running bool |
2527 | - } |
2528 | - |
2529 | - func (s *Server) listen() { |
2530 | - for s.running { |
2531 | - conn, err := s.listener.Accept() |
2532 | - if err != nil { |
2533 | - break |
2534 | - } |
2535 | - buf := make([]byte, 512) |
2536 | - // Ignore any content |
2537 | - conn.Read(buf) |
2538 | - raw, _ := json.Marshal(s.runner.Status()) |
2539 | - conn.Write(raw) |
2540 | - conn.Close() |
2541 | - } |
2542 | - } |
2543 | - |
2544 | - func (s *Server) Start() { |
2545 | - s.running = true |
2546 | - go s.listen() |
2547 | - } |
2548 | - |
2549 | - func (s *Server) Stop() { |
2550 | - s.running = false |
2551 | - s.listener.Close() |
2552 | - } |
2553 | - |
2554 | - func NewServer(runner *TaskRunner, config *Config) (*Server, error) { |
2555 | - //check if socket file exists |
2556 | - if _, err := os.Stat(config.SocketPath); err == nil { |
2557 | - _, err := net.Dial("unix", config.SocketPath) |
2558 | - //if error then sock file was saved after crash |
2559 | - if err != nil { |
2560 | - os.Remove(config.SocketPath) |
2561 | - } else { |
2562 | - // another instance of pomo is running |
2563 | - return nil, errors.New(fmt.Sprintf("Socket %s is already in use", config.SocketPath)) |
2564 | - } |
2565 | - } |
2566 | - listener, err := net.Listen("unix", config.SocketPath) |
2567 | - if err != nil { |
2568 | - return nil, err |
2569 | - } |
2570 | - return &Server{listener: listener, runner: runner}, nil |
2571 | - } |
2572 | - |
2573 | - // Client makes requests to a listening |
2574 | - // pomo server to check the status of |
2575 | - // any currently running task session. |
2576 | - type Client struct { |
2577 | - conn net.Conn |
2578 | - } |
2579 | - |
2580 | - func (c Client) read(statusCh chan *Status) { |
2581 | - buf := make([]byte, 512) |
2582 | - n, _ := c.conn.Read(buf) |
2583 | - status := &Status{} |
2584 | - json.Unmarshal(buf[0:n], status) |
2585 | - statusCh <- status |
2586 | - } |
2587 | - |
2588 | - func (c Client) Status() (*Status, error) { |
2589 | - statusCh := make(chan *Status) |
2590 | - c.conn.Write([]byte("status")) |
2591 | - go c.read(statusCh) |
2592 | - return <-statusCh, nil |
2593 | - } |
2594 | - |
2595 | - func (c Client) Close() error { return c.conn.Close() } |
2596 | - |
2597 | - func NewClient(path string) (*Client, error) { |
2598 | - conn, err := net.Dial("unix", path) |
2599 | - if err != nil { |
2600 | - return nil, err |
2601 | - } |
2602 | - return &Client{conn: conn}, nil |
2603 | - } |
2604 | diff --git a/store.go b/store.go |
2605 | deleted file mode 100644 |
2606 | index f3bf795..0000000 |
2607 | --- a/store.go |
2608 | +++ /dev/null |
2609 | @@ -1,187 +0,0 @@ |
2610 | - package main |
2611 | - |
2612 | - import ( |
2613 | - "database/sql" |
2614 | - "strings" |
2615 | - "time" |
2616 | - |
2617 | - _ "github.com/mattn/go-sqlite3" |
2618 | - ) |
2619 | - |
2620 | - // 2018-01-16 19:05:21.752851759+08:00 |
2621 | - const datetimeFmt = "2006-01-02 15:04:05.999999999-07:00" |
2622 | - |
2623 | - type StoreFunc func(tx *sql.Tx) error |
2624 | - |
2625 | - type Store struct { |
2626 | - db *sql.DB |
2627 | - } |
2628 | - |
2629 | - func NewStore(path string) (*Store, error) { |
2630 | - db, err := sql.Open("sqlite3", path) |
2631 | - if err != nil { |
2632 | - return nil, err |
2633 | - } |
2634 | - return &Store{db: db}, nil |
2635 | - } |
2636 | - |
2637 | - // With applies all of the given functions with |
2638 | - // a single transaction, rolling back on failure |
2639 | - // and commiting on success. |
2640 | - func (s Store) With(fns ...func(tx *sql.Tx) error) error { |
2641 | - tx, err := s.db.Begin() |
2642 | - if err != nil { |
2643 | - return err |
2644 | - } |
2645 | - for _, fn := range fns { |
2646 | - err = fn(tx) |
2647 | - if err != nil { |
2648 | - tx.Rollback() |
2649 | - return err |
2650 | - } |
2651 | - } |
2652 | - return tx.Commit() |
2653 | - } |
2654 | - |
2655 | - func (s Store) CreateTask(tx *sql.Tx, task Task) (int, error) { |
2656 | - var taskID int |
2657 | - _, err := tx.Exec( |
2658 | - "INSERT INTO task (message,pomodoros,duration,tags) VALUES ($1,$2,$3,$4)", |
2659 | - task.Message, task.NPomodoros, task.Duration.String(), strings.Join(task.Tags, ",")) |
2660 | - if err != nil { |
2661 | - return -1, err |
2662 | - } |
2663 | - err = tx.QueryRow("SELECT last_insert_rowid() FROM task").Scan(&taskID) |
2664 | - if err != nil { |
2665 | - return -1, err |
2666 | - } |
2667 | - err = tx.QueryRow("SELECT last_insert_rowid() FROM task").Scan(&taskID) |
2668 | - if err != nil { |
2669 | - return -1, err |
2670 | - } |
2671 | - return taskID, nil |
2672 | - } |
2673 | - |
2674 | - func (s Store) ReadTasks(tx *sql.Tx) ([]*Task, error) { |
2675 | - rows, err := tx.Query(`SELECT rowid,message,pomodoros,duration,tags FROM task`) |
2676 | - if err != nil { |
2677 | - return nil, err |
2678 | - } |
2679 | - tasks := []*Task{} |
2680 | - for rows.Next() { |
2681 | - var ( |
2682 | - tags string |
2683 | - strDuration string |
2684 | - ) |
2685 | - task := &Task{Pomodoros: []*Pomodoro{}} |
2686 | - err = rows.Scan(&task.ID, &task.Message, &task.NPomodoros, &strDuration, &tags) |
2687 | - if err != nil { |
2688 | - return nil, err |
2689 | - } |
2690 | - duration, _ := time.ParseDuration(strDuration) |
2691 | - task.Duration = duration |
2692 | - if tags != "" { |
2693 | - task.Tags = strings.Split(tags, ",") |
2694 | - } |
2695 | - pomodoros, err := s.ReadPomodoros(tx, task.ID) |
2696 | - if err != nil { |
2697 | - return nil, err |
2698 | - } |
2699 | - for _, pomodoro := range pomodoros { |
2700 | - task.Pomodoros = append(task.Pomodoros, pomodoro) |
2701 | - } |
2702 | - tasks = append(tasks, task) |
2703 | - } |
2704 | - return tasks, nil |
2705 | - } |
2706 | - |
2707 | - func (s Store) DeleteTask(tx *sql.Tx, taskID int) error { |
2708 | - _, err := tx.Exec("DELETE FROM task WHERE rowid = $1", &taskID) |
2709 | - if err != nil { |
2710 | - return err |
2711 | - } |
2712 | - _, err = tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
2713 | - if err != nil { |
2714 | - return err |
2715 | - } |
2716 | - return nil |
2717 | - } |
2718 | - |
2719 | - func (s Store) ReadTask(tx *sql.Tx, taskID int) (*Task, error) { |
2720 | - task := &Task{} |
2721 | - var ( |
2722 | - tags string |
2723 | - strDuration string |
2724 | - ) |
2725 | - err := tx.QueryRow(`SELECT rowid,message,pomodoros,duration,tags FROM task WHERE rowid = $1`, &taskID). |
2726 | - Scan(&task.ID, &task.Message, &task.NPomodoros, &strDuration, &tags) |
2727 | - if err != nil { |
2728 | - return nil, err |
2729 | - } |
2730 | - duration, _ := time.ParseDuration(strDuration) |
2731 | - task.Duration = duration |
2732 | - if tags != "" { |
2733 | - task.Tags = strings.Split(tags, ",") |
2734 | - } |
2735 | - return task, nil |
2736 | - } |
2737 | - |
2738 | - func (s Store) CreatePomodoro(tx *sql.Tx, taskID int, pomodoro Pomodoro) error { |
2739 | - _, err := tx.Exec( |
2740 | - `INSERT INTO pomodoro (task_id, start, end) VALUES ($1, $2, $3)`, |
2741 | - taskID, |
2742 | - pomodoro.Start, |
2743 | - pomodoro.End, |
2744 | - ) |
2745 | - return err |
2746 | - } |
2747 | - |
2748 | - func (s Store) ReadPomodoros(tx *sql.Tx, taskID int) ([]*Pomodoro, error) { |
2749 | - rows, err := tx.Query(`SELECT start,end FROM pomodoro WHERE task_id = $1`, &taskID) |
2750 | - if err != nil { |
2751 | - return nil, err |
2752 | - } |
2753 | - pomodoros := []*Pomodoro{} |
2754 | - for rows.Next() { |
2755 | - var ( |
2756 | - startStr string |
2757 | - endStr string |
2758 | - ) |
2759 | - pomodoro := &Pomodoro{} |
2760 | - err = rows.Scan(&startStr, &endStr) |
2761 | - if err != nil { |
2762 | - return nil, err |
2763 | - } |
2764 | - start, _ := time.Parse(datetimeFmt, startStr) |
2765 | - end, _ := time.Parse(datetimeFmt, endStr) |
2766 | - pomodoro.Start = start |
2767 | - pomodoro.End = end |
2768 | - pomodoros = append(pomodoros, pomodoro) |
2769 | - } |
2770 | - return pomodoros, nil |
2771 | - } |
2772 | - |
2773 | - func (s Store) DeletePomodoros(tx *sql.Tx, taskID int) error { |
2774 | - _, err := tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
2775 | - return err |
2776 | - } |
2777 | - |
2778 | - func (s Store) Close() error { return s.db.Close() } |
2779 | - |
2780 | - func initDB(db *Store) error { |
2781 | - stmt := ` |
2782 | - CREATE TABLE task ( |
2783 | - message TEXT, |
2784 | - pomodoros INTEGER, |
2785 | - duration TEXT, |
2786 | - tags TEXT |
2787 | - ); |
2788 | - CREATE TABLE pomodoro ( |
2789 | - task_id INTEGER, |
2790 | - start DATETTIME, |
2791 | - end DATETTIME |
2792 | - ); |
2793 | - ` |
2794 | - _, err := db.db.Exec(stmt) |
2795 | - return err |
2796 | - } |
2797 | diff --git a/types.go b/types.go |
2798 | deleted file mode 100644 |
2799 | index d248aed..0000000 |
2800 | --- a/types.go |
2801 | +++ /dev/null |
2802 | @@ -1,144 +0,0 @@ |
2803 | - package main |
2804 | - |
2805 | - import ( |
2806 | - "io/ioutil" |
2807 | - "os" |
2808 | - "time" |
2809 | - |
2810 | - "github.com/0xAX/notificator" |
2811 | - ) |
2812 | - |
2813 | - type State int |
2814 | - |
2815 | - func (s State) String() string { |
2816 | - switch s { |
2817 | - case RUNNING: |
2818 | - return "RUNNING" |
2819 | - case BREAKING: |
2820 | - return "BREAKING" |
2821 | - case COMPLETE: |
2822 | - return "COMPLETE" |
2823 | - case PAUSED: |
2824 | - return "PAUSED" |
2825 | - } |
2826 | - return "" |
2827 | - } |
2828 | - |
2829 | - const ( |
2830 | - RUNNING State = iota + 1 |
2831 | - BREAKING |
2832 | - COMPLETE |
2833 | - PAUSED |
2834 | - ) |
2835 | - |
2836 | - // Wheel keeps track of an ASCII spinner |
2837 | - type Wheel int |
2838 | - |
2839 | - func (w *Wheel) String() string { |
2840 | - switch int(*w) { |
2841 | - case 0: |
2842 | - *w++ |
2843 | - return "|" |
2844 | - case 1: |
2845 | - *w++ |
2846 | - return "/" |
2847 | - case 2: |
2848 | - *w++ |
2849 | - return "-" |
2850 | - case 3: |
2851 | - *w = 0 |
2852 | - return "\\" |
2853 | - } |
2854 | - return "" |
2855 | - } |
2856 | - |
2857 | - // Task describes some activity |
2858 | - type Task struct { |
2859 | - ID int `json:"id"` |
2860 | - Message string `json:"message"` |
2861 | - // Array of completed pomodoros |
2862 | - Pomodoros []*Pomodoro `json:"pomodoros"` |
2863 | - // Free-form tags associated with this task |
2864 | - Tags []string `json:"tags"` |
2865 | - // Number of pomodoros for this task |
2866 | - NPomodoros int `json:"n_pomodoros"` |
2867 | - // Duration of each pomodoro |
2868 | - Duration time.Duration `json:"duration"` |
2869 | - } |
2870 | - |
2871 | - // ByID is a sortable array of tasks |
2872 | - type ByID []*Task |
2873 | - |
2874 | - func (b ByID) Len() int { return len(b) } |
2875 | - func (b ByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
2876 | - func (b ByID) Less(i, j int) bool { return b[i].ID < b[j].ID } |
2877 | - |
2878 | - // After returns tasks that were started after the |
2879 | - // provided start time. |
2880 | - func After(start time.Time, tasks []*Task) []*Task { |
2881 | - filtered := []*Task{} |
2882 | - for _, task := range tasks { |
2883 | - if len(task.Pomodoros) > 0 { |
2884 | - if start.Before(task.Pomodoros[0].Start) { |
2885 | - filtered = append(filtered, task) |
2886 | - } |
2887 | - } |
2888 | - } |
2889 | - return filtered |
2890 | - } |
2891 | - |
2892 | - // Pomodoro is a unit of time to spend working |
2893 | - // on a single task. |
2894 | - type Pomodoro struct { |
2895 | - Start time.Time `json:"start"` |
2896 | - End time.Time `json:"end"` |
2897 | - } |
2898 | - |
2899 | - // Duration returns the runtime of the pomodoro |
2900 | - func (p Pomodoro) Duration() time.Duration { |
2901 | - return (p.End.Sub(p.Start)) |
2902 | - } |
2903 | - |
2904 | - // Status is used to communicate the state |
2905 | - // of a running Pomodoro session |
2906 | - type Status struct { |
2907 | - State State `json:"state"` |
2908 | - Remaining time.Duration `json:"remaining"` |
2909 | - Count int `json:"count"` |
2910 | - NPomodoros int `json:"n_pomodoros"` |
2911 | - } |
2912 | - |
2913 | - // Notifier sends a system notification |
2914 | - type Notifier interface { |
2915 | - Notify(string, string) error |
2916 | - } |
2917 | - |
2918 | - // NoopNotifier does nothing |
2919 | - type NoopNotifier struct{} |
2920 | - |
2921 | - func (n NoopNotifier) Notify(string, string) error { return nil } |
2922 | - |
2923 | - // Xnotifier can push notifications to mac, linux and windows. |
2924 | - type Xnotifier struct { |
2925 | - *notificator.Notificator |
2926 | - iconPath string |
2927 | - } |
2928 | - |
2929 | - func NewXnotifier(iconPath string) Notifier { |
2930 | - // Write the built-in tomato icon if it |
2931 | - // doesn't already exist. |
2932 | - _, err := os.Stat(iconPath) |
2933 | - if os.IsNotExist(err) { |
2934 | - raw := MustAsset("tomato-icon.png") |
2935 | - _ = ioutil.WriteFile(iconPath, raw, 0644) |
2936 | - } |
2937 | - return Xnotifier{ |
2938 | - Notificator: notificator.New(notificator.Options{}), |
2939 | - iconPath: iconPath, |
2940 | - } |
2941 | - } |
2942 | - |
2943 | - // Notify sends a notification to the OS. |
2944 | - func (n Xnotifier) Notify(title, body string) error { |
2945 | - return n.Push(title, body, n.iconPath, notificator.UR_NORMAL) |
2946 | - } |
2947 | diff --git a/ui.go b/ui.go |
2948 | deleted file mode 100644 |
2949 | index f9b42fc..0000000 |
2950 | --- a/ui.go |
2951 | +++ /dev/null |
2952 | @@ -1,123 +0,0 @@ |
2953 | - package main |
2954 | - |
2955 | - import ( |
2956 | - "fmt" |
2957 | - |
2958 | - "github.com/gizak/termui" |
2959 | - ) |
2960 | - |
2961 | - func render(wheel *Wheel, status *Status) termui.GridBufferer { |
2962 | - var text string |
2963 | - switch status.State { |
2964 | - case RUNNING: |
2965 | - text = fmt.Sprintf( |
2966 | - `[%d/%d] Pomodoros completed |
2967 | - |
2968 | - %s %s remaining |
2969 | - |
2970 | - |
2971 | - [q] - quit [p] - pause |
2972 | - `, |
2973 | - status.Count, |
2974 | - status.NPomodoros, |
2975 | - wheel, |
2976 | - status.Remaining, |
2977 | - ) |
2978 | - case BREAKING: |
2979 | - text = `It is time to take a break! |
2980 | - |
2981 | - Once you are ready, press [enter] |
2982 | - to begin the next Pomodoro. |
2983 | - |
2984 | - [q] - quit [p] - pause |
2985 | - ` |
2986 | - case PAUSED: |
2987 | - text = `Pomo is suspended. |
2988 | - |
2989 | - Press [p] to continue. |
2990 | - |
2991 | - |
2992 | - [q] - quit [p] - unpause |
2993 | - ` |
2994 | - case COMPLETE: |
2995 | - text = `This session has concluded. |
2996 | - |
2997 | - Press [q] to exit. |
2998 | - |
2999 | - |
3000 | - [q] - quit |
3001 | - ` |
3002 | - } |
3003 | - par := termui.NewPar(text) |
3004 | - par.Height = 8 |
3005 | - par.BorderLabel = fmt.Sprintf("Pomo - %s", status.State) |
3006 | - par.BorderLabelFg = termui.ColorWhite |
3007 | - par.BorderFg = termui.ColorRed |
3008 | - if status.State == RUNNING { |
3009 | - par.BorderFg = termui.ColorGreen |
3010 | - } |
3011 | - return par |
3012 | - } |
3013 | - |
3014 | - func newBlk() termui.GridBufferer { |
3015 | - blk := termui.NewBlock() |
3016 | - blk.Height = termui.TermHeight() / 3 |
3017 | - blk.Border = false |
3018 | - return blk |
3019 | - } |
3020 | - |
3021 | - func centered(part termui.GridBufferer) *termui.Grid { |
3022 | - grid := termui.NewGrid( |
3023 | - termui.NewRow( |
3024 | - termui.NewCol(12, 0, newBlk()), |
3025 | - ), |
3026 | - termui.NewRow( |
3027 | - termui.NewCol(3, 0, newBlk()), |
3028 | - termui.NewCol(6, 0, part), |
3029 | - termui.NewCol(3, 0, newBlk()), |
3030 | - ), |
3031 | - termui.NewRow( |
3032 | - termui.NewCol(12, 0, newBlk()), |
3033 | - ), |
3034 | - ) |
3035 | - grid.BgColor = termui.ThemeAttr("bg") |
3036 | - grid.Width = termui.TermWidth() |
3037 | - grid.Align() |
3038 | - return grid |
3039 | - } |
3040 | - |
3041 | - func startUI(runner *TaskRunner) { |
3042 | - err := termui.Init() |
3043 | - if err != nil { |
3044 | - panic(err) |
3045 | - } |
3046 | - wheel := Wheel(0) |
3047 | - |
3048 | - defer termui.Close() |
3049 | - |
3050 | - termui.Render(centered(render(&wheel, runner.Status()))) |
3051 | - |
3052 | - termui.Handle("/timer/1s", func(termui.Event) { |
3053 | - termui.Render(centered(render(&wheel, runner.Status()))) |
3054 | - }) |
3055 | - |
3056 | - termui.Handle("/sys/wnd/resize", func(termui.Event) { |
3057 | - termui.Render(centered(render(&wheel, runner.Status()))) |
3058 | - }) |
3059 | - |
3060 | - termui.Handle("/sys/kbd/<enter>", func(termui.Event) { |
3061 | - runner.Toggle() |
3062 | - termui.Render(centered(render(&wheel, runner.Status()))) |
3063 | - }) |
3064 | - |
3065 | - termui.Handle("/sys/kbd/p", func(termui.Event) { |
3066 | - runner.Pause() |
3067 | - termui.Render(centered(render(&wheel, runner.Status()))) |
3068 | - }) |
3069 | - |
3070 | - termui.Handle("/sys/kbd/q", func(termui.Event) { |
3071 | - termui.StopLoop() |
3072 | - }) |
3073 | - |
3074 | - termui.Loop() |
3075 | - } |
3076 | diff --git a/util.go b/util.go |
3077 | deleted file mode 100644 |
3078 | index 4c2425f..0000000 |
3079 | --- a/util.go |
3080 | +++ /dev/null |
3081 | @@ -1,96 +0,0 @@ |
3082 | - package main |
3083 | - |
3084 | - import ( |
3085 | - "fmt" |
3086 | - "os" |
3087 | - "os/user" |
3088 | - "path" |
3089 | - "time" |
3090 | - |
3091 | - "github.com/fatih/color" |
3092 | - ) |
3093 | - |
3094 | - func maybe(err error) { |
3095 | - if err != nil { |
3096 | - fmt.Printf("Error:\n%s\n", err) |
3097 | - os.Exit(1) |
3098 | - } |
3099 | - } |
3100 | - |
3101 | - func defaultConfigPath() string { |
3102 | - u, err := user.Current() |
3103 | - maybe(err) |
3104 | - return path.Join(u.HomeDir, "/.pomo/config.json") |
3105 | - } |
3106 | - |
3107 | - func summerizeTasks(config *Config, tasks []*Task) { |
3108 | - for _, task := range tasks { |
3109 | - var start string |
3110 | - if len(task.Pomodoros) > 0 { |
3111 | - start = task.Pomodoros[0].Start.Format(config.DateTimeFmt) |
3112 | - } |
3113 | - fmt.Printf("%d: [%s] [%s] ", task.ID, start, task.Duration.Truncate(time.Second)) |
3114 | - // a list of green/yellow/red pomodoros |
3115 | - // green indicates the pomodoro was finished normally |
3116 | - // yellow indicates the break was exceeded by +5minutes |
3117 | - // red indicates the pomodoro was never completed |
3118 | - fmt.Printf("[") |
3119 | - for i, pomodoro := range task.Pomodoros { |
3120 | - if i > 0 { |
3121 | - fmt.Printf(" ") |
3122 | - } |
3123 | - // pomodoro exceeded it's expected duration by more than 5m |
3124 | - if pomodoro.Duration() > task.Duration+5*time.Minute { |
3125 | - color.New(color.FgYellow).Printf("X") |
3126 | - } else { |
3127 | - // pomodoro completed normally |
3128 | - color.New(color.FgGreen).Printf("X") |
3129 | - } |
3130 | - } |
3131 | - // each missed pomodoro |
3132 | - for i := 0; i < task.NPomodoros-len(task.Pomodoros); i++ { |
3133 | - if i > 0 || i == 0 && len(task.Pomodoros) > 0 { |
3134 | - fmt.Printf(" ") |
3135 | - } |
3136 | - color.New(color.FgRed).Printf("X") |
3137 | - } |
3138 | - fmt.Printf("]") |
3139 | - // Tags |
3140 | - if len(task.Tags) > 0 { |
3141 | - fmt.Printf(" [") |
3142 | - for i, tag := range task.Tags { |
3143 | - if i > 0 && i != len(task.Tags) { |
3144 | - fmt.Printf(" ") |
3145 | - } |
3146 | - // user specified color mapping exists |
3147 | - if config.Colors != nil { |
3148 | - if color := config.Colors.Get(tag); color != nil { |
3149 | - color.Printf("%s", tag) |
3150 | - } else { |
3151 | - // no color mapping for tag |
3152 | - fmt.Printf("%s", tag) |
3153 | - } |
3154 | - } else { |
3155 | - // no color mapping |
3156 | - fmt.Printf("%s", tag) |
3157 | - } |
3158 | - |
3159 | - } |
3160 | - fmt.Printf("]") |
3161 | - } |
3162 | - fmt.Printf(" - %s", task.Message) |
3163 | - fmt.Printf("\n") |
3164 | - } |
3165 | - } |
3166 | - |
3167 | - func outputStatus(status Status) { |
3168 | - state := "?" |
3169 | - if status.State >= RUNNING { |
3170 | - state = string(status.State.String()[0]) |
3171 | - } |
3172 | - if status.State == RUNNING { |
3173 | - fmt.Printf("%s [%d/%d] %s", state, status.Count, status.NPomodoros, status.Remaining) |
3174 | - } else { |
3175 | - fmt.Printf("%s [%d/%d] -", state, status.Count, status.NPomodoros) |
3176 | - } |
3177 | - } |
3178 | diff --git a/version.go b/version.go |
3179 | deleted file mode 100644 |
3180 | index 3cb07c8..0000000 |
3181 | --- a/version.go |
3182 | +++ /dev/null |
3183 | @@ -1,3 +0,0 @@ |
3184 | - package main |
3185 | - |
3186 | - var Version = "undefined" |