db_sucker 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +193 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/db_sucker +12 -0
- data/bin/db_sucker.sh +14 -0
- data/db_sucker.gemspec +29 -0
- data/doc/config_example.rb +53 -0
- data/doc/container_example.yml +150 -0
- data/lib/db_sucker/adapters/mysql2.rb +103 -0
- data/lib/db_sucker/application/colorize.rb +28 -0
- data/lib/db_sucker/application/container/accessors.rb +60 -0
- data/lib/db_sucker/application/container/ssh.rb +225 -0
- data/lib/db_sucker/application/container/validations.rb +53 -0
- data/lib/db_sucker/application/container/variation/accessors.rb +45 -0
- data/lib/db_sucker/application/container/variation/helpers.rb +21 -0
- data/lib/db_sucker/application/container/variation/worker_api.rb +65 -0
- data/lib/db_sucker/application/container/variation.rb +60 -0
- data/lib/db_sucker/application/container.rb +70 -0
- data/lib/db_sucker/application/container_collection.rb +47 -0
- data/lib/db_sucker/application/core.rb +222 -0
- data/lib/db_sucker/application/dispatch.rb +364 -0
- data/lib/db_sucker/application/evented_resultset.rb +149 -0
- data/lib/db_sucker/application/fake_channel.rb +22 -0
- data/lib/db_sucker/application/output_helper.rb +197 -0
- data/lib/db_sucker/application/sklaven_treiber/log_spool.rb +57 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/accessors.rb +105 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/core.rb +168 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/helpers.rb +144 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/base.rb +240 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_copy.rb +81 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_gunzip.rb +58 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_import_sql.rb +80 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_shasum.rb +49 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/pv_wrapper.rb +73 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/sftp_download.rb +57 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/throughput.rb +219 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/routines.rb +313 -0
- data/lib/db_sucker/application/sklaven_treiber/worker.rb +48 -0
- data/lib/db_sucker/application/sklaven_treiber.rb +281 -0
- data/lib/db_sucker/application/slot_pool.rb +137 -0
- data/lib/db_sucker/application/tie.rb +25 -0
- data/lib/db_sucker/application/window/core.rb +185 -0
- data/lib/db_sucker/application/window/dialog.rb +142 -0
- data/lib/db_sucker/application/window/keypad/core.rb +85 -0
- data/lib/db_sucker/application/window/keypad.rb +174 -0
- data/lib/db_sucker/application/window/prompt.rb +124 -0
- data/lib/db_sucker/application/window.rb +329 -0
- data/lib/db_sucker/application.rb +168 -0
- data/lib/db_sucker/patches/beta-warning.rb +374 -0
- data/lib/db_sucker/patches/developer.rb +29 -0
- data/lib/db_sucker/patches/net-sftp.rb +20 -0
- data/lib/db_sucker/patches/thread-count.rb +30 -0
- data/lib/db_sucker/version.rb +4 -0
- data/lib/db_sucker.rb +81 -0
- metadata +217 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class Window
|
4
|
+
class Dialog
|
5
|
+
attr_accessor :border_color
|
6
|
+
|
7
|
+
BOX = {
|
8
|
+
tl: "┌",
|
9
|
+
t: "─",
|
10
|
+
tr: "┐",
|
11
|
+
l: "│",
|
12
|
+
r: "│",
|
13
|
+
bl: "└",
|
14
|
+
b: "─",
|
15
|
+
br: "┘",
|
16
|
+
hrl: "├",
|
17
|
+
hr: "─",
|
18
|
+
hrr: "┤",
|
19
|
+
}
|
20
|
+
|
21
|
+
def initialize window, &block
|
22
|
+
@window = window
|
23
|
+
@width = false
|
24
|
+
@border_color = :yellow
|
25
|
+
@border_padding = 1
|
26
|
+
@separator_padding = 1
|
27
|
+
@lines = []
|
28
|
+
block.call(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def line str = nil, color = :yellow, &block
|
32
|
+
if block
|
33
|
+
@lines << [].tap{|a| block.call(a) }
|
34
|
+
else
|
35
|
+
@lines << [[str, color]]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def br
|
40
|
+
@lines << []
|
41
|
+
end
|
42
|
+
|
43
|
+
def hr
|
44
|
+
@lines << :hr
|
45
|
+
end
|
46
|
+
|
47
|
+
def button *a
|
48
|
+
@lines.concat(build_button(*a))
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_button str, color = :yellow
|
52
|
+
tl = BOX[:tl]
|
53
|
+
t = BOX[:t]
|
54
|
+
tr = BOX[:tr]
|
55
|
+
l = BOX[:l]
|
56
|
+
r = BOX[:r]
|
57
|
+
bl = BOX[:bl]
|
58
|
+
b = BOX[:b]
|
59
|
+
br = BOX[:br]
|
60
|
+
width = str.length + 2
|
61
|
+
[].tap do |a|
|
62
|
+
a << [["#{tl}" << "".ljust(width, t) << "#{tr}", color]]
|
63
|
+
a << [["#{l}" << " #{str} ".ljust(width, t) << "#{r}", color]]
|
64
|
+
a << [["#{bl}" << "".ljust(width, b) << "#{br}", color]]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def button_group buttons = [], spaces = 2, &block
|
69
|
+
spaces = buttons if block
|
70
|
+
btns = block ? [].tap{|a| block.call(a) } : buttons.dup
|
71
|
+
spaces = "".ljust(spaces, " ")
|
72
|
+
first = btns.shift
|
73
|
+
|
74
|
+
btns.each_with_index do |lines, bi|
|
75
|
+
lines.each_with_index do |l, i|
|
76
|
+
first[i] << [spaces, :yellow]
|
77
|
+
first[i].concat(l)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@lines.concat first
|
82
|
+
end
|
83
|
+
|
84
|
+
# -----------------------------------------------------------
|
85
|
+
|
86
|
+
def _nl
|
87
|
+
@window.next_line
|
88
|
+
end
|
89
|
+
|
90
|
+
def _hr
|
91
|
+
l = BOX[:hrl]
|
92
|
+
r = BOX[:hrr]
|
93
|
+
m = "".ljust(@width - l.length - r.length, BOX[:hr])
|
94
|
+
@separator_padding.times { _line }
|
95
|
+
@window.send(@border_color, "#{l}#{m}#{r}")
|
96
|
+
_nl
|
97
|
+
end
|
98
|
+
|
99
|
+
def _line parts = []
|
100
|
+
return _hr if parts == :hr
|
101
|
+
l = BOX[:l]
|
102
|
+
r = BOX[:r]
|
103
|
+
p = "".ljust(@border_padding, " ")
|
104
|
+
|
105
|
+
@window.send(@border_color, "#{l}#{p}")
|
106
|
+
parts.each do |str, color|
|
107
|
+
@window.send(color, str)
|
108
|
+
end
|
109
|
+
@window.send(@border_color, "".ljust(@width - l.length - r.length - (p.length * 2) - parts.map{|s, _| s.length }.sum, " "))
|
110
|
+
@window.send(@border_color, "#{p}#{r}")
|
111
|
+
_nl
|
112
|
+
end
|
113
|
+
|
114
|
+
def _tborder
|
115
|
+
tl = BOX[:tl]
|
116
|
+
tr = BOX[:tr]
|
117
|
+
m = "".ljust(@width - tl.length - tr.length, BOX[:t])
|
118
|
+
@window.send(@border_color, "#{tl}#{m}#{tr}")
|
119
|
+
_nl
|
120
|
+
end
|
121
|
+
|
122
|
+
def _bborder
|
123
|
+
bl = BOX[:bl]
|
124
|
+
br = BOX[:br]
|
125
|
+
m = "".ljust(@width - bl.length - br.length, BOX[:b])
|
126
|
+
@window.send(@border_color, "#{bl}#{m}#{br}")
|
127
|
+
end
|
128
|
+
|
129
|
+
def render!
|
130
|
+
@width = @lines.map{|c| c.is_a?(Array) ? c.map{|s, _| s.length }.sum : 0 }.compact.max + (@border_padding * 2) + BOX[:l].length + BOX[:r].length
|
131
|
+
|
132
|
+
_tborder
|
133
|
+
@border_padding.times { _line }
|
134
|
+
@lines.each {|parts| _line(parts) }
|
135
|
+
@border_padding.times { _line }
|
136
|
+
_bborder
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class Window
|
4
|
+
class Keypad
|
5
|
+
module Core
|
6
|
+
def sync &block
|
7
|
+
@monitor.synchronize(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def sklaventreiber
|
11
|
+
@window.sklaventreiber
|
12
|
+
end
|
13
|
+
|
14
|
+
def app
|
15
|
+
@window.app
|
16
|
+
end
|
17
|
+
|
18
|
+
def enabled?
|
19
|
+
app.opts[:window_keypad]
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_loop
|
23
|
+
return false unless enabled?
|
24
|
+
@keyloop = app.spawn_thread(:window_keypad_loop) do |thr|
|
25
|
+
loop {
|
26
|
+
begin
|
27
|
+
handle_input(@window.send(:getch))
|
28
|
+
rescue StandardError => ex
|
29
|
+
app.notify_exception("DbSucker::Window::Keypad encountered an input handle error on tick ##{@window.tick}", ex)
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop_loop
|
36
|
+
return unless @keyloop
|
37
|
+
sync { @keyloop.kill }
|
38
|
+
@keyloop.join
|
39
|
+
end
|
40
|
+
|
41
|
+
def prompt! *a, &b
|
42
|
+
return false unless enabled?
|
43
|
+
@prompt.set!(*a, &b)
|
44
|
+
end
|
45
|
+
|
46
|
+
def _detect_worker arg, &block
|
47
|
+
sklaventreiber.sync do
|
48
|
+
if arg == "--all"
|
49
|
+
sklaventreiber.workers.each{|w| block.call(w) }
|
50
|
+
else
|
51
|
+
wrk = sklaventreiber.workers.detect do |w|
|
52
|
+
if arg.start_with?("^")
|
53
|
+
w.table.match(/#{arg}/i)
|
54
|
+
elsif arg.start_with?("/")
|
55
|
+
w.table.match(/#{arg[1..-1]}/i)
|
56
|
+
else
|
57
|
+
w.table == arg
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if wrk
|
61
|
+
block.call(wrk)
|
62
|
+
else
|
63
|
+
prompt!("Could not find any worker by the pattern `#{arg}'", color: :red)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def _eval evil
|
70
|
+
return if evil.blank?
|
71
|
+
app.dump_file "eval-result", true do |f|
|
72
|
+
begin
|
73
|
+
f.puts("#{evil}\n\n")
|
74
|
+
f.puts(app.sync{ app.instance_eval(evil) })
|
75
|
+
rescue StandardError => ex
|
76
|
+
f.puts("#{ex.class}: #{ex.message}")
|
77
|
+
ex.backtrace.each {|l| f.puts(" #{l}") }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class Window
|
4
|
+
class Keypad
|
5
|
+
include Core
|
6
|
+
attr_reader :prompt, :keyloop, :window
|
7
|
+
|
8
|
+
def initialize window
|
9
|
+
@window = window
|
10
|
+
@prompt = Prompt.new(@window, self)
|
11
|
+
@monitor = Monitor.new
|
12
|
+
end
|
13
|
+
|
14
|
+
HELP_INFO = {
|
15
|
+
key_bindings: [
|
16
|
+
["?", "shows this help"],
|
17
|
+
["^", "eval prompt (app context, synchronized)"],
|
18
|
+
["L", "show latest spooled log entries (no scrolling)"],
|
19
|
+
["P", "kill SSH polling (if it stucks)"],
|
20
|
+
["T", "create core dump and open in editor"],
|
21
|
+
["q", "quit prompt"],
|
22
|
+
["Q", "same as ctrl-c"],
|
23
|
+
[":", "main prompt"],
|
24
|
+
],
|
25
|
+
main_commands: [
|
26
|
+
[["?", %w[h elp]], [], "shows this help"],
|
27
|
+
[[%w[q uit]], [], "quit prompt"],
|
28
|
+
[["q!", "quit!"], [], "same as ctrl-c"],
|
29
|
+
[["kill"], [], "(dirty) interrupts all workers"],
|
30
|
+
[["kill!"], [], "(dirty) essentially SIGKILL (no cleanup)"],
|
31
|
+
[["dump"], [], "create and open coredump"],
|
32
|
+
[["eval"], [[:optional, "code"]], "executes code or opens eval prompt (app context, synchronized)"],
|
33
|
+
[[%w[c ancel]], [[:mandatory, %w[table_name --all]]], "cancels given or all workers"],
|
34
|
+
[[%w[p ause]], [[:mandatory, %w[table_name --all]]], "pauses given or all workers"],
|
35
|
+
[[%w[r esume]], [[:mandatory, %w[table_name --all]]], "resumes given or all workers"],
|
36
|
+
],
|
37
|
+
}
|
38
|
+
|
39
|
+
def handle_input ch
|
40
|
+
sync do
|
41
|
+
if @prompt.interactive?
|
42
|
+
@prompt.handle_input(ch)
|
43
|
+
else
|
44
|
+
case ch
|
45
|
+
when ":" then main_prompt
|
46
|
+
when "^" then eval_prompt # (@development)
|
47
|
+
when "L" then show_log # (@development)
|
48
|
+
when "T" then dump_core # (@development)
|
49
|
+
when "P" then kill_ssh_poll
|
50
|
+
when "?" then show_help
|
51
|
+
when "q" then quit_dialog
|
52
|
+
when "Q" then $core_runtime_exiting = 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def main_prompt
|
59
|
+
prompt!(":") do |raw|
|
60
|
+
break if raw.blank?
|
61
|
+
args = raw.split(" ")
|
62
|
+
cmd = args.shift
|
63
|
+
case cmd
|
64
|
+
when "?", "h", "help" then show_help
|
65
|
+
when "c", "cancel" then cancel_workers(args)
|
66
|
+
when "q", "quit" then quit_dialog
|
67
|
+
when "q!", "quit!" then $core_runtime_exiting = 1
|
68
|
+
when "kill" then kill_workers
|
69
|
+
when "kill!" then kill_app
|
70
|
+
when "dump" then dump_core
|
71
|
+
when "eval" then args.any? ? _eval(args.join(" ")) : eval_prompt
|
72
|
+
when "p", "pause" then pause_workers(args)
|
73
|
+
when "r", "resume" then resume_workers(args)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def eval_prompt
|
79
|
+
prompt!("eval> ") {|evil| _eval(evil) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def quit_dialog
|
83
|
+
q = "Do you want to abort all operations and quit?"
|
84
|
+
p = Proc.new do
|
85
|
+
blue q
|
86
|
+
gray " [y/q/t/1 n/f/0] "
|
87
|
+
end
|
88
|
+
prompt!(q,
|
89
|
+
prompt: p,
|
90
|
+
return_on_buffer: true,
|
91
|
+
capture_enter: false,
|
92
|
+
has_cursor: false
|
93
|
+
) do |response|
|
94
|
+
if response == "q" || @window.strbool(response) == true
|
95
|
+
$core_runtime_exiting = 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def show_help
|
101
|
+
view_was = window.change_view(:help)
|
102
|
+
prompt!("[press any key to return]",
|
103
|
+
return_on_buffer: true,
|
104
|
+
capture_enter: false,
|
105
|
+
has_cursor: false,
|
106
|
+
capture_escape: false,
|
107
|
+
cursor_visible: false
|
108
|
+
) do |response|
|
109
|
+
window.change_view(view_was)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def show_log
|
114
|
+
view_was = window.change_view(:log)
|
115
|
+
prompt!("[press any key to return]",
|
116
|
+
return_on_buffer: true,
|
117
|
+
capture_enter: false,
|
118
|
+
has_cursor: false,
|
119
|
+
capture_escape: false,
|
120
|
+
cursor_visible: false
|
121
|
+
) do |response|
|
122
|
+
window.change_view(view_was)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def pause_workers args
|
127
|
+
if args[0].is_a?(String)
|
128
|
+
_detect_worker(args.join(" "), &:pause)
|
129
|
+
else
|
130
|
+
prompt!("Usage: :p(ause) <table_name|--all>", color: :yellow)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def resume_workers args
|
135
|
+
if args[0].is_a?(String)
|
136
|
+
_detect_worker(args.join(" "), &:unpause)
|
137
|
+
else
|
138
|
+
prompt!("Usage: :r(esume) <table_name|--all>", color: :yellow)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def cancel_workers args
|
143
|
+
if args[0].is_a?(String)
|
144
|
+
_detect_worker(args.join(" ")) do |wrk|
|
145
|
+
wrk.cancel! "canceled by user"
|
146
|
+
end
|
147
|
+
else
|
148
|
+
prompt!("Usage: :c(cancel) <table_name|--all>", color: :yellow)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def kill_ssh_poll
|
153
|
+
return if sklaventreiber.workers.select{|w| !w.done? || w.sshing }.any?
|
154
|
+
sklaventreiber.poll.try(:kill)
|
155
|
+
end
|
156
|
+
|
157
|
+
def kill_workers
|
158
|
+
Thread.list.each do |thr|
|
159
|
+
thr.raise(Interrupt) if thr[:managed_worker]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def kill_app
|
164
|
+
exit!
|
165
|
+
end
|
166
|
+
|
167
|
+
def dump_core
|
168
|
+
app.dump_core
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class Window
|
4
|
+
class Prompt
|
5
|
+
attr_reader :cpos, :label, :buffer, :opts
|
6
|
+
|
7
|
+
def initialize window, keypad
|
8
|
+
@window = window
|
9
|
+
@keypad = keypad
|
10
|
+
reset_state(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def active?
|
14
|
+
@active
|
15
|
+
end
|
16
|
+
|
17
|
+
def interactive?
|
18
|
+
active? && @callback
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset_state initializing = false
|
22
|
+
unless initializing
|
23
|
+
@window.set_cursor(0)
|
24
|
+
@keypad.app.fire(:prompt_stop, @label)
|
25
|
+
@window.force_cursor(nil)
|
26
|
+
end
|
27
|
+
@active = false
|
28
|
+
@buffer = ""
|
29
|
+
@label = nil
|
30
|
+
@callback = nil
|
31
|
+
@cpos = 0
|
32
|
+
@opts = {
|
33
|
+
color: :blue,
|
34
|
+
return_on_buffer: false,
|
35
|
+
capture_enter: true,
|
36
|
+
capture_escape: true,
|
37
|
+
has_cursor: true,
|
38
|
+
prompt: nil, # proc
|
39
|
+
cursor_visible: true,
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def set! label, opts = {}, &callback
|
44
|
+
@label = label
|
45
|
+
@callback = callback
|
46
|
+
@active = true
|
47
|
+
@opts = @opts.merge(opts)
|
48
|
+
@keypad.app.fire(:prompt_start, @label, @opts)
|
49
|
+
@window.set_cursor(@opts[:cursor_visible] ? 2 : 0)
|
50
|
+
end
|
51
|
+
|
52
|
+
def render target, line
|
53
|
+
_this = self
|
54
|
+
target.instance_eval do
|
55
|
+
return unless _this.active?
|
56
|
+
setpos(line, 0)
|
57
|
+
clrtoeol
|
58
|
+
setpos(line, 0)
|
59
|
+
if _this.opts[:prompt]
|
60
|
+
instance_eval(&_this.opts[:prompt])
|
61
|
+
else
|
62
|
+
send(_this.opts[:color], _this.label)
|
63
|
+
end
|
64
|
+
white(_this.buffer)
|
65
|
+
force_cursor(line, stdscr.curx + _this.cpos)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_input ch
|
70
|
+
case ch
|
71
|
+
when 27 then (@opts[:capture_escape] ? _escape : sbuf(ch))
|
72
|
+
when 127 then (@opts[:has_cursor] ? _backspace : sbuf(ch))
|
73
|
+
when 330 then (@opts[:has_cursor] ? _delete : sbuf(ch))
|
74
|
+
when 260 then (@opts[:has_cursor] ? _left_arrow : sbuf(ch))
|
75
|
+
when 261 then (@opts[:has_cursor] ? _right_arrow : sbuf(ch))
|
76
|
+
when 13 then (@opts[:capture_enter] ? _enter : sbuf(ch))
|
77
|
+
else sbuf(ch)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def sbuf ch
|
82
|
+
#@buffer.concat(ch.bytes.to_s)
|
83
|
+
@cpos.zero? ? @buffer.concat(ch) : @buffer.insert(@cpos - 1, ch)
|
84
|
+
if @opts[:return_on_buffer].is_a?(Regexp)
|
85
|
+
_enter if @buffer.match(@opts[:return_on_buffer])
|
86
|
+
elsif @opts[:return_on_buffer].is_a?(Proc)
|
87
|
+
_enter if @opts[:return_on_buffer].call(@buffer, ch)
|
88
|
+
elsif @opts[:return_on_buffer]
|
89
|
+
_enter
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def _escape
|
94
|
+
reset_state
|
95
|
+
end
|
96
|
+
|
97
|
+
def _backspace
|
98
|
+
@buffer.slice!(@cpos - 1)
|
99
|
+
end
|
100
|
+
|
101
|
+
def _delete
|
102
|
+
return if @cpos.zero?
|
103
|
+
@buffer.slice!(@cpos)
|
104
|
+
@cpos += 1
|
105
|
+
end
|
106
|
+
|
107
|
+
def _left_arrow
|
108
|
+
@cpos = [@cpos - 1, -@buffer.length].max
|
109
|
+
end
|
110
|
+
|
111
|
+
def _right_arrow
|
112
|
+
@cpos = [@cpos + 1, 0].min
|
113
|
+
end
|
114
|
+
|
115
|
+
def _enter
|
116
|
+
_b = @buffer
|
117
|
+
_c = @callback
|
118
|
+
reset_state
|
119
|
+
_c.call(_b)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|