timr 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.ackrc +9 -0
  3. data/.editorconfig +1 -0
  4. data/.env.example +7 -0
  5. data/.github/CONTRIBUTING.md +32 -0
  6. data/.github/ISSUE_TEMPLATE.md +13 -0
  7. data/.gitignore +8 -2
  8. data/.rdoc_options +21 -0
  9. data/.travis.yml +10 -7
  10. data/Gemfile +8 -0
  11. data/README.md +216 -3
  12. data/bin/.gitignore +2 -0
  13. data/bin/README.md +17 -0
  14. data/bin/build.sh +14 -0
  15. data/bin/build_api.sh +14 -0
  16. data/bin/build_coverage.sh +23 -0
  17. data/bin/build_info.sh +27 -0
  18. data/bin/build_man.sh +41 -0
  19. data/bin/clean.sh +14 -0
  20. data/bin/dev_setup.sh +19 -0
  21. data/bin/install.sh +49 -0
  22. data/bin/publish +38 -0
  23. data/bin/release.sh +35 -0
  24. data/bin/test.sh +19 -0
  25. data/bin/timr +20 -40
  26. data/bin/timr_bash_completion.sh +337 -0
  27. data/bin/uninstall.sh +24 -0
  28. data/lib/timr.rb +36 -8
  29. data/lib/timr/command/basic_command.rb +170 -0
  30. data/lib/timr/command/continue_command.rb +86 -0
  31. data/lib/timr/command/help_command.rb +137 -0
  32. data/lib/timr/command/log_command.rb +297 -0
  33. data/lib/timr/command/pause_command.rb +89 -0
  34. data/lib/timr/command/pop_command.rb +176 -0
  35. data/lib/timr/command/push_command.rb +141 -0
  36. data/lib/timr/command/report_command.rb +689 -0
  37. data/lib/timr/command/start_command.rb +172 -0
  38. data/lib/timr/command/status_command.rb +198 -0
  39. data/lib/timr/command/stop_command.rb +127 -0
  40. data/lib/timr/command/task_command.rb +318 -0
  41. data/lib/timr/command/track_command.rb +381 -0
  42. data/lib/timr/command/version_command.rb +18 -0
  43. data/lib/timr/duration.rb +159 -0
  44. data/lib/timr/exception/timr_error.rb +113 -0
  45. data/lib/timr/ext/time.rb +12 -0
  46. data/lib/timr/helper/datetime_helper.rb +128 -0
  47. data/lib/timr/helper/terminal_helper.rb +58 -0
  48. data/lib/timr/helper/translation_helper.rb +45 -0
  49. data/lib/timr/model/basic_model.rb +287 -0
  50. data/lib/timr/model/config.rb +48 -0
  51. data/lib/timr/model/foreign_id_db.rb +84 -0
  52. data/lib/timr/model/stack.rb +161 -0
  53. data/lib/timr/model/task.rb +1039 -0
  54. data/lib/timr/model/track.rb +589 -0
  55. data/lib/timr/progressbar.rb +41 -0
  56. data/lib/timr/simple_opt_parser.rb +230 -0
  57. data/lib/timr/status.rb +70 -0
  58. data/lib/timr/table.rb +88 -0
  59. data/lib/timr/timr.rb +500 -558
  60. data/lib/timr/version.rb +4 -15
  61. data/man/.gitignore +2 -0
  62. data/man/_footer +3 -0
  63. data/man/timr-continue.1 +48 -0
  64. data/man/timr-continue.1.ronn +39 -0
  65. data/man/timr-ftime.7 +77 -0
  66. data/man/timr-ftime.7.ronn +57 -0
  67. data/man/timr-log.1 +109 -0
  68. data/man/timr-log.1.ronn +87 -0
  69. data/man/timr-pause.1 +56 -0
  70. data/man/timr-pause.1.ronn +45 -0
  71. data/man/timr-pop.1 +66 -0
  72. data/man/timr-pop.1.ronn +53 -0
  73. data/man/timr-push.1 +25 -0
  74. data/man/timr-push.1.ronn +20 -0
  75. data/man/timr-report.1 +228 -0
  76. data/man/timr-report.1.ronn +193 -0
  77. data/man/timr-start.1 +100 -0
  78. data/man/timr-start.1.ronn +82 -0
  79. data/man/timr-status.1 +53 -0
  80. data/man/timr-status.1.ronn +42 -0
  81. data/man/timr-stop.1 +75 -0
  82. data/man/timr-stop.1.ronn +60 -0
  83. data/man/timr-task.1 +147 -0
  84. data/man/timr-task.1.ronn +115 -0
  85. data/man/timr-track.1 +109 -0
  86. data/man/timr-track.1.ronn +89 -0
  87. data/man/timr.1 +119 -0
  88. data/man/timr.1.ronn +68 -0
  89. data/timr.gemspec +18 -3
  90. data/timr.sublime-project +20 -1
  91. metadata +142 -23
  92. data/Makefile +0 -12
  93. data/Makefile.common +0 -56
  94. data/lib/timr/stack.rb +0 -81
  95. data/lib/timr/task.rb +0 -258
  96. data/lib/timr/track.rb +0 -167
  97. data/lib/timr/window.rb +0 -259
  98. data/lib/timr/window_help.rb +0 -41
  99. data/lib/timr/window_tasks.rb +0 -30
  100. data/lib/timr/window_test.rb +0 -20
  101. data/lib/timr/window_timeline.rb +0 -33
  102. data/tests/tc_stack.rb +0 -121
  103. data/tests/tc_task.rb +0 -190
  104. data/tests/tc_track.rb +0 -144
  105. data/tests/tc_window.rb +0 -428
  106. data/tests/ts_all.rb +0 -6
