/
pry_wrapper.rb
293 lines (251 loc) · 8.05 KB
/
pry_wrapper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
require "guard/commands/all"
require "guard/commands/change"
require "guard/commands/notification"
require "guard/commands/pause"
require "guard/commands/reload"
require "guard/commands/scope"
require "guard/commands/show"
require "shellany/sheller"
require "guard/jobs/base"
module Guard
module Jobs
class TerminalSettings
def initialize
@settings = nil
@works = Shellany::Sheller.run("hash", "stty") || false
end
def restore
return unless configurable? && @settings
Shellany::Sheller.run("stty #{ @setting } 2>#{IO::NULL}")
end
def save
return unless configurable?
@settings = Shellany::Sheller.stdout("stty -g 2>#{IO::NULL}").chomp
end
def echo
return unless configurable?
Shellany::Sheller.run("stty echo 2>#{IO::NULL}")
end
def configurable?
@works
end
end
class PryWrapper < Base
# The default Ruby script to configure Guard Pry if the option `:guard_rc`
# is not defined.
GUARD_RC = "~/.guardrc"
# The default Guard Pry history file if the option `:history_file` is not
# defined.
HISTORY_FILE = "~/.guard_history"
# List of shortcuts for each interactor command
SHORTCUTS = {
help: "h",
all: "a",
reload: "r",
change: "c",
show: "s",
scope: "o",
notification: "n",
pause: "p",
exit: "e",
quit: "q"
}
def initialize(options)
@mutex = Mutex.new
@thread = nil
@terminal_settings = TerminalSettings.new
_setup(options)
end
def foreground
UI.debug "Start interactor"
@terminal_settings.save
_switch_to_pry
# TODO: rename :stopped to continue
_killed? ? :stopped : :exit
ensure
UI.reset_line
UI.debug "Interactor was stopped or killed"
@terminal_settings.restore
end
def background
_kill_pry
end
def handle_interrupt
thread = @thread
fail Interrupt unless thread
thread.raise Interrupt
end
private
attr_reader :thread
def _switch_to_pry
th = nil
@mutex.synchronize do
unless @thread
@thread = Thread.new { Pry.start }
@thread.join(0.5) # give pry a chance to start
th = @thread
end
end
# check for nill, because it might've been killed between the mutex and
# now
th.join unless th.nil?
end
def _killed?
th = nil
@mutex.synchronize { th = @thread }
th.nil?
end
def _kill_pry
@mutex.synchronize do
unless @thread.nil?
@thread.kill
@thread = nil # set to nil so we know we were killed
end
end
end
def _setup(options)
Pry.config.should_load_rc = false
Pry.config.should_load_local_rc = false
history_file_path = options[:history_file] || HISTORY_FILE
Pry.config.history.file = File.expand_path(history_file_path)
_add_hooks(options)
Commands::All.import
Commands::Change.import
Commands::Notification.import
Commands::Pause.import
Commands::Reload.import
Commands::Show.import
Commands::Scope.import
_setup_commands
_configure_prompt
end
# Add Pry hooks:
#
# * Load `~/.guardrc` within each new Pry session.
# * Load project's `.guardrc` within each new Pry session.
# * Restore prompt after each evaluation.
#
def _add_hooks(options)
_add_load_guard_rc_hook(Pathname(options[:guard_rc] || GUARD_RC))
_add_load_project_guard_rc_hook(Pathname.pwd + ".guardrc")
_add_restore_visibility_hook if @terminal_settings.configurable?
end
# Add a `when_started` hook that loads a global .guardrc if it exists.
#
def _add_load_guard_rc_hook(guard_rc)
Pry.config.hooks.add_hook :when_started, :load_guard_rc do
guard_rc.expand_path.tap { |p| load p if p.exist? }
end
end
# Add a `when_started` hook that loads a project .guardrc if it exists.
#
def _add_load_project_guard_rc_hook(guard_rc)
Pry.config.hooks.add_hook :when_started, :load_project_guard_rc do
load guard_rc if guard_rc.exist?
end
end
# Add a `after_eval` hook that restores visibility after a command is
# eval.
def _add_restore_visibility_hook
Pry.config.hooks.add_hook :after_eval, :restore_visibility do
@terminal_settings.echo
end
end
def _setup_commands
_replace_reset_command
_create_run_all_command
_create_command_aliases
_create_guard_commands
_create_group_commands
end
# Replaces reset defined inside of Pry with a reset that
# instead restarts guard.
#
def _replace_reset_command
Pry.commands.command "reset", "Reset the Guard to a clean state." do
output.puts "Guard reset."
exec "guard"
end
end
# Creates a command that triggers the `:run_all` action
# when the command is empty (just pressing enter on the
# beginning of a line).
#
def _create_run_all_command
Pry.commands.block_command(/^$/, "Hit enter to run all") do
Pry.run_command "all"
end
end
# Creates command aliases for the commands: `help`, `reload`, `change`,
# `scope`, `notification`, `pause`, `exit` and `quit`, which will be the
# first letter of the command.
#
def _create_command_aliases
SHORTCUTS.each do |command, shortcut|
Pry.commands.alias_command shortcut, command.to_s
end
end
# Create a shorthand command to run the `:run_all`
# action on a specific Guard plugin. For example,
# when guard-rspec is available, then a command
# `rspec` is created that runs `all rspec`.
#
def _create_guard_commands
Guard.state.session.plugins.all.each do |guard_plugin|
cmd = "Run all #{ guard_plugin.title }"
Pry.commands.create_command guard_plugin.name, cmd do
group "Guard"
def process
Pry.run_command "all #{ match }"
end
end
end
end
# Create a shorthand command to run the `:run_all`
# action on a specific Guard group. For example,
# when you have a group `frontend`, then a command
# `frontend` is created that runs `all frontend`.
#
def _create_group_commands
Guard.state.session.groups.all.each do |group|
next if group.name == :default
cmd = "Run all #{ group.title }"
Pry.commands.create_command group.name.to_s, cmd do
group "Guard"
def process
Pry.run_command "all #{ match }"
end
end
end
end
# Configures the pry prompt to see `guard` instead of
# `pry`.
#
def _configure_prompt
Pry.config.prompt = [_prompt(">"), _prompt("*")]
end
# Returns the plugins scope, or the groups scope ready for display in the
# prompt.
#
def _scope_for_prompt
titles = Guard.state.scope.titles.join(",")
titles == "all" ? "" : titles + " "
end
# Returns a proc that will return itself a string ending with the given
# `ending_char` when called.
#
def _prompt(ending_char)
proc do |target_self, nest_level, pry|
history = pry.input_array.size
process = Guard.listener.paused? ? "pause" : "guard"
level = ":#{ nest_level }" unless nest_level.zero?
"[#{ history }] #{ _scope_for_prompt }#{ process }"\
"(#{ _clip_name(target_self) })#{ level }#{ ending_char } "
end
end
def _clip_name(target)
Pry.view_clip(target)
end
end
end
end