timr 0.1.0.pre.dev.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b78ed2b17c8e1324c1e8ce4ea8d331955c861227
4
+ data.tar.gz: cc2b02d5c1d05dd690ad117e76b5b0cc3e17df1f
5
+ SHA512:
6
+ metadata.gz: 984d1c45cf0e32ef972ff0f94c427fa792071c5f6520235b97acc35952b151ee2619ca00025ad5da692793e8d6a1eb890d3874e0f21941ac5d5b2f6cc9ae87c0
7
+ data.tar.gz: 5bcbacb70f1b9412213d3b7b7214e38c7b081ef2357de42be7594f70e25d7571a36b4e982c80df9d0b18cc7760d1c57a8a7b416eb77d5ad0cda249330ae53ddc
@@ -0,0 +1,10 @@
1
+ # editorconfig.org
2
+
3
+ root = true
4
+
5
+ [*]
6
+ indent_style = tab
7
+ end_of_line = lf
8
+ charset = utf-8
9
+ trim_trailing_whitespace = false
10
+ insert_final_newline = true
@@ -0,0 +1,5 @@
1
+ /.bundle/
2
+ .setup
3
+ /releases/
4
+ README.html
5
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+
2
+ source 'https://rubygems.org'
3
+ gemspec
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ timr (0.1.0.pre.dev.1)
5
+ curses (~> 1.0)
6
+ thefox-ext (~> 1.4)
7
+ uuid (~> 2.3)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ curses (1.0.2)
13
+ macaddr (1.7.1)
14
+ systemu (~> 2.6.2)
15
+ minitest (5.8.4)
16
+ systemu (2.6.5)
17
+ thefox-ext (1.4.1)
18
+ uuid (2.3.8)
19
+ macaddr (~> 1.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ minitest (~> 5.8)
26
+ timr!
27
+
28
+ BUNDLED WITH
29
+ 1.11.2
@@ -0,0 +1,12 @@
1
+
2
+ GEM_NAME = timr
3
+ ALL_TARGETS_EXT = tmp
4
+
5
+ include Makefile.common
6
+
7
+ dev:
8
+ RUBYOPT=-rbundler/setup ruby ./bin/timr
9
+
10
+ .PHONY: test
11
+ test:
12
+ RUBYOPT=-w $(BUNDLER) exec ./tests/ts_all.rb
@@ -0,0 +1,58 @@
1
+
2
+ # Ruby Common Big
3
+ # 2016-04-09
4
+
5
+ MV = mv -nv
6
+ RM = rm -rf
7
+ MKDIR = mkdir -p
8
+ CHMOD = chmod
9
+ BUNDLER = bundle
10
+ BUNDLER_OPTIONS = --jobs=5 --retry=3
11
+ GEMSPEC_FILE = $(GEM_NAME).gemspec
12
+
13
+ .PHONY: all
14
+ all: setup $(ALL_TARGETS_EXT)
15
+
16
+ .PHONY: setup
17
+ setup: .setup
18
+
19
+ .setup:
20
+ $(BUNDLER) install $(BUNDLER_OPTIONS)
21
+ touch $@
22
+
23
+ .PHONY: install
24
+ install:
25
+ gem_file=$$(gem build $(GEMSPEC_FILE) | grep 'File:' | tail -1 | awk '{ print $$2 }'); \
26
+ sudo gem install $$gem_file; \
27
+ $(RM) $$gem_file
28
+
29
+ .PHONY: uninstall
30
+ uninstall:
31
+ sudo gem uninstall $(GEM_NAME)
32
+
33
+ .PHONY: update
34
+ update:
35
+ $(BUNDLER) update
36
+
37
+ .PHONY: clean
38
+ clean:
39
+ $(RM) .bundle
40
+ $(RM) .setup
41
+ $(RM) Gemfile.lock
42
+
43
+ .PHONY: release
44
+ release: | releases
45
+ set -e; \
46
+ gem_file=$$(gem build $(GEMSPEC_FILE) | grep 'File:' | tail -1 | awk '{ print $$2 }'); \
47
+ dst="releases/$$gem_file"; \
48
+ [ ! -f $$dst ]; \
49
+ $(MV) $$gem_file releases; \
50
+ gem push $$dst; \
51
+ echo 'done'
52
+
53
+ releases:
54
+ $(MKDIR) $@
55
+
56
+ tmp:
57
+ $(MKDIR) $@
58
+ $(CHMOD) u=rwx,go-rwx $@
@@ -0,0 +1,11 @@
1
+ # Timr
2
+
3
+ A Time Tracking Tool for the Command-line.
4
+
5
+ ## License
6
+
7
+ Copyright (C) 2016 Christian Mayer <http://fox21.at>
8
+
9
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
data/bin/dev ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: UTF-8
3
+
4
+ require 'pp'
5
+ require 'timr'
6
+
7
+
8
+ h = {
9
+ #'x' => 10,
10
+ #'y' => 5,
11
+ }
12
+
13
+ pp h.length
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: UTF-8
3
+
4
+ require 'optparse'
5
+ require 'timr'
6
+
7
+
8
+ @options = {
9
+ :dir => nil,
10
+ }
11
+ opts = OptionParser.new do |o|
12
+ o.banner = 'Usage: timr [options]'
13
+ o.separator('')
14
+
15
+ o.on_tail('-d', '--dir <path>', 'Path to a timr directory.') do |path|
16
+ @options[:dir] = path
17
+ end
18
+
19
+ o.on_tail('-v', '--version', 'Show version.') do
20
+ puts "#{TheFox::Timr::NAME} #{TheFox::Timr::VERSION} (#{TheFox::Timr::DATE})"
21
+ puts "#{TheFox::Timr::HOMEPAGE}"
22
+ exit
23
+ end
24
+
25
+ o.on_tail('-h', '--help', 'Show this message.') do
26
+ puts o
27
+ puts
28
+ exit 3
29
+ end
30
+ end
31
+ ARGV << '-h' if ARGV.count == 0
32
+ opts.parse(ARGV)
33
+
34
+ puts 'currently under heavy development'
35
+
36
+ # timr = TheFox::Timr::Timr.new(@options[:dir])
37
+ # timr.run
38
+ # timr.close
@@ -0,0 +1,10 @@
1
+
2
+ require 'timr/version'
3
+ require 'timr/timr'
4
+ require 'timr/stack'
5
+ require 'timr/task'
6
+ require 'timr/window'
7
+ require 'timr/window_help'
8
+ require 'timr/window_tasks'
9
+ require 'timr/window_timeline'
10
+ require 'timr/window_test'
@@ -0,0 +1,76 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ class Stack
6
+
7
+ def initialize
8
+ @tasks = []
9
+ @task = nil
10
+ end
11
+
12
+ def task
13
+ @task
14
+ end
15
+
16
+ def has_task?
17
+ !@task.nil?
18
+ end
19
+
20
+ def create(name, description)
21
+ @task = Task.new
22
+ @task.name = name
23
+ @task.description = description
24
+
25
+ @task
26
+ end
27
+
28
+ def length
29
+ @tasks.length
30
+ end
31
+
32
+ def tasks_texts
33
+ show_star = length > 1
34
+ @tasks.map{ |task|
35
+ status = task.status
36
+ status = '*' if task == @task
37
+ "#{status} #{task.name}"
38
+ }
39
+ end
40
+
41
+ def pop
42
+ old = @tasks.pop
43
+ if !old.nil?
44
+ old.stop
45
+ @task = @tasks.last
46
+ @task.start if has_task?
47
+ true
48
+ else
49
+ false
50
+ end
51
+ end
52
+
53
+ def pop_all(new_task = nil)
54
+ @tasks.each do |task|
55
+ task.stop
56
+ end
57
+ @tasks = []
58
+ @task = nil
59
+
60
+ if !new_task.nil?
61
+ push(new_task)
62
+ end
63
+ end
64
+
65
+ def push(task)
66
+ @task.stop if has_task?
67
+
68
+ @task = task
69
+ @task.start
70
+ @tasks << @task
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,144 @@
1
+
2
+ require 'time'
3
+ #require 'yaml'
4
+ require 'yaml/store'
5
+ require 'uuid'
6
+
7
+ module TheFox
8
+ module Timr
9
+
10
+ class Task
11
+
12
+ def initialize(path = nil)
13
+ # Status
14
+ # :running
15
+ # :stop
16
+ @status = :stop
17
+ @changed = false
18
+
19
+ @meta = {
20
+ 'id' => UUID.new.generate,
21
+ 'name' => nil,
22
+ 'description' => nil,
23
+ 'created' => Time.now.strftime(TIME_FORMAT),
24
+ 'modified' => Time.now.strftime(TIME_FORMAT),
25
+ }
26
+ @time = nil
27
+ @timeline = []
28
+
29
+ @path = path
30
+ if !@path.nil?
31
+ load_from_file(@path)
32
+ end
33
+ end
34
+
35
+ def load_from_file(path)
36
+ content = YAML::load_file(path)
37
+ @meta = content['meta']
38
+ @timeline = content['timeline']
39
+ .map{ |time_raw|
40
+ time = {
41
+ 'b' => Time.parse(time_raw['b']),
42
+ 'e' => Time.parse(time_raw['e']),
43
+ }
44
+ }
45
+ end
46
+
47
+ def save_to_file(basepath)
48
+ if @changed
49
+ path = File.expand_path("task_#{@meta['id']}.yml", basepath)
50
+
51
+ timeline_c = @timeline
52
+ .clone
53
+ .map{ |time|
54
+ time = time.clone
55
+ if !time['b'].nil?
56
+ time['b'] = time['b'].strftime(TIME_FORMAT)
57
+ end
58
+ if !time['e'].nil?
59
+ time['e'] = time['e'].strftime(TIME_FORMAT)
60
+ end
61
+ time
62
+ }
63
+
64
+ store = YAML::Store.new(path)
65
+ store.transaction do
66
+ store['meta'] = @meta
67
+ store['timeline'] = timeline_c
68
+ end
69
+ @changed = false
70
+ end
71
+ end
72
+
73
+ def running?
74
+ @status == :running
75
+ end
76
+
77
+ def status
78
+ case @status
79
+ when :running
80
+ '>'
81
+ when :stop
82
+ '|'
83
+ end
84
+ end
85
+
86
+ def changed
87
+ @changed = true
88
+ @meta['modified'] = Time.now.strftime(TIME_FORMAT)
89
+ end
90
+
91
+ def id
92
+ @meta['id']
93
+ end
94
+
95
+ def name
96
+ @meta['name']
97
+ end
98
+
99
+ def name=(name)
100
+ changed
101
+ @meta['name'] = name
102
+ end
103
+
104
+ def description=(description)
105
+ changed
106
+ @meta['description'] = description == '' ? nil : description
107
+ end
108
+
109
+ def start
110
+ if !running?
111
+ changed
112
+ @time = {
113
+ 'b' => Time.now, # begin
114
+ 'e' => nil, # end
115
+ }
116
+ @timeline << @time
117
+ end
118
+ @status = :running
119
+ end
120
+
121
+ def stop
122
+ if running? && !@time.nil?
123
+ changed
124
+ @time['e'] = Time.now
125
+ end
126
+ @status = :stop
127
+ end
128
+
129
+ def toggle
130
+ if running?
131
+ stop
132
+ else
133
+ start
134
+ end
135
+ end
136
+
137
+ def to_s
138
+ name
139
+ end
140
+
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,392 @@
1
+
2
+ require 'curses'
3
+ require 'time'
4
+
5
+ module TheFox
6
+ module Timr
7
+
8
+ class Timr
9
+
10
+ #include Curses
11
+
12
+ def initialize(path)
13
+ @base_dir_path = File.expand_path(path)
14
+ @base_dir_name = File.basename(@base_dir_path)
15
+ @data_dir_path = "#{@base_dir_path}/data"
16
+
17
+ puts "base: #{@base_dir_path}"
18
+ puts "name: #{@base_dir_name}"
19
+ puts "data: #{@data_dir_path}"
20
+
21
+ @stack = Stack.new
22
+ @tasks = {}
23
+
24
+ init_dirs
25
+ tasks_load
26
+
27
+ @window = nil
28
+ @window_timeline = TimelineWindow.new
29
+ @window_help = HelpWindow.new
30
+ @window_test = TestWindow.new
31
+
32
+ @window_tasks = TasksWindow.new
33
+ @window_tasks.tasks = @tasks
34
+ end
35
+
36
+ def init_dirs
37
+ if !Dir.exist?(@base_dir_path)
38
+ Dir.mkdir(@base_dir_path)
39
+ end
40
+ if !Dir.exist?(@data_dir_path)
41
+ Dir.mkdir(@data_dir_path)
42
+ end
43
+ end
44
+
45
+ def tasks_load
46
+ Dir.chdir(@data_dir_path) do
47
+ Dir['task_*.yml'].each do |file_name|
48
+ puts "file: '#{file_name}'"
49
+ task = Task.new(file_name)
50
+ @tasks[task.id] = task
51
+ end
52
+ end
53
+ end
54
+
55
+ def tasks_stop
56
+ @tasks.each do |task_id, task|
57
+ task.stop
58
+ end
59
+ end
60
+
61
+ def tasks_save
62
+ Dir.chdir(@data_dir_path) do
63
+ @tasks.each do |task_id, task|
64
+ task.save_to_file('.')
65
+ end
66
+ end
67
+ end
68
+
69
+ def init_curses
70
+ Curses.noecho
71
+ Curses.timeout = TIMEOUT
72
+ Curses.curs_set(0)
73
+ Curses.init_screen
74
+ Curses.start_color
75
+ Curses.use_default_colors
76
+ Curses.crmode
77
+ Curses.stdscr.keypad(true)
78
+
79
+ Curses.init_pair(Curses::COLOR_BLUE, Curses::COLOR_WHITE, Curses::COLOR_BLUE)
80
+ Curses.init_pair(Curses::COLOR_RED, Curses::COLOR_WHITE, Curses::COLOR_RED)
81
+ Curses.init_pair(Curses::COLOR_YELLOW, Curses::COLOR_BLACK, Curses::COLOR_YELLOW)
82
+ end
83
+
84
+ def title_line
85
+ title = "#{NAME} #{VERSION} -- #{@base_dir_name}"
86
+ if Curses.cols <= title.length + 1
87
+ title = "#{NAME} #{VERSION}"
88
+ if Curses.cols <= title.length + 1
89
+ title = NAME
90
+ end
91
+ end
92
+ rest = Curses.cols - title.length - COL
93
+
94
+ Curses.setpos(0, 0)
95
+ Curses.attron(Curses.color_pair(Curses::COLOR_BLUE) | Curses::A_BOLD) do
96
+ Curses.addstr(' ' * COL + title + ' ' * rest)
97
+ end
98
+ end
99
+
100
+ def status_text(text, attrn = Curses::A_NORMAL)
101
+ line_nr = Curses.lines - 1
102
+
103
+ Curses.setpos(line_nr, 0)
104
+ Curses.clrtoeol
105
+ Curses.setpos(line_nr, COL)
106
+ Curses.attron(attrn) do
107
+ Curses.addstr(text)
108
+ end
109
+ Curses.refresh
110
+ end
111
+
112
+ def status_input(text)
113
+ Curses.echo
114
+ Curses.timeout = -1
115
+ Curses.curs_set(1)
116
+
117
+ status_text(text)
118
+
119
+ input = ''
120
+ abort = false
121
+ loop do
122
+ key_pressed = Curses.getch
123
+ case key_pressed
124
+ when 27
125
+ abort = true
126
+ break
127
+ when Curses::Key::BACKSPACE
128
+ Curses.stdscr.delch
129
+ input = input[0..-2]
130
+ when 10
131
+ break
132
+ else
133
+ input += key_pressed
134
+ end
135
+ end
136
+ if abort
137
+ input = nil
138
+ end
139
+
140
+ Curses.noecho
141
+ Curses.timeout = TIMEOUT
142
+ Curses.curs_set(0)
143
+
144
+ input
145
+ end
146
+
147
+ def status_line(init = false)
148
+ line_nr = Curses.lines - 2
149
+
150
+ Curses.attron(Curses.color_pair(Curses::COLOR_YELLOW) | Curses::A_NORMAL) do
151
+ if init
152
+ Curses.setpos(line_nr, 0)
153
+ Curses.clrtoeol
154
+ Curses.addstr(' ' * Curses.cols)
155
+ end
156
+
157
+ Curses.setpos(line_nr, COL)
158
+ if @stack.has_task?
159
+ Curses.addstr(@stack.task.status)
160
+ else
161
+ Curses.addstr(TASK_NO_TASK_LOADED_C)
162
+ end
163
+
164
+ if Curses.cols > MIN_COLS
165
+ time_format = '%F %R %Z'
166
+ if Curses.cols <= 30
167
+ time_format = '%R'
168
+ elsif Curses.cols <= 40
169
+ time_format = '%m-%d %R'
170
+ elsif Curses.cols <= 50
171
+ time_format = '%y-%m-%d %R'
172
+ elsif Curses.cols <= 60
173
+ time_format = '%F %R'
174
+ elsif Curses.cols > 80
175
+ time_format = '%F %T %Z'
176
+ end
177
+ time_str = Time.now.strftime(time_format)
178
+ Curses.setpos(line_nr, Curses.cols - time_str.length - 1)
179
+ Curses.addstr(time_str)
180
+ end
181
+ end
182
+
183
+ Curses.refresh
184
+ end
185
+
186
+ def window_show(window)
187
+ @window = window
188
+ window_refresh
189
+ end
190
+
191
+ def window_refresh
192
+ max_lines = content_length
193
+ (1..max_lines).each do |line_nr|
194
+ Curses.setpos(line_nr, 0)
195
+ Curses.clrtoeol
196
+ end
197
+
198
+ if !@window.nil?
199
+ line_nr = 1
200
+ @window.content_refresh
201
+ page_length = @window.page_length
202
+ current_line = @window.current_line
203
+ max_line_len = Curses.cols - 2
204
+ @window.page.each do |line_object|
205
+ is_cursor = line_nr == @window.cursor
206
+
207
+ line_text = line_object.to_s
208
+ #line_text = "#{line_text} #{is_cursor ? 'X' : ''}"
209
+ if line_text.length > max_line_len
210
+ cut = line_text.length - max_line_len + 4
211
+ line_text = "#{line_text[0..-cut]}..."
212
+ end
213
+
214
+ rest = Curses.cols - line_text.length - COL
215
+
216
+ if is_cursor
217
+ Curses.setpos(line_nr, 0)
218
+ Curses.attron(Curses.color_pair(Curses::COLOR_BLUE) | Curses::A_BOLD) do
219
+ Curses.addstr(' ' * COL + line_text + ' ' * rest)
220
+ end
221
+ else
222
+ Curses.setpos(line_nr, COL)
223
+ Curses.addstr(line_text)
224
+ end
225
+
226
+ line_nr += 1
227
+ end
228
+ end
229
+
230
+ Curses.refresh
231
+ end
232
+
233
+ def content_length
234
+ Curses.lines - RESERVED_LINES - @stack.length
235
+ end
236
+
237
+ def update_content_length
238
+ cl = content_length
239
+
240
+ @window_timeline.content_length = cl
241
+ @window_help.content_length = cl
242
+ @window_test.content_length = cl
243
+ @window_tasks.content_length = cl
244
+ end
245
+
246
+ def refresh
247
+ update_content_length
248
+ title_line
249
+ status_line(true)
250
+ stack_lines
251
+ window_refresh
252
+ end
253
+
254
+ def stack_lines
255
+ line_nr = Curses.lines - (3 + (@stack.length - 1))
256
+
257
+ Curses.attron(Curses.color_pair(Curses::COLOR_BLUE)) do
258
+ @stack.tasks_texts.reverse.each do |line_text|
259
+ rest = Curses.cols - line_text.length - COL
260
+
261
+ Curses.setpos(line_nr, 0)
262
+ Curses.clrtoeol
263
+ Curses.addstr(' ' * COL + line_text + ' ' * rest)
264
+
265
+ line_nr += 1
266
+ end
267
+ end
268
+
269
+ Curses.refresh
270
+ end
271
+
272
+ def run
273
+ init_curses
274
+ update_content_length
275
+ title_line
276
+ status_line(true)
277
+
278
+ loop do
279
+ key_pressed = Curses.getch
280
+
281
+ case key_pressed
282
+ when Curses::Key::NPAGE
283
+ @window.next_page if !@window.nil?
284
+ window_refresh
285
+ when Curses::Key::PPAGE
286
+ @window.previous_page if !@window.nil?
287
+ window_refresh
288
+ when Curses::Key::DOWN
289
+ #@window.next_line if !@window.nil?
290
+ @window.cursor_next_line if !@window.nil?
291
+ #status_text("Cursor: #{@window.cursor} c=#{content_length} l=#{@window.current_line} pr=#{@window.page_refreshes} cr=#{@window.content_refreshes}") if !@window.nil?
292
+ window_refresh
293
+ when Curses::Key::UP
294
+ #@window.previous_line if !@window.nil?
295
+ @window.cursor_previous_line if !@window.nil?
296
+ #status_text("Cursor: #{@window.cursor} c=#{content_length} l=#{@window.current_line} pr=#{@window.page_refreshes} cr=#{@window.content_refreshes}") if !@window.nil?
297
+ window_refresh
298
+ when Curses::Key::HOME
299
+ @window.first_page if !@window.nil?
300
+ window_refresh
301
+ when Curses::Key::END
302
+ @window.last_page if !@window.nil?
303
+ window_refresh
304
+ when Curses::Key::RESIZE
305
+ update_content_length
306
+ status_text("Resizing: #{Curses.lines}x#{Curses.cols}")
307
+
308
+ # Refreshing the complete screen while resizing
309
+ # can make everything slower. So for fast resizing
310
+ # comment this line.
311
+ refresh
312
+ when 10
313
+ object = @window.page_object if !@window.nil?
314
+ if object.is_a?(Task)
315
+ status_text("Object: #{object.class} #{object}")
316
+ end
317
+ when 'r'
318
+ refresh
319
+ status_text('')
320
+ when 'n'
321
+ task_name = status_input('New task: ')
322
+ if task_name.nil?
323
+ status_text('Aborted.')
324
+ else
325
+ task_description = status_input('Description: ')
326
+
327
+ task = @stack.create(task_name, task_description)
328
+ @tasks[task.id] = task
329
+ @stack.pop_all(task)
330
+ @window_tasks.content_changed
331
+
332
+ status_text("Task '#{task_name}' created: #{task.id}")
333
+
334
+ stack_lines
335
+ window_refresh
336
+ end
337
+ when 'p'
338
+ task = Task.new
339
+ task.name = "task #{Time.now.strftime(TIME_FORMAT)}"
340
+ task.description = 'description1'
341
+
342
+ @tasks[task.id] = task
343
+ @stack.push(task)
344
+
345
+ status_text("Task '#{task_name}' created: #{task.id}")
346
+
347
+ stack_lines
348
+ when 'x'
349
+ @stack.task.stop if @stack.has_task?
350
+ when 'c'
351
+ @stack.task.toggle if @stack.has_task?
352
+ when 'v'
353
+ if @stack.pop
354
+ stack_lines
355
+ window_refresh
356
+ end
357
+ when 'h', '?'
358
+ window_show(@window_help)
359
+ when 't' # Test Windows
360
+ window_show(@window_test)
361
+ when '1'
362
+ window_show(@window_timeline)
363
+ when '2'
364
+ window_show(@window_tasks)
365
+ when 'w'
366
+ tasks_save
367
+ when 'q'
368
+ break
369
+ when nil
370
+ # Do some work.
371
+ status_line
372
+ else
373
+ status_text("Invalid key '#{key_pressed}' (#{Curses.keyname(key_pressed)})", Curses.color_pair(Curses::COLOR_RED) | Curses::A_BOLD)
374
+ end
375
+ end
376
+ end
377
+
378
+ def close
379
+ Curses.stdscr.clear
380
+ Curses.stdscr.refresh
381
+ Curses.stdscr.close
382
+
383
+ Curses.close_screen
384
+
385
+ tasks_stop
386
+ tasks_save
387
+ end
388
+
389
+ end
390
+
391
+ end
392
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+ NAME = 'Timr'
5
+ VERSION = '0.1.0-dev.1'
6
+ DATE = '2016-05-08'
7
+ HOMEPAGE = 'https://github.com/TheFox/timr'
8
+
9
+ COL = 1
10
+ MIN_COLS = 20
11
+ TIMEOUT = 250
12
+
13
+ # Reserved Lines
14
+ # - Title
15
+ # - Status Time Line
16
+ # - Status Text Line
17
+ RESERVED_LINES = 3
18
+
19
+ TIME_FORMAT = '%FT%T%z'
20
+
21
+ TASK_NO_TASK_LOADED_C = ?-
22
+ end
23
+ end
@@ -0,0 +1,202 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ class Window
6
+
7
+ def initialize
8
+ @content_length = 0
9
+ @current_line = 0
10
+ @cursor = 1
11
+ @content_changed = true
12
+ @content_refreshes = 1
13
+ @page = nil
14
+ @page_changed = true
15
+ @page_refreshes = 1
16
+
17
+ content_refresh
18
+ end
19
+
20
+ def content_length=(content_length)
21
+ @content_length = content_length
22
+ end
23
+
24
+ def content
25
+ []
26
+ end
27
+
28
+ def content_changed
29
+ @content_changed = true
30
+ end
31
+
32
+ def content_refreshes
33
+ @content_refreshes
34
+ end
35
+
36
+ def content_refresh
37
+ if @content_changed
38
+ @content = content
39
+ @content_refreshes += 1
40
+ @content_changed = false
41
+ end
42
+ end
43
+
44
+ def page_changed
45
+ @page_changed = true
46
+ end
47
+
48
+ def page_refreshes
49
+ @page_refreshes
50
+ end
51
+
52
+ def page
53
+ if @content.nil?
54
+ []
55
+ else
56
+ if @page.nil? || @page_changed
57
+ @page = @content[@current_line, @content_length]
58
+ @page_refreshes += 1
59
+ @page_changed = false
60
+ end
61
+ @page
62
+ end
63
+ end
64
+
65
+ def page_object
66
+ page[@cursor - 1]
67
+ end
68
+
69
+ def page_length
70
+ page.length
71
+ end
72
+
73
+ def next_page?
74
+ has = false
75
+ new_current_line = @current_line + @content_length
76
+ new_page = @content[new_current_line, @content_length]
77
+ if !new_page.nil?
78
+ new_page_length = new_page.length
79
+ if new_page_length > 0
80
+ has = true
81
+ end
82
+ end
83
+ has
84
+ end
85
+
86
+ def next_page(add_lines = nil)
87
+ if add_lines.nil?
88
+ add_lines = @content_length
89
+ end
90
+
91
+ page_changed
92
+ @current_line += add_lines if next_page?
93
+ cursor_set_to_last_if_out_of_range
94
+ end
95
+
96
+ def previous_page?
97
+ @current_line > 0
98
+ end
99
+
100
+ def previous_page
101
+ if previous_page?
102
+ page_changed
103
+ @current_line -= @content_length
104
+ if @current_line < 0
105
+ @current_line = 0
106
+ end
107
+ end
108
+ end
109
+
110
+ def first_page
111
+ page_changed
112
+ @current_line = 0
113
+ cursor_first_line
114
+ end
115
+
116
+ def last_page?
117
+ !next_page?
118
+ end
119
+
120
+ def last_page
121
+ page_changed
122
+ @current_line = @content.length - @content_length
123
+ cursor_last_line
124
+ end
125
+
126
+ def next_line
127
+ page_changed
128
+ next_page(1)
129
+ end
130
+
131
+ def previous_line
132
+ page_changed
133
+ previous_page(1)
134
+ end
135
+
136
+ def current_line
137
+ @current_line
138
+ end
139
+
140
+ def cursor
141
+ @cursor
142
+ end
143
+
144
+ def cursor_next_line
145
+ @cursor += 1
146
+ border = @content_length - 2
147
+ if @cursor > border
148
+ if last_page?
149
+ if @cursor > @content_length
150
+ @cursor = @content_length
151
+ end
152
+ else
153
+ @cursor = border
154
+ next_line
155
+ end
156
+ else
157
+
158
+ end
159
+
160
+ cursor_set_to_last_if_out_of_range
161
+ end
162
+
163
+ def cursor_previous_line
164
+ @cursor -= 1
165
+ border = 3
166
+ if @cursor < 1
167
+ @cursor = 1
168
+
169
+ if previous_page?
170
+ previous_line
171
+ else
172
+
173
+ end
174
+ elsif @cursor <= border
175
+ if previous_page?
176
+ @cursor = border
177
+ previous_line
178
+ end
179
+ else
180
+
181
+ end
182
+ end
183
+
184
+ def cursor_last_line
185
+ @cursor = @content_length
186
+ end
187
+
188
+ def cursor_first_line
189
+ @cursor = 1
190
+ end
191
+
192
+ def cursor_set_to_last_if_out_of_range
193
+ plength = page_length
194
+ if @cursor > plength
195
+ @cursor = plength
196
+ end
197
+ end
198
+
199
+ end
200
+
201
+ end
202
+ end
@@ -0,0 +1,36 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ class HelpWindow < Window
6
+
7
+ def content
8
+ [
9
+ '#### Help ####',
10
+ '',
11
+ ' n .. Create a New Task',
12
+ ' c .. Current Task: Start/Continue',
13
+ ' x .. Current Task: Stop',
14
+ ' v .. Current Task: Stop and Pop from Stack',
15
+ ' r .. Refresh Window',
16
+ ' w .. Write all changes.',
17
+ ' q .. Exit',
18
+ ' h .. Help',
19
+ ' 1 .. Timeline Window',
20
+ ' 2 .. Tasks Window',
21
+ ' KEY UP .. Move Cursor up.',
22
+ ' KEY DOWN .. Move Cursor down.',
23
+ '',
24
+ 'Current Task Status',
25
+ '',
26
+ " #{TASK_NO_TASK_LOADED_C} .. No task loaded.",
27
+ ' | .. Task stopped.',
28
+ ' > .. Task is running.',
29
+ #'',
30
+ ]
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require 'pp'
3
+
4
+ module TheFox
5
+ module Timr
6
+
7
+ class TasksWindow < Window
8
+
9
+ @tasks = nil
10
+
11
+ def tasks=(tasks)
12
+ content_changed
13
+ @tasks = tasks
14
+ end
15
+
16
+ def content
17
+ if @tasks.nil? || @tasks.length == 0
18
+ [
19
+ '',
20
+ '#### NO TASKS YET ####',
21
+ '',
22
+ "Press 'n' to create a new task.",
23
+ ]
24
+ else
25
+ @tasks
26
+ .sort_by{ |k, v|
27
+ v.name
28
+ }
29
+ .map{ |a| a[1] }
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ class TestWindow < Window
6
+
7
+ def content
8
+ c = []
9
+ (1..30).each do |n|
10
+ c << 'LINE %03d 123456789_123456789_123456789_123456789_123456789_123456789' % [n]
11
+ end
12
+ c
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ class TimelineWindow < Window
6
+
7
+ def content
8
+
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ # coding: UTF-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'timr/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'timr'
10
+ spec.version = TheFox::Timr::VERSION
11
+ spec.date = TheFox::Timr::DATE
12
+ spec.author = 'Christian Mayer'
13
+ spec.email = 'christian@fox21.at'
14
+
15
+ spec.summary = %q{Timr}
16
+ spec.description = %q{Time Tracking Tool for the Command-line.}
17
+ spec.homepage = TheFox::Timr::HOMEPAGE
18
+ spec.license = 'GPL-3.0'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject{ |f| f.match(%r{^(test|spec|features)/}) }
21
+ spec.bindir = 'bin'
22
+ spec.executables = ['timr']
23
+ spec.require_paths = ['lib']
24
+ spec.required_ruby_version = '>=2.1.0'
25
+
26
+ spec.add_development_dependency 'minitest', '~>5.8'
27
+
28
+ spec.add_dependency 'curses', '~>1.0'
29
+ spec.add_dependency 'uuid', '~>2.3'
30
+
31
+ spec.add_dependency 'thefox-ext', '~>1.4'
32
+ end
@@ -0,0 +1,10 @@
1
+ {
2
+ "folders":[
3
+ {
4
+ "path": ".",
5
+ "name": "Timr",
6
+ "folder_exclude_patterns": [ ],
7
+ "file_exclude_patterns": [ ]
8
+ }
9
+ ]
10
+ }
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.dev.1
5
+ platform: ruby
6
+ authors:
7
+ - Christian Mayer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: curses
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: uuid
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thefox-ext
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ description: Time Tracking Tool for the Command-line.
70
+ email: christian@fox21.at
71
+ executables:
72
+ - timr
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".editorconfig"
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - Makefile
81
+ - Makefile.common
82
+ - README.md
83
+ - bin/dev
84
+ - bin/timr
85
+ - lib/timr.rb
86
+ - lib/timr/stack.rb
87
+ - lib/timr/task.rb
88
+ - lib/timr/timr.rb
89
+ - lib/timr/version.rb
90
+ - lib/timr/window.rb
91
+ - lib/timr/window_help.rb
92
+ - lib/timr/window_tasks.rb
93
+ - lib/timr/window_test.rb
94
+ - lib/timr/window_timeline.rb
95
+ - timr.gemspec
96
+ - timr.sublime-project
97
+ homepage: https://github.com/TheFox/timr
98
+ licenses:
99
+ - GPL-3.0
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 2.1.0
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">"
113
+ - !ruby/object:Gem::Version
114
+ version: 1.3.1
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.4.7
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Timr
121
+ test_files: []
122
+ has_rdoc: