db_sucker 3.0.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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +45 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +193 -0
  7. data/Rakefile +1 -0
  8. data/VERSION +1 -0
  9. data/bin/db_sucker +12 -0
  10. data/bin/db_sucker.sh +14 -0
  11. data/db_sucker.gemspec +29 -0
  12. data/doc/config_example.rb +53 -0
  13. data/doc/container_example.yml +150 -0
  14. data/lib/db_sucker/adapters/mysql2.rb +103 -0
  15. data/lib/db_sucker/application/colorize.rb +28 -0
  16. data/lib/db_sucker/application/container/accessors.rb +60 -0
  17. data/lib/db_sucker/application/container/ssh.rb +225 -0
  18. data/lib/db_sucker/application/container/validations.rb +53 -0
  19. data/lib/db_sucker/application/container/variation/accessors.rb +45 -0
  20. data/lib/db_sucker/application/container/variation/helpers.rb +21 -0
  21. data/lib/db_sucker/application/container/variation/worker_api.rb +65 -0
  22. data/lib/db_sucker/application/container/variation.rb +60 -0
  23. data/lib/db_sucker/application/container.rb +70 -0
  24. data/lib/db_sucker/application/container_collection.rb +47 -0
  25. data/lib/db_sucker/application/core.rb +222 -0
  26. data/lib/db_sucker/application/dispatch.rb +364 -0
  27. data/lib/db_sucker/application/evented_resultset.rb +149 -0
  28. data/lib/db_sucker/application/fake_channel.rb +22 -0
  29. data/lib/db_sucker/application/output_helper.rb +197 -0
  30. data/lib/db_sucker/application/sklaven_treiber/log_spool.rb +57 -0
  31. data/lib/db_sucker/application/sklaven_treiber/worker/accessors.rb +105 -0
  32. data/lib/db_sucker/application/sklaven_treiber/worker/core.rb +168 -0
  33. data/lib/db_sucker/application/sklaven_treiber/worker/helpers.rb +144 -0
  34. data/lib/db_sucker/application/sklaven_treiber/worker/io/base.rb +240 -0
  35. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_copy.rb +81 -0
  36. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_gunzip.rb +58 -0
  37. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_import_sql.rb +80 -0
  38. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_shasum.rb +49 -0
  39. data/lib/db_sucker/application/sklaven_treiber/worker/io/pv_wrapper.rb +73 -0
  40. data/lib/db_sucker/application/sklaven_treiber/worker/io/sftp_download.rb +57 -0
  41. data/lib/db_sucker/application/sklaven_treiber/worker/io/throughput.rb +219 -0
  42. data/lib/db_sucker/application/sklaven_treiber/worker/routines.rb +313 -0
  43. data/lib/db_sucker/application/sklaven_treiber/worker.rb +48 -0
  44. data/lib/db_sucker/application/sklaven_treiber.rb +281 -0
  45. data/lib/db_sucker/application/slot_pool.rb +137 -0
  46. data/lib/db_sucker/application/tie.rb +25 -0
  47. data/lib/db_sucker/application/window/core.rb +185 -0
  48. data/lib/db_sucker/application/window/dialog.rb +142 -0
  49. data/lib/db_sucker/application/window/keypad/core.rb +85 -0
  50. data/lib/db_sucker/application/window/keypad.rb +174 -0
  51. data/lib/db_sucker/application/window/prompt.rb +124 -0
  52. data/lib/db_sucker/application/window.rb +329 -0
  53. data/lib/db_sucker/application.rb +168 -0
  54. data/lib/db_sucker/patches/beta-warning.rb +374 -0
  55. data/lib/db_sucker/patches/developer.rb +29 -0
  56. data/lib/db_sucker/patches/net-sftp.rb +20 -0
  57. data/lib/db_sucker/patches/thread-count.rb +30 -0
  58. data/lib/db_sucker/version.rb +4 -0
  59. data/lib/db_sucker.rb +81 -0
  60. metadata +217 -0
