task-tree 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +61 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +124 -0
- data/LICENSE.txt +21 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/task-tree +22 -0
- data/fonts/larry3d.flf +924 -0
- data/lib/tasktree/tasky.rb +319 -0
- data/lib/tasktree/version.rb +3 -0
- data/task-tree.gemspec +29 -0
- metadata +157 -0
@@ -0,0 +1,319 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'tty-prompt'
|
3
|
+
require 'tree'
|
4
|
+
require 'json'
|
5
|
+
require 'open3'
|
6
|
+
require 'tty'
|
7
|
+
|
8
|
+
module TaskTree
|
9
|
+
class Tasky
|
10
|
+
def initialize(options)
|
11
|
+
@output = options.output || 'default.json'
|
12
|
+
@font_path = options.figlet_font || './fonts/larry3d'
|
13
|
+
@prompt = TTY::Prompt.new
|
14
|
+
@tree_root = Tree::TreeNode.new('__', '__')
|
15
|
+
@current_node = @tree_root
|
16
|
+
@screen = TTY::Screen
|
17
|
+
@animations = options.animations || false
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify_dependencies?
|
21
|
+
out, err, = Open3.capture3('which figlet')
|
22
|
+
if err.include?('no figlet') || err.include?('not found')
|
23
|
+
puts ''
|
24
|
+
puts '---------------------------------------------------------------'
|
25
|
+
puts '--> unfortunately you need to install figlet for this to work '
|
26
|
+
puts '---------------------------------------------------------------'
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
return unless verify_dependencies?
|
34
|
+
restore_state
|
35
|
+
print_current
|
36
|
+
|
37
|
+
action = :quit
|
38
|
+
loop do
|
39
|
+
action = @prompt.expand('Next action?') do |q|
|
40
|
+
q.choice key: 'a', name: 'add', value: :add
|
41
|
+
q.choice key: 'u', name: 'up', value: :up
|
42
|
+
q.choice key: 'd', name: 'down', value: :down
|
43
|
+
q.choice key: 'n', name: 'next', value: :next
|
44
|
+
q.choice key: 'p', name: 'previous', value: :previous
|
45
|
+
q.choice key: 'P', name: 'print', value: :print
|
46
|
+
q.choice key: 'c', name: 'complete', value: :complete
|
47
|
+
q.choice key: 'q', name: 'quit', value: :quit
|
48
|
+
q.choice key: 'x', name: 'quit without saving', value: :exit
|
49
|
+
q.choice key: 's', name: 'start pommodoro timer', value: :pommo
|
50
|
+
q.choice key: 'l', name: 'clean display', value: :clean
|
51
|
+
q.choice key: '$', name: 'last', value: :last_sibling
|
52
|
+
q.choice key: '_', name: 'first', value: :first_sibling
|
53
|
+
end
|
54
|
+
handle_action(action)
|
55
|
+
break if action == :quit || action == :exit
|
56
|
+
end
|
57
|
+
save_state if action == :quit
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_action(action)
|
61
|
+
case action
|
62
|
+
when :clean
|
63
|
+
print_current
|
64
|
+
@prompt.keypress('_')
|
65
|
+
when :add
|
66
|
+
prompt_and_add_task
|
67
|
+
print_current(action)
|
68
|
+
save_state
|
69
|
+
when :up
|
70
|
+
move_up
|
71
|
+
print_current(action)
|
72
|
+
when :down
|
73
|
+
move_down
|
74
|
+
print_current(action)
|
75
|
+
when :print
|
76
|
+
print_tree
|
77
|
+
print_current(action)
|
78
|
+
when :complete
|
79
|
+
set_as_complete
|
80
|
+
print_current(action)
|
81
|
+
save_state
|
82
|
+
when :previous
|
83
|
+
move_previous
|
84
|
+
print_current(action)
|
85
|
+
when :next
|
86
|
+
move_next
|
87
|
+
print_current(action)
|
88
|
+
when :last_sibling
|
89
|
+
move_last
|
90
|
+
print_current
|
91
|
+
when :first_sibling
|
92
|
+
move_first
|
93
|
+
print_current
|
94
|
+
when :pommo
|
95
|
+
when :pommo
|
96
|
+
start_pommo
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def start_pommo
|
101
|
+
minutes = @prompt.ask('how many minutes?', convert: :int)
|
102
|
+
bar = TTY::ProgressBar.new('[:bar]', total: minutes*6)
|
103
|
+
(minutes*6).times do
|
104
|
+
sleep(10)
|
105
|
+
bar.advance(1)
|
106
|
+
# TODO: check for quit or pause?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def print_tree
|
111
|
+
@tree_root.print_tree
|
112
|
+
end
|
113
|
+
|
114
|
+
def select_with_menu
|
115
|
+
selected = @prompt.select("Current level:") do |menu|
|
116
|
+
@current_node.children.each do |c|
|
117
|
+
menu.choice c.name, c
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@current_node = selected
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_as_complete
|
124
|
+
@current_node.content = 'complete'
|
125
|
+
move_next
|
126
|
+
end
|
127
|
+
|
128
|
+
def move_up
|
129
|
+
@current_node = @current_node.parent if !@current_node.parent.nil?
|
130
|
+
end
|
131
|
+
|
132
|
+
def move_down
|
133
|
+
@current_node = @current_node.first_child unless @current_node.first_child.nil?
|
134
|
+
if @current_node.content == 'complete'
|
135
|
+
move_up unless move_next || move_previous
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def move_previous(prev=@current_node)
|
140
|
+
return false if prev.is_only_child? || prev.is_first_sibling?
|
141
|
+
prev = prev.previous_sibling
|
142
|
+
if prev.content == 'complete'
|
143
|
+
prev = move_previous(prev)
|
144
|
+
else
|
145
|
+
@current_node = prev
|
146
|
+
end
|
147
|
+
true
|
148
|
+
end
|
149
|
+
|
150
|
+
def move_next(nxt=@current_node)
|
151
|
+
return false if nxt.is_only_child? || nxt.is_last_sibling?
|
152
|
+
nxt = nxt.next_sibling
|
153
|
+
if nxt.content == 'complete'
|
154
|
+
nxt = move_next(nxt)
|
155
|
+
else
|
156
|
+
@current_node = nxt
|
157
|
+
end
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
def move_last(current=@current_node)
|
162
|
+
last = current.last_sibling
|
163
|
+
if last.content == 'complete'
|
164
|
+
last = move_previous(last)
|
165
|
+
else
|
166
|
+
@current_node = last
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def move_first(current=@current_node)
|
171
|
+
first = current.first_sibling
|
172
|
+
if first.content == 'complete'
|
173
|
+
first = move_next(first)
|
174
|
+
else
|
175
|
+
@current_node = first
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def prompt_and_add_task
|
180
|
+
new_task = @prompt.ask('what to add?')
|
181
|
+
return if new_task.nil? || new_task.empty?
|
182
|
+
new_node = Tree::TreeNode.new(new_task, new_task)
|
183
|
+
@current_node << new_node
|
184
|
+
@current_node = new_node
|
185
|
+
end
|
186
|
+
|
187
|
+
def print_current(direction=nil)
|
188
|
+
if !@animations
|
189
|
+
direction = nil
|
190
|
+
end
|
191
|
+
empty_row = ""
|
192
|
+
@previous_page = @screen.height.times.map { empty_row } if @previous_page.nil?
|
193
|
+
page = @previous_page
|
194
|
+
task = draw_task(@current_node.content)
|
195
|
+
task_lines = task.split("\n")
|
196
|
+
case direction
|
197
|
+
when :down
|
198
|
+
blank = 0
|
199
|
+
@screen.height.times do |line_num|
|
200
|
+
start_task = @screen.height - task_lines.length
|
201
|
+
line = line_num >= start_task ? task_lines[line_num - start_task] : empty_row
|
202
|
+
blank +=1 if line == empty_row
|
203
|
+
puts page
|
204
|
+
sleep 0.01
|
205
|
+
# add after
|
206
|
+
page.push(line)
|
207
|
+
# remove first
|
208
|
+
page = page[1..-1]
|
209
|
+
end
|
210
|
+
when :up
|
211
|
+
task_lines.reverse!
|
212
|
+
@screen.height.times do |line_num|
|
213
|
+
line = task_lines.length > line_num ? task_lines[line_num] : empty_row
|
214
|
+
puts page
|
215
|
+
sleep 0.01
|
216
|
+
# add before
|
217
|
+
page.unshift(line)
|
218
|
+
# remove last
|
219
|
+
page = page[0..-2]
|
220
|
+
end
|
221
|
+
when :next
|
222
|
+
blank_lines = (@screen.height-task_lines.length).times.map { empty_row }
|
223
|
+
right_page = blank_lines + task_lines
|
224
|
+
joined = join_page(page, right_page)
|
225
|
+
speed = 3
|
226
|
+
((@screen.width * 0.8).to_i / speed).times do |col_num|
|
227
|
+
joined = remove_page_col(joined, speed: speed)
|
228
|
+
print_from_left(joined)
|
229
|
+
# TODO: need to regulate speed here, moving previous is very slow
|
230
|
+
sleep 0.005
|
231
|
+
end
|
232
|
+
page = right_page
|
233
|
+
when :previous
|
234
|
+
blank_lines = (@screen.height-task_lines.length).times.map { empty_row }
|
235
|
+
left_page = blank_lines + task_lines
|
236
|
+
joined = join_page(left_page, page)
|
237
|
+
speed = 3
|
238
|
+
((@screen.width * 0.666).to_i / speed).times do |col_num|
|
239
|
+
joined = remove_page_col(joined, reverse: true, speed: speed)
|
240
|
+
print_from_right(joined)
|
241
|
+
sleep 0.003
|
242
|
+
end
|
243
|
+
page = left_page
|
244
|
+
puts page
|
245
|
+
else
|
246
|
+
task = draw_task(@current_node.content)
|
247
|
+
task_lines = task.split("\n")
|
248
|
+
blank_lines = (@screen.height-task_lines.length).times.map { empty_row }
|
249
|
+
page = blank_lines + task_lines
|
250
|
+
puts task
|
251
|
+
end
|
252
|
+
@previous_page = page
|
253
|
+
end
|
254
|
+
|
255
|
+
def join_page(left, right)
|
256
|
+
return nil if left.length != right.length
|
257
|
+
joined = []
|
258
|
+
left.length.times do |i|
|
259
|
+
joined.push(left[i] + right[i]) unless left.empty?
|
260
|
+
end
|
261
|
+
joined
|
262
|
+
end
|
263
|
+
|
264
|
+
def print_from_left(page)
|
265
|
+
to_print = []
|
266
|
+
end_num = @screen.width-1
|
267
|
+
page.each { |line| to_print.push(line[0..end_num]) }
|
268
|
+
puts to_print
|
269
|
+
end
|
270
|
+
|
271
|
+
def print_from_right(page)
|
272
|
+
to_print = []
|
273
|
+
page.each { |line| to_print.push(line[-@screen.width..-1]) }
|
274
|
+
puts to_print
|
275
|
+
end
|
276
|
+
|
277
|
+
def remove_page_col(page, options={})
|
278
|
+
new_page = []
|
279
|
+
speed = options[:speed] || 1
|
280
|
+
page.each do |line|
|
281
|
+
if line.empty?
|
282
|
+
new_page.push(line)
|
283
|
+
else
|
284
|
+
if options[:reverse]
|
285
|
+
new_page.push(line[0..-speed])
|
286
|
+
else
|
287
|
+
new_page.push(line[speed..-1])
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
new_page
|
292
|
+
end
|
293
|
+
|
294
|
+
def draw_task(task)
|
295
|
+
width = @screen.width - 2
|
296
|
+
text = `figlet -f #{@font_path} -c -w #{width} #{task}`
|
297
|
+
text
|
298
|
+
end
|
299
|
+
|
300
|
+
def save_state
|
301
|
+
File.write(@output, @tree_root.to_json)
|
302
|
+
end
|
303
|
+
|
304
|
+
def restore_state
|
305
|
+
return unless File.exist?(@output)
|
306
|
+
data = File.read(@output)
|
307
|
+
parsed_data = JSON.parse(data)
|
308
|
+
@tree_root = load_tree(parsed_data)
|
309
|
+
@current_node = @tree_root
|
310
|
+
end
|
311
|
+
|
312
|
+
def load_tree(json_hash)
|
313
|
+
node = Tree::TreeNode.new(json_hash['name'], json_hash['content'])
|
314
|
+
json_hash['children'].each { |h| node << load_tree(h) } unless json_hash['children'].nil?
|
315
|
+
node
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
data/task-tree.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "tasktree/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "task-tree"
|
8
|
+
spec.version = TaskTree::VERSION
|
9
|
+
spec.authors = ["Ewoudt Kellerman"]
|
10
|
+
spec.email = ["ewoudt.kellerman@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Task Tree is a CLI tool that helps you keep track of your tasks in a tree structure}
|
13
|
+
spec.homepage = "https://github.com/hellola/task-tree"
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency "bundler", "~> 1.16"
|
23
|
+
spec.add_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_dependency "tty"
|
25
|
+
spec.add_dependency "tty-screen"
|
26
|
+
spec.add_dependency "rubytree"
|
27
|
+
spec.add_dependency "commander"
|
28
|
+
spec.add_development_dependency "byebug"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: task-tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ewoudt Kellerman
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: tty
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: tty-screen
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubytree
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: commander
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: byebug
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- ewoudt.kellerman@gmail.com
|
114
|
+
executables:
|
115
|
+
- task-tree
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- bin/console
|
126
|
+
- bin/setup
|
127
|
+
- exe/task-tree
|
128
|
+
- fonts/larry3d.flf
|
129
|
+
- lib/tasktree/tasky.rb
|
130
|
+
- lib/tasktree/version.rb
|
131
|
+
- task-tree.gemspec
|
132
|
+
homepage: https://github.com/hellola/task-tree
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.5.2
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Task Tree is a CLI tool that helps you keep track of your tasks in a tree
|
156
|
+
structure
|
157
|
+
test_files: []
|