@@ -0,0 +1,41 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ # See [ruby-progressbar Issue #131](https://github.com/jfelchner/ruby-progressbar/issues/131).
6
+ class ProgressBar
7
+
8
+ def initialize(options = Hash.new)
9
+ @total = options.fetch(:total, 100)
10
+ @progress = options.fetch(:progress, 0)
11
+ @length = options.fetch(:length, 10)
12
+ @progress_mark = options.fetch(:progress_mark, ?#)
13
+ @remainder_mark = options.fetch(:remainder_mark, ?-)
14
+ end
15
+
16
+ # Render ProgressBar as String.
17
+ def render(progress = nil)
18
+ if progress
19
+ @progress = progress
20
+ end
21
+
22
+ progress_f = @progress.to_f / @total.to_f
23
+ if progress_f > 1.0
24
+ progress_f = 1.0
25
+ end
26
+
27
+ progress_f = @length.to_f * progress_f
28
+
29
+ progress_s = @progress_mark * progress_f
30
+
31
+ remain_l = @length - progress_s.length
32
+
33
+ remain_s = @remainder_mark * remain_l
34
+
35
+ '%s%s' % [progress_s, remain_s]
36
+ end
37
+
38
+ end # class ProgressBar
39
+
40
+ end # module Timr
41
+ end # module TheFox
@@ -0,0 +1,230 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ # Simple Option Parser
6
+ #
7
+ # Because OptParse sucks. Parsing arguments should be easy. :(
8
+ #
9
+ # Covered Usecases
10
+ #
11
+ # - -a
12
+ # - --all
13
+ # - -a --all
14
+ # - -a -b val
15
+ # - -a -b val cmd
16
+ # - -a -b val cmd1 cmd2
17
+ # - -a -b val1 val2
18
+ # - -a -b val1 val2 cmd1 cmd2
19
+ # - -ab
20
+ # - -ab val
21
+ #
22
+ # ### Usage
23
+ # ```
24
+ # optparser = SimpleOptParser.new
25
+ # optparser.register_option(['-a'])
26
+ # optparser.register_option(['-b'])
27
+ # optparser.register_option(['-c'], 1)
28
+ # opts = optparser.parse('-a -b -c val')
29
+ # ```
30
+ class SimpleOptParser
31
+
32
+ # Parsed Options (+Commands)
33
+ attr_reader :options
34
+
35
+ # Not Recognized Options
36
+ attr_reader :unknown_options
37
+
38
+ def initialize
39
+ # -a => 0
40
+ # --all => 0
41
+ # -b => 1
42
+ @valid_options = Hash.new
43
+
44
+ # a => 0
45
+ # all => 0
46
+ # b => 1
47
+ # @valid_options_without_prefix = Hash.new
48
+
49
+ # Parsed Options (+Commands)
50
+ @options = Array.new
51
+
52
+ # Not Recognized Options
53
+ @unknown_options = Array.new
54
+ end
55
+
56
+ # `aliases` (Array)
57
+ #
58
+ # `number_values`
59
+ #
60
+ # - 0 = no value, just a switch
61
+ # - -1 = unlimited
62
+ # - n = n values
63
+ def register_option(aliases, number_values = 0)
64
+ aliases.each do |ali|
65
+ @valid_options[ali] = number_values
66
+
67
+ # if ali[0] == '-'
68
+ # if ali[1] == '-'
69
+ # # --arg
70
+ # # Cut the two first characters.
71
+ # @valid_options_without_prefix[ali[2..-1]] = number_values
72
+ # else
73
+ # # -a
74
+ # # Cut only the first character.
75
+ # @valid_options_without_prefix[ali[1..-1]] = number_values
76
+ # end
77
+ # end
78
+ end
79
+ end
80
+
81
+ # `args` (Array|String)
82
+ def parse(args)
83
+ # Reset previous options.
84
+ @options = []
85
+
86
+ if args.is_a?(Array)
87
+ pre_argv = args
88
+ else
89
+ pre_argv = args.split(' ')
90
+ end
91
+
92
+ argv = Array.new
93
+
94
+ # Pre-process Special Argument (Compact)
95
+ # For example '-abcd' is '-a -b -c -d'.
96
+ pre_argv.each do |arg|
97
+ if arg[0] == '-'
98
+ if arg[1] == '-'
99
+ # Normal --arg
100
+ argv << arg
101
+ else
102
+ if arg.length > 2
103
+ # Special -ab
104
+ arg = arg[1..-1]
105
+
106
+ # Add each single argument separate.
107
+ # Array ['-abc'] will become ['-a', '-b', '-c'].
108
+ arg.length.times do |n|
109
+ argv << "-#{arg[n]}"
110
+ end
111
+ else
112
+ # Normal -a
113
+ argv << arg
114
+ end
115
+ end
116
+ else
117
+ # Commands, Values, etc
118
+ argv << arg
119
+ end
120
+ end
121
+
122
+ loop_c = 0 # Limit the loop.
123
+ while argv.length > 0 && loop_c < 1024
124
+ loop_c += 1
125
+
126
+ arg = argv.shift
127
+
128
+ if arg[0] == '-'
129
+ if @valid_options[arg]
130
+ # Recognized Argument
131
+
132
+ number_values = @valid_options[arg]
133
+
134
+ if number_values == 0
135
+ # No values. Like Command.
136
+ @options << [arg]
137
+ elsif number_values == -1
138
+ # Eat all the arguments. Nom nom nom.
139
+ arg_values = []
140
+ sub_loop_c = 0 # Limit the loop.
141
+ while argv.length > 0 && sub_loop_c < 1024
142
+ sub_loop_c += 1
143
+
144
+ arg = argv.shift
145
+
146
+ # When reaching the end.
147
+ unless arg
148
+ break
149
+ end
150
+
151
+ arg_values << arg
152
+ end
153
+ @options << arg_values
154
+ else
155
+ # n values.
156
+
157
+ arg_values = []
158
+ sub_loop_c = 0 # Limit the loop.
159
+ while argv.length > 0 && sub_loop_c < number_values && sub_loop_c < 1024
160
+ sub_loop_c += 1
161
+
162
+ val = argv.shift
163
+
164
+ # When reaching the end.
165
+ unless val
166
+ raise ArgumentError, "SimpleOptParser: Argument '#{arg}' expects #{number_values} value(s). Found #{sub_loop_c}."
167
+ end
168
+
169
+ if @valid_options[val]
170
+ raise ArgumentError, "SimpleOptParser: Argument '#{arg}' expects #{number_values} value(s). Found another Argument '#{val}'."
171
+ end
172
+
173
+ arg_values << val
174
+ end
175
+
176
+ if arg_values.length != number_values
177
+ raise ArgumentError, "SimpleOptParser: Argument '#{arg}' expects #{number_values} value(s), #{arg_values.length} given."
178
+ end
179
+
180
+ arg_values.unshift(arg)
181
+ @options << arg_values
182
+ end
183
+ else
184
+ # Unknown Arguments
185
+ # '-ab' arguments will be processed in pre_argv.
186
+ @unknown_options << arg
187
+ end
188
+ else
189
+ # Command
190
+ @options << [arg]
191
+ end
192
+ end
193
+
194
+ @options
195
+ end
196
+
197
+ # Do not re-parse. Verify an already parsed array.
198
+ def verify(opts)
199
+ opts.each do |opt_ar|
200
+ arg = opt_ar.first
201
+
202
+ number_values = @valid_options[arg]
203
+ if number_values.nil?
204
+ # Unknown Arguments
205
+ else
206
+ # Argument is valid.
207
+
208
+ # Check length.
209
+ if number_values == 0
210
+ if opt_ar.length > 0
211
+ raise ArgumentError, "SimpleOptParser: Argument '#{arg}' expects no values, #{opt_ar.length} given."
212
+ end
213
+ elsif number_values == -1
214
+ # Always OK. Keep eating, Pacman.
215
+ else
216
+ if number_values != opt_ar.length
217
+ raise ArgumentError, "SimpleOptParser: Argument '#{arg}' expects #{number_values} value(s), #{opt_ar.length} given."
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ true
224
+ end
225
+
226
+ end # class SimpleOptParser
227
+
228
+ end # module Timr
229
+ end # module TheFox
230
+
@@ -0,0 +1,70 @@
1
+
2
+ require 'term/ansicolor'
3
+
4
+ module TheFox
5
+ module Timr
6
+
7
+ # Used as [Task](rdoc-ref:TheFox::Timr::Model::Task) and [Track](rdoc-ref:TheFox::Timr::Model::Track) Status.
8
+ #
9
+ # - `R` running
10
+ # - `S` stopped
11
+ # - `P` paused. It's actually stopped but with an additional flag.
12
+ # - `-` (dash) not started
13
+ # - `U` unknown
14
+ class Status
15
+
16
+ include Term::ANSIColor
17
+
18
+ # Source Data
19
+ attr_reader :short_status
20
+
21
+ # Resolved by `short_status`. See `set_long_status` method.
22
+ attr_reader :long_status
23
+
24
+ def initialize(short_status)
25
+ @short_status = short_status
26
+
27
+ @long_status = nil
28
+ set_long_status
29
+ end
30
+
31
+ # Use `term/ansicolor` to colorize the Long Status.
32
+ def colorized
33
+ case @short_status
34
+ when ?R
35
+ green(@long_status)
36
+ when ?S
37
+ red(@long_status)
38
+ else
39
+ @long_status
40
+ end
41
+ end
42
+
43
+ # To String
44
+ def to_s
45
+ long_status
46
+ end
47
+
48
+ private
49
+
50
+ def set_long_status
51
+ @long_status = case @short_status
52
+ when ?-
53
+ 'not started'
54
+ when ?R
55
+ 'running'
56
+ when ?S
57
+ 'stopped'
58
+ when ?P
59
+ 'paused'
60
+ when ?U
61
+ 'unknown'
62
+ else
63
+ 'unknown'
64
+ end
65
+ end
66
+
67
+ end # class Status
68
+
69
+ end # module Timr
70
+ end # module TheFox
data/lib/timr/table.rb ADDED
@@ -0,0 +1,88 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+
5
+ class Table
6
+
7
+ # Holds all rows.
8
+ attr_reader :rows
9
+
10
+ def initialize(options = Hash.new)
11
+ @headings = options.fetch(:headings, Array.new)
12
+
13
+ @rows = Array.new
14
+ end
15
+
16
+ # Append a row.
17
+ def <<(row)
18
+ col_n = 0
19
+ row.each do |col|
20
+ header = @headings[col_n]
21
+ if header
22
+ header[:format] ||= '%s'
23
+ header[:label] ||= ''
24
+ unless header.has_key?(:empty)
25
+ header[:empty] = true
26
+ end
27
+ header[:max_length] ||= 0
28
+ header[:padding_left] ||= ''
29
+ header[:padding_right] ||= ''
30
+ else
31
+ header = {
32
+ :format => '%s',
33
+ :label => '',
34
+ :empty => true,
35
+ :max_length => 0,
36
+ :padding_left => '',
37
+ :padding_right => '',
38
+ }
39
+ end
40
+
41
+ unless col.nil?
42
+ if header[:empty]
43
+ header[:empty] = false
44
+ end
45
+ col_s = col.to_s
46
+ if col_s.length > header[:max_length]
47
+ header[:max_length] = (header[:format] % [col_s]).length + header[:padding_left].length + header[:padding_right].length
48
+ end
49
+ end
50
+
51
+ col_n += 1
52
+ end
53
+ @rows << row
54
+ end
55
+
56
+ # Render Table to String.
57
+ def to_s
58
+ s = ''
59
+
60
+ s << @headings.map{ |header|
61
+ unless header[:empty]
62
+ "%s#{header[:format]}%s" % [header[:padding_left], header[:label], header[:padding_right]]
63
+ end
64
+ }.select{ |ts| !ts.nil? }.join(' ')
65
+ s << "\n"
66
+
67
+ @rows.each do |row|
68
+ col_n = 0
69
+ columns = []
70
+ row.each do |col|
71
+ header = @headings[col_n]
72
+ unless header[:empty]
73
+ col_s = "%s#{header[:format]}%s" % [header[:padding_left], col, header[:padding_right]]
74
+
75
+ columns << col_s
76
+ end
77
+ col_n += 1
78
+ end
79
+ s << columns.join(' ') << "\n"
80
+ end
81
+
82
+ s
83
+ end
84
+
85
+ end # class Task
86
+
87
+ end # module Timr
88
+ end #module TheFox
data/lib/timr/timr.rb CHANGED
@@ -1,659 +1,601 @@
1
1
 
2
- require 'curses'
3
- require 'time'
4
- require 'fileutils'
5
- require 'yaml/store'
6
- require 'thefox-ext'
2
+ require 'pathname'
7
3
 
8
4
  module TheFox
9
5
  module Timr
10
6
 
7
+ # Core Class
8
+ #
9
+ # Loads and saves Models to files. Tasks are loaded to `@tasks`.
10
+ #
11
+ # Holds the [Stack](rdoc-ref:TheFox::Timr::Model::Stack) instance. Responsible to call Stack methods.
11
12
  class Timr
12
13
 
13
- #include Curses
14
+ include Model
15
+ include Error
14
16
 
15
- def initialize(base_dir_path, config_path = nil)
16
- @base_dir_path = File.expand_path(base_dir_path)
17
- @base_dir_name = File.basename(@base_dir_path)
18
- @data_dir_path = "#{@base_dir_path}/data"
19
- @config_path = config_path
20
-
21
- puts "base: #{@base_dir_path}"
22
- puts "name: #{@base_dir_name}"
23
- puts "data: #{@data_dir_path}"
24
- puts "config: #{@config_path}"
25
-
26
- @stack = Stack.new
27
- @tasks = {}
28
- @last_write = nil
29
- @config = {
30
- 'clock' => {
31
- 'default' => '%F %R',
32
- 'large' => '%F %T',
33
- 'short' => '%R',
34
- },
35
- }
36
- #@ui_window_refresh_last = nil
37
- #@ui_status_line_last = nil
38
-
39
- config_read
40
- init_dirs
41
- tasks_load
42
-
43
- @window = nil
44
- @window_help = HelpWindow.new
45
- @window_test = TestWindow.new
46
-
47
- @window_tasks = TasksWindow.new(@tasks)
48
- @window_timeline = TimelineWindow.new(@tasks)
49
- end
17
+ # ForeignIdDb instance.
18
+ attr_reader :foreign_id_db
50
19
 
51
- def config_read(path = @config_path)
52
- if !path.nil? && File.exist?(path)
53
- content = YAML::load_file(path)
54
- if content
55
- @config.merge_recursive!(content)
56
- end
57
- end
58
- end
20
+ # Stack instance.
21
+ attr_reader :stack
59
22
 
60
- def init_dirs
61
- if !Dir.exist?(@base_dir_path)
62
- FileUtils.mkdir_p(@base_dir_path)
63
- end
64
- if !Dir.exist?(@data_dir_path)
65
- FileUtils.mkdir_p(@data_dir_path)
23
+ def initialize(cwd)
24
+ # Current Working Directory
25
+ case cwd
26
+ when String
27
+ @cwd = Pathname.new(cwd)
28
+ else
29
+ @cwd = cwd
66
30
  end
67
- end
68
-
69
- def tasks_load
70
- Dir.chdir(@data_dir_path) do
71
- Dir['task_*.yml'].each do |file_name|
72
- #puts "file: '#{file_name}'"
73
- task = Task.new(file_name)
74
- @tasks[task.id] = task
75
- end
31
+
32
+ if @cwd && !@cwd.exist?
33
+ puts "Initialize Timr in #{@cwd}"
34
+ @cwd.mkpath
76
35
  end
77
- end
78
-
79
- def tasks_stop
80
- @tasks.each do |task_id, task|
81
- task.stop
36
+
37
+ @config = Config.new
38
+ @config.file_path = Pathname.new('config.yml').expand_path(@cwd)
39
+ if @config.file_path.exist?
40
+ @config.load_from_file
41
+ else
42
+ #@config.save_to_file
43
+ @config.save_to_file(nil, true)
82
44
  end
83
- end
84
-
85
- def tasks_save(print_status = false)
86
- @last_write = Time.now
87
- if print_status
88
- ui_status_text("Store files ... #{Time.now.strftime('%T')}")
45
+
46
+ @foreign_id_db = ForeignIdDb.new
47
+ @foreign_id_db.file_path = Pathname.new('foreign_id_db.yml').expand_path(@cwd)
48
+ if @foreign_id_db.file_path.exist?
49
+ @foreign_id_db.load_from_file
89
50
  end
90
- Dir.chdir(@data_dir_path) do
91
- @tasks.each do |task_id, task|
92
- task.save_to_file('.')
93
- end
51
+
52
+ @tasks_path = Pathname.new('tasks').expand_path(@cwd)
53
+ unless @tasks_path.exist?
54
+ @tasks_path.mkpath
94
55
  end
95
- if print_status
96
- ui_status_text("Files stored. #{Time.now.strftime('%T')}")
56
+
57
+ # Holds all loaded Tasks.
58
+ @tasks = Hash.new
59
+
60
+ # Stack Path
61
+ stack_path = Pathname.new('stack.yml').expand_path(@cwd)
62
+
63
+ # Stack
64
+ @stack = Stack.new
65
+ @stack.timr = self
66
+ @stack.file_path = stack_path
67
+ if stack_path.exist?
68
+ @stack.load_from_file
97
69
  end
98
70
  end
99
71
 
100
- def ui_init_curses
101
- Curses.noecho
102
- Curses.timeout = TIMEOUT
103
- Curses.curs_set(0)
104
- Curses.init_screen
105
- Curses.start_color
106
- Curses.use_default_colors
107
- Curses.crmode
108
- Curses.stdscr.keypad(true)
109
-
110
- Curses.init_pair(Curses::COLOR_BLUE, Curses::COLOR_WHITE, Curses::COLOR_BLUE)
111
- Curses.init_pair(Curses::COLOR_RED, Curses::COLOR_WHITE, Curses::COLOR_RED)
112
- Curses.init_pair(Curses::COLOR_GREEN, Curses::COLOR_BLACK, Curses::COLOR_GREEN)
113
- end
114
-
115
- def ui_title_line
116
- title = "#{NAME} #{VERSION} -- #{@base_dir_name}"
117
- if Curses.cols <= title.length + 1
118
- title = "#{NAME} #{VERSION}"
119
- if Curses.cols <= title.length + 1
120
- title = NAME
72
+ # Removes all previous [Tracks](rdoc-ref:TheFox::Timr::Model::Track) and starts a new one.
73
+ #
74
+ # Options:
75
+ #
76
+ # - `:foreign_id` (String)
77
+ # - `:task_id` (String)
78
+ # - `:track_id` (String)
79
+ def start(options = Hash.new)
80
+ foreign_id_opt = options.fetch(:foreign_id, nil)
81
+ task_id_opt = options.fetch(:task_id, nil)
82
+ track_id_opt = options.fetch(:track_id, nil)
83
+
84
+ # Get current Track from Stack.
85
+ old_track = @stack.current_track
86
+
87
+ # Stop current running Track.
88
+ if old_track
89
+ # Get Task from Track.
90
+ old_task = old_track.task
91
+ unless old_task
92
+ raise TrackError, "Track #{old_track.short_id} has no Task."
121
93
  end
94
+
95
+ # Stop Task
96
+ old_task.stop
97
+
98
+ # Save Task
99
+ old_task.save_to_file
100
+
101
+ old_task = nil
122
102
  end
123
- rest = Curses.cols - title.length - COL
124
103
 
125
- Curses.setpos(0, 0)
126
- Curses.attron(Curses.color_pair(Curses::COLOR_BLUE) | Curses::A_BOLD) do
127
- Curses.addstr(' ' * COL + title + ' ' * rest)
128
- end
129
- end
130
-
131
- # This is the line on the bottom of the screen.
132
- def ui_status_text(text = nil, attrn = Curses::A_NORMAL)
133
- line_nr = Curses.lines - 1
134
-
135
- Curses.setpos(line_nr, 0)
136
- Curses.clrtoeol
137
- if !text.nil?
138
- Curses.setpos(line_nr, COL)
139
- Curses.attron(attrn) do
140
- Curses.addstr(text)
104
+ if task_id_opt
105
+ task = get_task_by_id(task_id_opt)
106
+
107
+ if foreign_id_opt
108
+ # Throws exception when Foreign ID already exists in DB.
109
+ # Break before new Track creation.
110
+ @foreign_id_db.add_task(task, foreign_id_opt)
111
+ @foreign_id_db.save_to_file
141
112
  end
142
- end
143
- Curses.refresh
144
- end
145
-
146
- def ui_status_text_error(text)
147
- ui_status_text(text, Curses.color_pair(Curses::COLOR_RED) | Curses::A_BOLD)
148
- end
149
-
150
- # Use the Status Text line to get an input from user.
151
- def ui_status_input(text)
152
- Curses.echo
153
- Curses.timeout = -1
154
- Curses.curs_set(1)
155
-
156
- ui_status_text(text)
157
-
158
- input = ''
159
- abort = false
160
- loop do
161
- key_pressed = Curses.getch
162
- case key_pressed
163
- when 27
164
- abort = true
165
- break
166
- when Curses::Key::BACKSPACE
167
- if input.length > 0
168
- Curses.stdscr.delch
169
- input = input[0..-2]
113
+
114
+ track = task.start(options)
115
+
116
+ task.save_to_file
117
+
118
+ @stack.start(track)
119
+ @stack.save_to_file
120
+ else
121
+ if track_id_opt
122
+ # Seach Track ID the long way. Should be avoided.
123
+ # Searches all files.
124
+
125
+ track = Track.find_track_by_id(@tasks_path, track_id_opt)
126
+ if track
127
+ options[:track_id] = track.id
128
+
129
+ # Get Task from Track.
130
+ task = track.task
131
+ unless task
132
+ raise TrackError, "Track #{track.short_id} has no Task."
133
+ end
134
+
135
+ # Start Task
136
+ track = task.start(options)
137
+
138
+ # Save Task
139
+ task.save_to_file
140
+
141
+ @stack.start(track)
142
+ @stack.save_to_file
170
143
  end
171
- when 10
172
- break
173
- when nil
174
- # Do nothing.
175
- #sleep TIMEOUT.to_f / 1000
176
144
  else
177
- input += key_pressed.to_s
145
+ # Create completely new Task.
146
+ task = Task.create_task_from_hash(options)
147
+
148
+ if foreign_id_opt
149
+ # Throws exception when Foreign ID already exists in DB.
150
+ # Break before new Track creation.
151
+ @foreign_id_db.add_task(task, foreign_id_opt)
152
+ @foreign_id_db.save_to_file
153
+ end
154
+
155
+ # Start Task
156
+ track = task.start(options)
157
+
158
+ # Task Path
159
+ task_file_path = BasicModel.create_path_by_id(@tasks_path, task.id)
160
+
161
+ # Save Track to file.
162
+ task.save_to_file(task_file_path)
163
+
164
+ @stack.start(track)
165
+ @stack.save_to_file
178
166
  end
179
167
  end
180
- if abort
181
- input = nil
182
- end
183
168
 
184
- Curses.noecho
185
- Curses.timeout = TIMEOUT
186
- Curses.curs_set(0)
187
-
188
- input
169
+ track
189
170
  end
190
171
 
191
- # The second line from the bottom:
192
- # Show current Track status, Track Time Difference, Task Time Total.
193
- def ui_status_line(init = false)
194
- line_nr = Curses.lines - 2
172
+ # Stops the current running [Track](rdoc-ref:TheFox::Timr::Model::Track) and removes it from the Stack.
173
+ def stop(options = Hash.new)
174
+ # Get current Track from Stack.
175
+ track = @stack.current_track
176
+ unless track
177
+ return
178
+ end
195
179
 
196
- status_s = ''
197
- track_begin_time_s = ''
180
+ # Get Task from Track.
181
+ task = track.task
198
182
 
199
- run_time_track_str = ''
200
- run_time_track_h = 0
201
- run_time_track_m = 0
202
- run_time_track_s = 0
183
+ # Stop Task
184
+ task.stop(options)
203
185
 
204
- run_time_total_str = ''
205
- run_time_total_h = 0
206
- run_time_total_m = 0
207
- run_time_total_s = 0
186
+ # Save Task
187
+ task.save_to_file
208
188
 
209
- time_s = ''
189
+ @stack.stop
190
+ @stack.save_to_file
210
191
 
211
- stack_has_task = @stack.has_task?.freeze
212
- if stack_has_task
213
- status_s = @stack.task.status
214
-
215
- track_begin_time_s = '--:--'
216
- if @stack.task.running? && @stack.task.has_track?
217
- track_begin_time_s = @stack.task.track.begin_time.strftime('%R')
218
- end
219
-
220
- run_time_track_h, run_time_track_m, run_time_track_s = @stack.task.run_time_track
221
- run_time_total_h, run_time_total_m, run_time_total_s = @stack.task.run_time_total
222
- else
223
- status_s = TASK_NO_TASK_LOADED_CHAR
192
+ track
193
+ end
194
+
195
+ # Stops the current running [Track](rdoc-ref:TheFox::Timr::Model::Track) but does not remove it from the Stack.
196
+ def pause(options = Hash.new)
197
+ # Get current Track from Stack.
198
+ track = @stack.current_track
199
+ unless track
200
+ return
224
201
  end
225
202
 
226
- if Curses.cols > MIN_COLS
227
- if stack_has_task
228
- run_time_track_str = '%2d:%02d:%02d' % [run_time_track_h, run_time_track_m, run_time_track_s]
229
- run_time_total_str = '%2d:%02d:%02d' % [run_time_total_h, run_time_total_m, run_time_total_s]
230
- end
231
-
232
- time_format = @config['clock']['default']
233
- if Curses.cols <= 50
234
- if stack_has_task
235
- run_time_track_str = '%2d:%02d' % [run_time_track_h, run_time_track_m]
236
- end
237
- run_time_total_str = ''
238
-
239
- time_format = nil
240
- elsif Curses.cols <= 60
241
- if stack_has_task
242
- run_time_track_str = '%2d:%02d' % [run_time_track_h, run_time_track_m]
243
- run_time_total_str = '%2d:%02d' % [run_time_total_h, run_time_total_m]
244
- end
245
-
246
- time_format = @config['clock']['short']
247
- elsif Curses.cols > 80
248
- time_format = @config['clock']['large']
249
- end
250
- if !time_format.nil?
251
- time_s = Time.now.strftime(time_format)
252
- end
203
+ # Track Status
204
+ if track.stopped?
205
+ raise TrackError, "Cannot pause current Track #{track.short_id}, is not running."
253
206
  end
254
207
 
255
- line = "#{status_s} #{track_begin_time_s} #{run_time_track_str} #{run_time_total_str}"
208
+ # Get Task from Track.
209
+ task = track.task
256
210
 
257
- rest = Curses.cols - COL - line.length - time_s.length - 1
258
- line += ' ' * rest + time_s
211
+ # Pause Task
212
+ track = task.pause(options)
259
213
 
260
- Curses.attron(Curses.color_pair(Curses::COLOR_GREEN) | Curses::A_NORMAL) do
261
- if init
262
- Curses.setpos(line_nr, 0)
263
- Curses.clrtoeol
264
- Curses.addstr(' ' * Curses.cols)
265
- end
266
-
267
- Curses.setpos(line_nr, COL)
268
- Curses.addstr(line)
269
- end
214
+ # Save Task
215
+ task.save_to_file
216
+
217
+ # Do nothing on the Stack.
270
218
 
271
- Curses.refresh
219
+ track
272
220
  end
273
221
 
274
- def ui_window_show(window)
275
- @window = window
276
- ui_window_refresh_all
222
+ # Continues the Top [Track](rdoc-ref:TheFox::Timr::Model::Track).
223
+ #
224
+ # Options:
225
+ #
226
+ # - `:track` (Track)
227
+ def continue(options = Hash.new)
228
+ # Get current Track from Stack.
229
+ track = @stack.current_track
230
+ unless track
231
+ return
232
+ end
233
+ options[:track] = track
234
+
235
+ # Get Task from Track.
236
+ task = track.task
237
+
238
+ # Continue Task
239
+ track = task.continue(options)
240
+
241
+ # Save Task
242
+ task.save_to_file
243
+
244
+ @stack.stop
245
+ @stack.push(track)
246
+ @stack.save_to_file
247
+
248
+ track
277
249
  end
278
250
 
279
- def ui_content_line(line_nr, text)
280
- Curses.setpos(line_nr, 0)
281
- Curses.clrtoeol
282
-
283
- rest = Curses.cols - text.length - COL
284
- if rest < 0
285
- rest = 0
251
+ # Starts a new [Track](rdoc-ref:TheFox::Timr::Model::Track) and pauses the underlying one.
252
+ #
253
+ # Options:
254
+ #
255
+ # - `:foreign_id` (String)
256
+ # - `:task_id` (String)
257
+ # - `:track_id` (String)
258
+ def push(options = Hash.new)
259
+ foreign_id_opt = options.fetch(:foreign_id, nil)
260
+ task_id_opt = options.fetch(:task_id, nil)
261
+ track_id_opt = options.fetch(:track_id, nil)
262
+
263
+ # Get current Track from Stack.
264
+ old_track = @stack.current_track
265
+
266
+ # Stop current running Track.
267
+ if old_track
268
+ # Get Task from Track.
269
+ old_task = old_track.task
270
+ unless old_task
271
+ raise TrackError, "Track #{old_track.short_id} has no Task."
272
+ end
273
+
274
+ # Stop Task here because on pop we need to take the
275
+ # current Track from Stack instead from Task.
276
+ # You can push another Track from an already existing
277
+ # Task on the Stack. A Task can hold only one current Track.
278
+ old_task.stop
279
+
280
+ # Save Task
281
+ old_task.save_to_file
282
+
283
+ old_task = nil
286
284
  end
287
- out = ' ' * COL + text + ' ' * rest
288
285
 
289
- if @window.has_cursor? && line_nr == @window.cursor
290
- Curses.attron(Curses.color_pair(Curses::COLOR_BLUE) | Curses::A_BOLD) do
291
- Curses.addstr(out)
286
+ if task_id_opt
287
+ task = get_task_by_id(task_id_opt)
288
+
289
+ if foreign_id_opt
290
+ # Throws exception when Foreign ID already exists in DB.
291
+ # Break before new Track creation.
292
+ @foreign_id_db.add_task(task, foreign_id_opt)
293
+ @foreign_id_db.save_to_file
292
294
  end
295
+
296
+ # Start Task
297
+ track = task.start(options)
298
+
299
+ # Save Task
300
+ task.save_to_file
301
+
302
+ @stack.push(track)
303
+ @stack.save_to_file
293
304
  else
294
- Curses.addstr(out)
295
- end
296
- end
297
-
298
- def ui_window_refresh_all
299
- if !@window.nil?
300
- content_line_nr = 1
301
- @window.content_refresh
302
- max_line_len = Curses.cols - 2
303
- @window.page.each do |line_object|
304
- line_text = line_object.to_s
305
+ if track_id_opt
306
+ # The long way. Should be avoided.
307
+ # Search all files.
308
+
309
+ track = Track.find_track_by_id(@tasks_path, track_id_opt)
310
+ if track
311
+ options[:track_id] = track.id
312
+
313
+ # Get Task from Track.
314
+ task = track.task
315
+ unless task
316
+ raise TrackError, "Track #{track.short_id} has no Task."
317
+ end
318
+
319
+ # Start Task
320
+ track = task.start(options)
321
+
322
+ # Save Task
323
+ task.save_to_file
324
+
325
+ @stack.push(track)
326
+ @stack.save_to_file
327
+ end
328
+ else
329
+ # Create completely new Task.
330
+ task = Task.create_task_from_hash(options)
305
331
 
306
- if line_text.length > max_line_len
307
- range = 0..-(line_text.length - max_line_len + 4)
308
- line_text = "#{line_text[range]}..."
332
+ if foreign_id_opt
333
+ # Throws exception when Foreign ID already exists in DB.
334
+ # Break before new Track creation.
335
+ @foreign_id_db.add_task(task, foreign_id_opt)
336
+ @foreign_id_db.save_to_file
309
337
  end
310
338
 
311
- ui_content_line(content_line_nr, line_text)
339
+ # Start Task
340
+ track = task.start(options)
312
341
 
313
- content_line_nr += 1
342
+ # Task Path
343
+ task_file_path = BasicModel.create_path_by_id(@tasks_path, task.id)
314
344
 
315
- # if $DEBUG
316
- # Curses.refresh
317
- # sleep 0.01
318
- # end
319
- end
320
-
321
- window_page_length = @window.page_length
322
- content_length = ui_content_length
323
- if window_page_length < content_length
324
- ((window_page_length + 1)..content_length).to_a.each do |rest_line_nr|
325
- Curses.setpos(rest_line_nr, 0)
326
- Curses.clrtoeol
327
-
328
- # if $DEBUG
329
- # Curses.addstr("-- CLEAR -- #{rest_line_nr} #{Time.now.strftime('%T')}")
330
- # Curses.refresh
331
- # sleep 0.01
332
- # end
333
- end
345
+ # Save Track to file.
346
+ task.save_to_file(task_file_path)
347
+
348
+ @stack.push(track)
349
+ @stack.save_to_file
334
350
  end
335
351
  end
336
352
 
337
- Curses.refresh
353
+ track
338
354
  end
339
355
 
340
- def ui_content_length
341
- Curses.lines - RESERVED_LINES - @stack.size
356
+ # Stops the Top [Track](rdoc-ref:TheFox::Timr::Model::Track), removes it from the [Stack](rdoc-ref:TheFox::Timr::Model::Stack) and
357
+ # continues the next underlying (new Top) Track.
358
+ def pop(options = Hash.new)
359
+ stop(options)
360
+ continue(options)
342
361
  end
343
362
 
344
- def update_content_length
345
- cl = ui_content_length
363
+ # Create a new [Task](rdoc-ref:TheFox::Timr::Model::Task) based on the `options` Hash. Will not be started or something else.
364
+ #
365
+ # Uses [Task#create_task_from_hash](rdoc-ref:TheFox::Timr::Model::Task::create_task_from_hash) to create a new Task instance and [BasicModel#create_path_by_id](rdoc-ref:TheFox::Timr::Model::BasicModel.create_path_by_id) to create a new file path.
366
+ #
367
+ # Returns the new created Task instance.
368
+ #
369
+ # Options:
370
+ #
371
+ # - `:foreign_id` (String)
372
+ def add_task(options = Hash.new)
373
+ foreign_id_opt = options.fetch(:foreign_id, nil)
374
+
375
+ task = Task.create_task_from_hash(options)
376
+
377
+ if foreign_id_opt
378
+ # Throws exception when Foreign ID already exists in DB.
379
+ # Break before Task save.
380
+ @foreign_id_db.add_task(task, foreign_id_opt)
381
+ @foreign_id_db.save_to_file
382
+ end
346
383
 
347
- @window_help.content_length = cl
348
- @window_test.content_length = cl
349
- @window_tasks.content_length = cl
350
- @window_timeline.content_length = cl
351
- end
352
-
353
- def ui_refresh_simple
354
- ui_stack_lines_refresh
355
- ui_window_refresh_all
384
+ # Task Path
385
+ task_file_path = BasicModel.create_path_by_id(@tasks_path, task.id)
386
+
387
+ # Save Track to file.
388
+ task.save_to_file(task_file_path)
389
+
390
+ # Leave Stack untouched.
391
+
392
+ task
356
393
  end
357
394
 
358
- def ui_refresh_all
359
- #@ui_window_refresh_last = nil
395
+ # Remove a [Task](rdoc-ref:TheFox::Timr::Model::Task).
396
+ #
397
+ # Options:
398
+ #
399
+ # - `:task_id` (String) Can be either a internal ID (hex) or Foreign ID, because `get_task_by_id` searches also the Foreign ID DB.
400
+ def remove_task(options = Hash.new)
401
+ task_id_opt = options.fetch(:task_id, nil)
402
+
403
+ unless task_id_opt
404
+ raise TaskError, 'No Task ID given.'
405
+ end
360
406
 
361
- update_content_length
362
- ui_title_line
363
- ui_status_line(true)
364
- ui_refresh_simple
407
+ task = get_task_by_id(task_id_opt)
408
+
409
+ @tasks.delete(task.id)
410
+
411
+ # Get running Tracks and remove these from Stack.
412
+ task.tracks({:status => ?R}).each do |track_id, track|
413
+ @stack.remove(track)
414
+ end
415
+ @stack.save_to_file
416
+
417
+ # Remove Task from Foreign ID DB.
418
+ @foreign_id_db.remove_task(task)
419
+ @foreign_id_db.save_to_file
420
+
421
+ task.delete_file
422
+
423
+ task
365
424
  end
366
425
 
367
- def ui_stack_lines_refresh
368
- line_nr = Curses.lines - (3 + (@stack.size - 1))
426
+ # Remove a Track.
427
+ #
428
+ # Options:
429
+ #
430
+ # - `:track_id` (String)
431
+ def remove_track(options = Hash.new)
432
+ track_id_opt = options.fetch(:track_id, nil)
369
433
 
370
- Curses.attron(Curses.color_pair(Curses::COLOR_BLUE)) do
371
- @stack.tasks_texts.reverse.each do |line_text|
372
- rest = Curses.cols - line_text.length - COL
373
-
374
- Curses.setpos(line_nr, 0)
375
- Curses.clrtoeol
376
- Curses.addstr(' ' * COL + line_text + ' ' * rest)
377
-
378
- line_nr += 1
379
- end
434
+ unless track_id_opt
435
+ raise TrackError, 'No Track ID given.'
436
+ end
437
+
438
+ track = get_track_by_id(track_id_opt)
439
+ unless track
440
+ raise TrackError, "Track for ID '#{track_id_opt}' not found."
380
441
  end
381
442
 
382
- Curses.refresh
443
+ task = track.task
444
+
445
+ track.remove
446
+
447
+ task.save_to_file
448
+
449
+ # Remove Track from Stack.
450
+ @stack.remove(track)
451
+ @stack.save_to_file
452
+
453
+ {
454
+ :task => task,
455
+ :track => track,
456
+ }
383
457
  end
384
458
 
385
- def task_apply(task = nil, parent_track = nil, push = false)
386
- if task.nil?
387
- @stack.pop_all
459
+ # Find a [Task](rdoc-ref:TheFox::Timr::Model::Task) by ID (internal or foreign).
460
+ #
461
+ # Tasks always should be loaded with this methods to check if a Task instance already exist at `@tasks`. This is like a cache.
462
+ #
463
+ # `task_id` can be a short ID or a Foreign ID. If a Task is already loaded with full ID another search by short ID would lead to generate a new object_id. Then there would be two Tasks instances loaded for the same Task ID. The Check Cache if condition prohibits this.
464
+ def get_task_by_id(task_id)
465
+ # First search in Foreign ID DB.
466
+ tmp_task_id = @foreign_id_db.get_task_id(task_id)
467
+ if tmp_task_id
468
+ task_id = tmp_task_id
469
+ end
470
+
471
+ task = @tasks[task_id]
472
+
473
+ if task
474
+ # Take Task from cache.
388
475
  else
389
- @tasks[task.id] = task
390
- if push
391
- @stack.push(task, parent_track)
476
+ task = Task.load_task_from_file_with_id(@tasks_path, task_id)
477
+
478
+ # Check cache.
479
+ if @tasks[task.id]
480
+ # Task already loaded.
481
+ task = @tasks[task.id]
392
482
  else
393
- @stack.pop_all(task, parent_track)
483
+ # Set new loaded Task.
484
+ @tasks[task.id] = task
394
485
  end
395
486
  end
396
487
 
397
- update_content_length
398
- window_content_changed
399
- ui_stack_lines_refresh
400
- ui_window_refresh_all
488
+ task
401
489
  end
402
490
 
403
- def task_apply_replace_stack(task, parent_track = nil)
404
- if !task.nil?
405
- task_apply(task, parent_track, false)
491
+ # Find a [Track](rdoc-ref:TheFox::Timr::Model::Track) by ID.
492
+ def get_track_by_id(track_id)
493
+ @tasks.each do |task_id, task|
494
+ track = task.find_track_by_id(track_id)
495
+ if track
496
+ return track
497
+ end
406
498
  end
499
+
500
+ nil
407
501
  end
408
502
 
409
- def task_apply_stack_pop_all
410
- task_apply(nil, nil, false)
503
+ # Get a Track by a specific Task ID and Track ID.
504
+ def get_track_by_task_id(task_id, track_id)
505
+ if task_id && track_id
506
+ task = get_task_by_id(task_id)
507
+ if task
508
+ return task.find_track_by_id(track_id)
509
+ end
510
+ end
511
+
512
+ nil
411
513
  end
412
514
 
413
- def task_apply_push(task, parent_track = nil)
414
- task_apply(task, parent_track, true)
515
+ # Get all [Tasks](rdoc-ref:TheFox::Timr::Model::Task).
516
+ def tasks
517
+ load_all_tracks
518
+
519
+ @tasks
415
520
  end
416
521
 
417
- def task_apply_pop
418
- if @stack.pop
419
- update_content_length
420
- ui_refresh_simple
522
+ # Get all [Tracks](rdoc-ref:TheFox::Timr::Model::Track).
523
+ #
524
+ # Options:
525
+ #
526
+ # - `:sort` (Boolean)
527
+ def tracks(options = Hash.new)
528
+ sort_opt = options.fetch(:sort, true)
529
+
530
+ load_all_tracks
531
+
532
+ filtered_tracks = Hash.new
533
+ @tasks.each do |task_id, task|
534
+ tracks = task.tracks(options)
535
+ filtered_tracks.merge!(tracks)
536
+ end
537
+
538
+ if sort_opt
539
+ # Sort ASC by Begin DateTime, End DateTime.
540
+ filtered_tracks.sort{ |t1, t2|
541
+ t1 = t1.last
542
+ t2 = t2.last
543
+
544
+ cmp1 = t1.begin_datetime <=> t2.begin_datetime
545
+
546
+ if cmp1 == 0
547
+ t1.end_datetime <=> t2.end_datetime
548
+ else
549
+ cmp1
550
+ end
551
+ }.to_h
552
+ else
553
+ filtered_tracks
421
554
  end
422
555
  end
423
556
 
424
- # Update only Windows which shows the data. For example,
425
- # if a task is created all Windows needs to know this,
426
- # and only Windows which are using the tasks.
427
- # Not only the length of the rows can change, but also
428
- # the actual an change.
429
- def window_content_changed
430
- @window_tasks.content_changed
431
- @window_timeline.content_changed
432
- end
557
+ # Alias for `ForeignIdDb#match_task_with_foreign_id`.
558
+ # def match_task_with_foreign_id(task, foreign_id)
559
+ # end
433
560
 
434
- def window_page_object
435
- object = @window.page_object if !@window.nil?
436
-
437
- task = nil
438
- track = nil
439
- if object.is_a?(Task)
440
- task = object
441
- elsif object.is_a?(Track)
442
- task = object.task
443
- track = object
444
- end
561
+ # Save [Stack](rdoc-ref:TheFox::Timr::Model::Stack) and [Config](rdoc-ref:TheFox::Timr::Model::Config).
562
+ def shutdown
563
+ # Save Stack
564
+ @stack.save_to_file
565
+
566
+ # Save config
567
+ @config.save_to_file
445
568
 
446
- [task, track]
569
+ # Save Foreign ID DB
570
+ @foreign_id_db.save_to_file
447
571
  end
448
572
 
449
- def write_all_data
450
- if @last_write.nil?
451
- @last_write = Time.now
452
- else
453
- if Time.now - @last_write >= 300
454
- tasks_save(true)
573
+ # Load all [Tracks](rdoc-ref:TheFox::Timr::Model::Track) using `get_task_by_id`.
574
+ def load_all_tracks
575
+ # Iterate all files.
576
+ @tasks_path.find.each do |file|
577
+ # Filter all directories.
578
+ unless file.file?
579
+ next
455
580
  end
456
- end
457
- end
458
-
459
- def run
460
- ui_init_curses
461
- update_content_length
462
- ui_title_line
463
- ui_status_line(true)
464
- ui_window_show(@window_timeline)
465
-
466
- loop do
467
- key_pressed = Curses.getch
468
581
 
469
- case key_pressed
470
- when Curses::Key::NPAGE
471
- @window.next_page if !@window.nil?
472
-
473
- # if $DEBUG
474
- # ui_status_text('DEBUG: %03d C=%03d L=%03d pr=%03d cr=%03d' % [@window.cursor, ui_content_length, @window.current_line, @window.page_refreshes, @window.content_refreshes])
475
- # end
476
-
477
- ui_window_refresh_all
478
- when Curses::Key::PPAGE
479
- @window.previous_page if !@window.nil?
480
-
481
- # if $DEBUG
482
- # ui_status_text('DEBUG: %03d C=%03d L=%03d pr=%03d cr=%03d' % [@window.cursor, ui_content_length, @window.current_line, @window.page_refreshes, @window.content_refreshes])
483
- # end
484
-
485
- ui_window_refresh_all
486
- when Curses::Key::DOWN
487
- if !@window.nil? && @window.has_cursor?
488
- simple_refresh_b = @window.cursor_on_inner_range?
489
-
490
- @window.cursor_next_line
491
-
492
- simple_refresh_a = @window.cursor_on_inner_range?
493
-
494
- # if $DEBUG
495
- # ui_status_text('DEBUG: %03d C=%03d L=%03d pr=%03d cr=%03d %02d %02d %s %s' % [@window.cursor, ui_content_length, @window.current_line, @window.page_refreshes, @window.content_refreshes, @window.cursor_border_top, @window.cursor_border_bottom, simple_refresh_b ? 'SIMPLE' : 'FULL', simple_refresh_a ? 'SIMPLE' : 'FULL'])
496
- #end
497
-
498
- ui_window_refresh_all
499
- end
500
- when Curses::Key::UP
501
- if !@window.nil? && @window.has_cursor?
502
- simple_refresh_b = @window.cursor_on_inner_range?
503
-
504
- @window.cursor_previous_line
505
-
506
- simple_refresh_a = @window.cursor_on_inner_range?
507
-
508
- # if $DEBUG
509
- # ui_status_text('DEBUG: %03d C=%03d L=%03d pr=%03d cr=%03d %02d %02d %s %s' % [@window.cursor, ui_content_length, @window.current_line, @window.page_refreshes, @window.content_refreshes, @window.cursor_border_top, @window.cursor_border_bottom, simple_refresh_b ? 'SIMPLE' : 'FULL', simple_refresh_a ? 'SIMPLE' : 'FULL'])
510
- # end
511
-
512
- ui_window_refresh_all
513
- end
514
- when Curses::Key::HOME
515
- @window.first_page if !@window.nil?
516
-
517
- if $DEBUG
518
- ui_status_text('DEBUG: %03d C=%03d L=%03d pr=%03d cr=%03d' % [@window.cursor, ui_content_length, @window.current_line, @window.page_refreshes, @window.content_refreshes])
519
- end
520
-
521
- ui_window_refresh_all
522
- when Curses::Key::END
523
- @window.last_page if !@window.nil?
524
-
525
- # if $DEBUG
526
- # ui_status_text('DEBUG: %03d C=%03d L=%03d pr=%03d cr=%03d' % [@window.cursor, ui_content_length, @window.current_line, @window.page_refreshes, @window.content_refreshes])
527
- # end
528
-
529
- ui_window_refresh_all
530
- when Curses::Key::DC
531
- task, track = window_page_object
532
-
533
- if track.nil?
534
- # Remove Task
535
- else
536
- # Remove Track
537
- end
538
- task.remove_track(track)
539
-
540
- ui_status_text("Del '#{task.class}' '#{track.class}'")
541
- when Curses::Key::RESIZE
542
- update_content_length
543
- ui_status_text("Window size: #{Curses.cols}x#{Curses.lines}")
544
-
545
- # Refreshing the complete screen while resizing
546
- # can make everything slower. So for fast resizing
547
- # comment this line.
548
- ui_refresh_all
549
- when 10
550
- task, track = window_page_object
551
-
552
- # if track.nil?
553
- # track = Track.new
554
- # track.task = task
555
- # end
556
-
557
- task_apply_replace_stack(task, track)
558
- when '#'
559
- task, * = window_page_object
560
-
561
- track_description = ui_status_input('Track Description: ')
562
-
563
- track = Track.new
564
- track.task = task
565
- track.description = track_description
566
-
567
- task_apply_replace_stack(task, track)
568
- ui_status_text('OK')
569
- when 'b', 'p'
570
- task, track = window_page_object
571
-
572
- if task.nil?
573
- ui_status_text("Unrecognized object: #{object.class}")
574
- else
575
- task_apply_push(task, track)
576
- end
577
- when 'r'
578
- ui_refresh_all
579
- ui_status_text
580
- when 'n'
581
- task_name = ui_status_input('New task: ')
582
- if task_name.nil?
583
- ui_status_text('Aborted.')
584
- else
585
- task = Task.new
586
- task.name = task_name
587
- task.save_to_file(@data_dir_path)
588
-
589
- task_apply_replace_stack(task)
590
-
591
- ui_status_text("Task '#{task_name}' created.")
592
- end
593
- when 't'
594
- task_name = ui_status_input('New task: ')
595
- if task_name.nil?
596
- ui_status_text('Aborted.')
597
- else
598
- task = Task.new
599
- task.name = task_name
600
- task.save_to_file(@data_dir_path)
601
-
602
- @tasks[task.id] = task
603
-
604
- update_content_length
605
- window_content_changed
606
- ui_stack_lines_refresh
607
- ui_window_refresh_all
608
-
609
- ui_status_text("Task '#{task_name}' created.")
610
- end
611
- when 'x'
612
- @stack.task.pause if @stack.has_task?
613
- window_content_changed
614
- ui_refresh_simple
615
- when 'c'
616
- @stack.task.toggle if @stack.has_task?
617
- window_content_changed
618
- ui_refresh_simple
619
- when 'v'
620
- task_apply_pop
621
- when 'f'
622
- task_apply_stack_pop_all
623
- when 'h', '?'
624
- ui_window_show(@window_help)
625
- when '1'
626
- ui_window_show(@window_timeline)
627
- when '2'
628
- ui_window_show(@window_tasks)
629
- when 'z' # Test Windows
630
- ui_window_show(@window_test)
631
- when 'w'
632
- tasks_save(true)
633
- when 'q'
634
- break
635
- when nil
636
- # Do some work.
637
- ui_status_line
638
- write_all_data
639
- else
640
- ui_status_text_error("Invalid key '#{key_pressed}' (#{Curses.keyname(key_pressed)})")
582
+ # Filter all non-yaml files.
583
+ unless file.basename.fnmatch('*.yml')
584
+ next
641
585
  end
586
+
587
+ track_id = BasicModel.get_id_from_path(@tasks_path, file)
588
+
589
+ # Loads the Task from file into @tasks.
590
+ get_task_by_id(track_id)
642
591
  end
643
592
  end
644
593
 
645
- def close
646
- Curses.stdscr.clear
647
- Curses.stdscr.refresh
648
- Curses.stdscr.close
649
-
650
- Curses.close_screen
651
-
652
- tasks_stop
653
- tasks_save
594
+ def to_s
595
+ 'Timr'
654
596
  end
655
597
 
656
- end
657
-
658
- end
659
- end
598
+ end # class Timr
599
+
600
+ end # module Timr
601
+ end # module TheFox