@@ -0,0 +1,329 @@
1
+ module DbSucker
2
+ class Application
3
+ class Window
4
+ include Core
5
+ include Curses
6
+ COLOR_GRAY = 8
7
+ COL1 = 20
8
+ COL2 = 25
9
+ COL3 = 20
10
+ OutputHelper.hook(self)
11
+
12
+ attr_reader :app, :sklaventreiber, :keypad, :tick, :spinner_frames
13
+ attr_accessor :view, :x_offset, :force_kill
14
+
15
+ def initialize app, sklaventreiber
16
+ @app = app
17
+ @keypad = Keypad.new(self)
18
+ @sklaventreiber = sklaventreiber
19
+ @monitor = Monitor.new
20
+ @line = 0
21
+ @tick = 0
22
+ @view = :status
23
+ @force_kill = false
24
+ choose_spinner
25
+ end
26
+
27
+ def refresh_screen
28
+ Thread.current[:last_render_duration] = rt = Benchmark.realtime do
29
+ @monitor.synchronize do
30
+ @tick += 1
31
+ update { __send__(:"_view_#{@view}") }
32
+ end
33
+ end
34
+ if rt > 0.020
35
+ @app.warning "window render took: #{"%.6f" % rt}"
36
+ else
37
+ @app.debug "window render took: #{"%.6f" % rt}", 125
38
+ end
39
+ rescue StandardError => ex
40
+ @app.notify_exception("DbSucker::Window encountered an render error on tick ##{@tick}", ex)
41
+
42
+ update do
43
+ next_line
44
+ red "RenderError occured!"
45
+ next_line
46
+ red "#{ex.class}: #{ex.message}"
47
+ ex.backtrace.each do |l|
48
+ next_line
49
+ red(" #{l}")
50
+ end
51
+ end
52
+ Thread.current.wait(1)
53
+ end
54
+
55
+ def _view_help
56
+ _render_status(threads: false, started: false, trxid: false, database: false)
57
+
58
+ #next_line; next_line
59
+ #magenta "db_sucker"
60
+ #blue " #{VERSION}"
61
+ #yellow " – "
62
+ #cyan "(C) 2016-#{Time.current.year} Sven Pachnit (bmonkeys.net)"
63
+
64
+ #next_line
65
+ #gray "Released under the MIT license."
66
+
67
+ next_line; next_line
68
+ blue "Key Bindings (case sensitive):"
69
+ next_line
70
+
71
+ Keypad::HELP_INFO[:key_bindings].each do |key, desc|
72
+ next_line
73
+ magenta " #{key}"
74
+ yellow " #{desc}"
75
+ end
76
+
77
+ next_line; next_line
78
+ blue "Main prompt commands:"
79
+ next_line
80
+
81
+ # only build output once and save it in memory
82
+ @_view_help_memory ||= begin
83
+ mchp = Keypad::HELP_INFO[:main_commands].map do |aliases, options, desc|
84
+ [].tap do |result|
85
+ result << [].tap{|r|
86
+ aliases.each do |al|
87
+ r << (al.is_a?(Array) ? al.join("").length + 4 : al.length + 2)
88
+ end
89
+ }.sum
90
+
91
+ result << [].tap{|r|
92
+ options.each do |type, name|
93
+ r << (type == :mandatory ? "<#{[*name] * "|"}> " : "[#{[*name] * "|"}] ").length
94
+ end
95
+ }.sum
96
+ end
97
+ end
98
+ columns = { aliases: mchp.map(&:first).max, options: mchp.map(&:second).max }
99
+
100
+ # render commands
101
+ [].tap do |instruct|
102
+ Keypad::HELP_INFO[:main_commands].each do |aliases, options, desc|
103
+ # aliases
104
+ instruct << [:next_line]
105
+ instruct << [:addstr, " "]
106
+ cl = 0
107
+ aliases.each do |al|
108
+ instruct << [:magenta, ":"]
109
+ if al.is_a?(Array)
110
+ instruct << [:blue, "#{al[0]}"]
111
+ instruct << [:gray, "("]
112
+ instruct << [:cyan, "#{al[1]}"]
113
+ cl += al.join("").length + 4
114
+ instruct << [:gray, ") "]
115
+ else
116
+ cl += al.length + 2
117
+ instruct << [:blue, "#{al} "]
118
+ end
119
+ end
120
+ instruct << [:addstr, "".ljust(columns[:aliases] - cl, " ")]
121
+
122
+ # options
123
+ cl = 0
124
+ instruct << [:addstr, " "]
125
+ options.each do |type, name|
126
+ if type == :mandatory
127
+ instruct << [:red, "<"]
128
+ [*name].each_with_index do |n, i|
129
+ instruct << [:gray, "|"] if i > 0
130
+ instruct << [:blue, n]
131
+ end
132
+ instruct << [:red, "> "]
133
+ else
134
+ instruct << [:yellow, "["]
135
+ [*name].each_with_index do |n, i|
136
+ instruct << [:gray, "|"] if i > 0
137
+ instruct << [:cyan, n]
138
+ end
139
+ instruct << [:yellow, "] "]
140
+ end
141
+ cl += [*name].join("").length + 3 + [*name].length - 1
142
+ end
143
+ instruct << [:addstr, "".ljust(columns[:options] - cl, " ")]
144
+
145
+ instruct << [:yellow, " #{desc}"]
146
+ end
147
+ end
148
+ end
149
+
150
+ @_view_help_memory.each do |a|
151
+ send(*a)
152
+ end
153
+
154
+ @keypad.prompt.render(self, lines-1)
155
+ end
156
+
157
+ def _view_log
158
+ _render_status(threads: false, started: false, trxid: false, database: false)
159
+ next_line
160
+ if app.opts[:stdout].is_a?(SklavenTreiber::LogSpool)
161
+ limit = [lines - @line - 1, app.opts[:stdout].spool.length].min
162
+ app.opts[:stdout].spool[-limit..-1].each do |m, l, t|
163
+ ts = "[#{t}] "
164
+ gray(ts)
165
+ white decolorize(l.join(" "))[0..(cols - ts.length)]
166
+ next_line
167
+ end
168
+ else
169
+ red "Log spooling is not enabled, can't show log entries!"
170
+ end
171
+ @keypad.prompt.render(self, lines-1)
172
+ end
173
+
174
+ def _view_status
175
+ _render_status
176
+ _render_workers
177
+ @keypad.prompt.render(self, lines-1)
178
+ end
179
+
180
+ def _render_status opts = {}
181
+ opts = opts.reverse_merge(status: true, threads: true, started: true, trxid: true, database: true, progress: true)
182
+
183
+ if opts[:status]
184
+ next_line
185
+ yellow " Status: "
186
+ send(sklaventreiber.status[1].presence || :blue, sklaventreiber.status[0])
187
+ # shutdown message
188
+ if $core_runtime_exiting && sklaventreiber.status[0] != "terminated"
189
+ red " (HALTING … please wait)"
190
+ end
191
+ end
192
+
193
+ if opts[:threads]
194
+ next_line
195
+ yellow " Threads: "
196
+ blue "#{Thread.list.length} ".ljust(COL1, " ")
197
+ end
198
+
199
+ if opts[:started]
200
+ next_line
201
+ yellow " Started: "
202
+ blue "#{@app.boot}"
203
+ yellow " ("
204
+ blue human_seconds(Time.current - app.boot)
205
+ yellow ")"
206
+ end
207
+
208
+ if opts[:trxid]
209
+ next_line
210
+ yellow "Transaction ID: "
211
+ cyan sklaventreiber.trxid
212
+ end
213
+
214
+ if opts[:database]
215
+ next_line
216
+ yellow " Database: "
217
+ magenta sklaventreiber.data[:database] || "?"
218
+ yellow " (transfering "
219
+ blue "#{sklaventreiber.data[:tables_transfer] || "?"}"
220
+ yellow " of "
221
+ blue "#{sklaventreiber.data[:tables_total] || "?"}"
222
+ yellow " tables)"
223
+ end
224
+
225
+ if opts[:progress]
226
+ next_line
227
+ total, done = sklaventreiber.data[:tables_transfer], sklaventreiber.data[:tables_done]
228
+ perc = total && done ? f_percentage(done, total) : "?"
229
+ yellow " Progress: "
230
+ green perc
231
+ yellow " – "
232
+ blue "#{done}/#{total || "?"} workers done"
233
+ end
234
+ end
235
+
236
+ def _render_workers
237
+ if sklaventreiber.workers.any?
238
+ next_line
239
+ limit = lines - @line - 3 - (@keypad.prompt.active? ? 1 : 0) # @l starting at 0, 1 for blank line to come, placeholder
240
+ enum = sklaventreiber.workers.sort_by{|w| [w.priority, w.table] }
241
+ enum.each_with_index do |w, i|
242
+ # limit reached and more than one entry to come?
243
+ if i > limit && (enum.length - i - 1) > 0
244
+ next_line
245
+ rest = enum[i..-1]
246
+ part = rest.group_by(&:state).map do |k, v|
247
+ "#{v.length} #{k}"
248
+ end
249
+ yellow "… #{rest.length} more [#{part.join(", ")}]"
250
+ break
251
+ end
252
+ _render_worker_line(w)
253
+ end
254
+ end
255
+ end
256
+
257
+ def _render_worker_line worker
258
+ next_line
259
+ col1 = sklaventreiber.data[:window_col1]
260
+ col2 = sklaventreiber.data[:window_col2]
261
+
262
+ # status icon
263
+ case worker.state
264
+ when :pending then gray("⊙")
265
+ when :pausing, :paused then gray("♨")
266
+ when :aquired then white("⊙")
267
+ when :done then green("✔")
268
+ when :failed then red("✘")
269
+ when :canceled then red("⊘")
270
+ when :running then yellow("#{worker.spinner_frame}")
271
+ end
272
+
273
+ # table_name
274
+ send(worker.should_cancel ? :red : worker.paused? ? :gray : :magenta, " #{worker.table}".ljust(col1 + 1, " "))
275
+ gray " | "
276
+
277
+ # status
278
+ if worker.step
279
+ cyan "[#{worker.step}/#{worker.perform.length}] "
280
+ end
281
+ if worker.status[0].respond_to?(:to_curses)
282
+ worker.status[0].to_curses(self)
283
+ else
284
+ send(worker.status[1].presence || :blue, decolorize("#{worker.status[0]}"))
285
+ end
286
+ end
287
+
288
+ # used to render everything before exiting, can't fucking dump the pads I tried to implement -.-"
289
+ def _render_final_results
290
+ t_db, t_total, t_done = sklaventreiber.data[:database], sklaventreiber.data[:tables_transfer], sklaventreiber.data[:tables_done]
291
+ perc = t_total && t_done ? f_percentage(t_done, t_total) : "?"
292
+
293
+ puts
294
+ puts c(" Status: ") << c(sklaventreiber.status[0], sklaventreiber.status[1].presence || "red")
295
+ puts c(" Threads: ") << c("#{Thread.list.length} ".ljust(COL1, " "), :blue)
296
+ puts c(" Started: ") << c("#{@app.boot}", :blue) << c(" (") << c(human_seconds(Time.current - app.boot), :blue) << c(")")
297
+ puts c("Transaction ID: ") << c("#{sklaventreiber.trxid}", :cyan)
298
+ puts c(" Database: ") << c(t_db || "?", :magenta) << c(" (transferred ") << c(t_total || "?", :blue) << c(" of ") << c(t_done || "?", :blue) << c(" tables)")
299
+ puts c(" Progress: ") << c(perc, :green) << c(" – ") << c(t_total || "?", :blue) << c("#{t_done}/#{t_total || "?"} workers done", :blue)
300
+
301
+ if sklaventreiber.workers.any?
302
+ puts
303
+ enum = sklaventreiber.workers.sort_by{|w| [w.priority, w.table] }
304
+ enum.each do |worker|
305
+ col1 = sklaventreiber.data[:window_col1]
306
+ col2 = sklaventreiber.data[:window_col2]
307
+
308
+ puts "".tap{|res|
309
+ # status icon
310
+ res << case worker.state
311
+ when :done then c("✔", :green)
312
+ when :failed then c("✘", :red)
313
+ when :canceled then c("⊘", :red)
314
+ else "#{worker.state.inspect}"
315
+ end
316
+ # table
317
+ res << c(" #{worker.table}".ljust(col1 + 1, " "), :magenta) << c(" | ", :black)
318
+ # steps
319
+ res << c("[#{worker.step}/#{worker.perform.length}] ", :cyan) if worker.step
320
+ # status
321
+ res << c(worker.status[0], worker.status[1].presence || :blue)
322
+ }
323
+ end
324
+ puts
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,168 @@
1
+ module DbSucker
2
+ class Application
3
+ attr_reader :opts, :cfg, :sklaventreiber, :boot
4
+ include Core
5
+ include Colorize
6
+ include OutputHelper
7
+ include Dispatch
8
+ GC_FORCE_RATE = 25*1024*1024
9
+
10
+ # main dispatch routine for application
11
+ def self.dispatch *a
12
+ new(*a) do |app|
13
+ begin
14
+ app.signalify_thread(Thread.main)
15
+ Thread.main[:app] = app
16
+ app.load_appconfig
17
+ app.parse_params
18
+ app.debug "Running with PID #{Process.pid}"
19
+ app.debug "Runtime: #{RUBY_DESCRIPTION}"
20
+ app.dispatch
21
+ app.haltpoint
22
+ rescue Interrupt
23
+ app.abort("Interrupted", 1)
24
+ rescue OptionParser::ParseError => ex
25
+ app.fire(:core_exception, ex)
26
+ app.abort("#{ex.message}", false)
27
+ app.log app.c("Run `#{$0} --help' for more info", :blue)
28
+ exit 1
29
+ rescue StandardError => ex
30
+ app.fire(:core_exception, ex)
31
+ app.warn app.c("[FATAL] #{ex.class}: #{ex.message}", :red)
32
+ ex.backtrace.each do |l|
33
+ app.warn app.c("\t#{l}", :red)
34
+ end
35
+ app.abort case ex
36
+ when Container::TableNotFoundError then ex.message
37
+ else "Unhandled exception terminated application!"
38
+ end
39
+ ensure
40
+ app.fire(:core_shutdown)
41
+ remain = Thread.list.length
42
+ if remain > 1
43
+ app.warning "#{remain} threads remain (should be 1)..."
44
+ else
45
+ app.debug "1 thread remains..."
46
+ end
47
+ Thread.main[:app] = nil unless app.opts[:debug]
48
+ end
49
+ end
50
+ end
51
+
52
+ def initialize env, argv
53
+ @boot = Time.current
54
+ @env, @argv = env, argv
55
+ @hooks = {}
56
+ @monitor = Monitor.new
57
+ @output_monitor = Monitor.new
58
+ @cfg = ContainerCollection.new(self)
59
+ @opts = {
60
+ dispatch: :index, # (internal) action to dispatch
61
+ mode: :default, # (internal) mode for action
62
+ check_for_updates: true, # -z flag
63
+ colorize: true, # --monochrome flag
64
+ debug: false, # -d flag
65
+ stdout: STDOUT, # (internal) STDOUT redirect
66
+ pipein: ARGF, # (internal) INPUT redirect
67
+
68
+ list_databases: false, # --list-databases flag
69
+ list_tables: false, # --list-tables flag
70
+ suck_only: [], # --only flag
71
+ suck_except: [], # --except flag
72
+ simulate: false, # --simulate flag
73
+ deferred_import: true, # -n flag
74
+ deferred_threshold: 50_000_000, # 50 MB
75
+
76
+ # features
77
+ status_format: :full, # used for IO operations, can be one of: none, minimal, full
78
+ pv_enabled: true, # disable pv utility autodiscovery (force non-usage)
79
+
80
+ # sklaven treiber
81
+ window_enabled: true, # if disabled effectively disables any status progress or window drawing
82
+ window_draw: true, # wether to refresh screen or not
83
+ window_refresh_delay: 0.25, # refresh screen every so many seconds
84
+ window_keypad: true, # allow keyboard controls
85
+ window_spinner: :circle_quarter, # change spinner... why is this configurable?
86
+ consumers: 10, # amount of workers to run at the same time
87
+
88
+ # thread priorities (-3..+3)
89
+ tp_window_draw_loop: -3,
90
+ tp_window_keypad_loop: +2,
91
+ tp_sklaventreiber_ssh_poll: +3,
92
+ tp_sklaventreiber_throughput: +2,
93
+ tp_sklaventreiber_worker: -1,
94
+ tp_sklaventreiber_worker_ctrl: -1,
95
+ tp_sklaventreiber_worker_slot_progress: -2,
96
+ tp_sklaventreiber_worker_second_progress: -2,
97
+ tp_sklaventreiber_worker_io_pv_killer: -2,
98
+ tp_sklaventreiber_worker_io_import_sql: +3,
99
+
100
+ # used to open core dumps (should be a blocking call, e.g. `subl -w' or `mate -w')
101
+ # MUST be windowed! vim, nano, etc. will not work!
102
+ core_dump_editor: "subl -w",
103
+
104
+ # amount of workers that can use a slot (false = infinite)
105
+ # you can create as many pools as you want and use them in `routine_pools' setting
106
+ slot_pools: {
107
+ all: false,
108
+ remote: false,
109
+ download: false,
110
+ local: false,
111
+ import: 3,
112
+ deferred: 1,
113
+ },
114
+
115
+ # assign tasks to certain slot pools
116
+ routine_pools: {
117
+ r_dump_file: [:all, :remote],
118
+ r_calculate_raw_hash: [:all, :remote],
119
+ r_compress_file: [:all, :remote],
120
+ r_calculate_compressed_hash: [:all, :remote],
121
+ l_download_file: [:all, :download],
122
+ l_verify_compressed_hash: [:all, :local],
123
+ l_copy_file: [:all, :local],
124
+ l_decompress_file: [:all, :local],
125
+ l_verify_raw_hash: [:all, :local],
126
+ l_import_file: [:all, :local, :import],
127
+ l_wait_for_workers: [], # does nothing, no need for any slots
128
+ l_import_file_deferred: [:all, :local, :deferred],
129
+ }
130
+ }
131
+ init_params
132
+ Tie.hook_all!(self)
133
+ yield(self)
134
+ end
135
+
136
+ def init_params
137
+ @optparse = OptionParser.new do |opts|
138
+ opts.banner = "Usage: db_sucker [options] [identifier [variation]]"
139
+
140
+ opts.separator("\n" << "# Application options")
141
+ opts.on( "--new NAME", String, "Generates new container config in #{core_cfg_path}") {|v| @opts[:dispatch] = :generate_config; @opts[:config_name] = v }
142
+ opts.on("-a", "--action ACTION", String, "Dispatch given action") {|v| @opts[:dispatch] = v }
143
+ opts.on("-m", "--mode MODE", String, "Dispatch action with given mode") {|v| @opts[:mode] = v.to_sym }
144
+ opts.on("-n", "--no-deffer", "Don't use deferred import for files > 50 MB SQL data size.") { @opts[:deferred_import] = false }
145
+ opts.on("-l", "--list-databases", "List databases for given identifier.") { @opts[:list_databases] = true }
146
+ opts.on("-t", "--list-tables [DATABASE]", String, "List tables for given identifier and database.", "If used with --list-databases the DATABASE parameter is optional.") {|s| @opts[:list_tables] = s || :all }
147
+ opts.on("-o", "--only table,table2", Array, "Only suck given tables. Identifier is required, variation is optional (defaults to default).", "WARNING: ignores ignore_always option") {|s| @opts[:suck_only] = s }
148
+ opts.on("-e", "--except table,table2", Array, "Don't suck given tables. Identifier is required, variation is optional (defaults to default).") {|s| @opts[:suck_except] = s }
149
+ opts.on("-c", "--consumers NUM=10", Integer, "Maximal amount of tasks to run simultaneously") {|n| @opts[:consumers] = n }
150
+ opts.on( "--stat-tmp", "Show information about the remote temporary directory.", "If no identifier is given check local temp directory instead.") { @opts[:dispatch] = :stat_tmp }
151
+ opts.on( "--cleanup-tmp", "Remove all temporary files from db_sucker in target directory.") { @opts[:dispatch] = :cleanup_tmp }
152
+ opts.on( "--simulate", "To use with --cleanup-tmp to not actually remove anything.") { @opts[:simulate] = true }
153
+
154
+ opts.separator("\n" << "# General options")
155
+ opts.on("-d", "--debug [lvl=1]", Integer, "Enable debug output") {|l| @opts[:debug] = l || 1 }
156
+ opts.on("--monochrome", "Don't colorize output (does not apply to curses)") { @opts[:colorize] = false }
157
+ opts.on("--no-window", "Disables curses window alltogether (no progress)") { @opts[:window_enabled] = false }
158
+ opts.on("-h", "--help", "Shows this help") { @opts[:dispatch] = :help }
159
+ opts.on("-v", "--version", "Shows version and other info") { @opts[:dispatch] = :info }
160
+ opts.on("-z", "Do not check for updates on GitHub (with -v/--version)") { @opts[:check_for_updates] = false }
161
+ end
162
+ end
163
+
164
+ def parse_params
165
+ @optparse.parse!(@argv)
166
+ end
167
+ end
168
+ end