timr 0.3.0 → 0.4.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.
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