timr 0.1.0.pre.dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: