puppet-debugger 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +30 -13
- data/.rubocop.yml +3 -1
- data/.rubocop_todo.yml +11 -2
- data/.ruby-version +1 -1
- data/CHANGELOG.md +9 -0
- data/DEVELOPMENT.md +6 -1
- data/Gemfile +14 -12
- data/Plugin_development.md +304 -0
- data/README.md +4 -7
- data/Rakefile +6 -5
- data/bin/pdb +1 -0
- data/lib/awesome_print/ext/awesome_puppet.rb +1 -0
- data/lib/plugins/puppet-debugger/input_responders/benchmark.rb +40 -0
- data/lib/plugins/puppet-debugger/input_responders/classes.rb +15 -0
- data/lib/plugins/puppet-debugger/input_responders/classification.rb +14 -0
- data/lib/plugins/puppet-debugger/input_responders/commands.rb +95 -0
- data/lib/plugins/puppet-debugger/input_responders/datatypes.rb +17 -0
- data/lib/plugins/puppet-debugger/input_responders/environment.rb +14 -0
- data/lib/plugins/puppet-debugger/input_responders/exit.rb +14 -0
- data/lib/plugins/puppet-debugger/input_responders/facterdb_filter.rb +16 -0
- data/lib/plugins/puppet-debugger/input_responders/facts.rb +15 -0
- data/lib/plugins/puppet-debugger/input_responders/functions.rb +15 -0
- data/lib/plugins/puppet-debugger/input_responders/help.rb +14 -0
- data/lib/plugins/puppet-debugger/input_responders/krt.rb +14 -0
- data/lib/{puppet-debugger/support → plugins/puppet-debugger/input_responders}/play.rb +37 -26
- data/lib/plugins/puppet-debugger/input_responders/reset.rb +21 -0
- data/lib/plugins/puppet-debugger/input_responders/resources.rb +22 -0
- data/lib/plugins/puppet-debugger/input_responders/set.rb +55 -0
- data/lib/plugins/puppet-debugger/input_responders/types.rb +35 -0
- data/lib/plugins/puppet-debugger/input_responders/vars.rb +18 -0
- data/lib/plugins/puppet-debugger/input_responders/whereami.rb +29 -0
- data/lib/puppet-debugger.rb +12 -1
- data/lib/puppet-debugger/cli.rb +38 -22
- data/lib/puppet-debugger/code/code_file.rb +16 -15
- data/lib/puppet-debugger/code/code_range.rb +1 -0
- data/lib/puppet-debugger/code/loc.rb +1 -0
- data/lib/puppet-debugger/debugger_code.rb +1 -0
- data/lib/puppet-debugger/hooks.rb +174 -0
- data/lib/puppet-debugger/input_responder_plugin.rb +45 -0
- data/lib/puppet-debugger/plugin_test_helper.rb +44 -0
- data/lib/puppet-debugger/support.rb +13 -9
- data/lib/puppet-debugger/support/compiler.rb +1 -0
- data/lib/puppet-debugger/support/environment.rb +2 -0
- data/lib/puppet-debugger/support/errors.rb +9 -0
- data/lib/puppet-debugger/support/facts.rb +2 -1
- data/lib/puppet-debugger/support/functions.rb +3 -1
- data/lib/puppet-debugger/support/loader.rb +2 -0
- data/lib/puppet-debugger/support/node.rb +1 -0
- data/lib/puppet-debugger/support/scope.rb +1 -0
- data/lib/puppet/application/debugger.rb +1 -0
- data/lib/version.rb +2 -1
- data/puppet-debugger.gemspec +20 -15
- data/run_container_test.sh +1 -1
- data/spec/environment_spec.rb +2 -1
- data/spec/facts_spec.rb +1 -0
- data/spec/hooks_spec.rb +341 -0
- data/spec/input_responder_plugin_spec.rb +45 -0
- data/spec/input_responders/help_spec.rb +17 -0
- data/spec/input_responders/krt_spec.rb +12 -0
- data/spec/input_responders/play_spec.rb +160 -0
- data/spec/pdb_spec.rb +1 -0
- data/spec/puppet/application/debugger_spec.rb +1 -2
- data/spec/puppet_debugger_spec.rb +49 -88
- data/spec/remote_node_spec.rb +3 -2
- data/spec/spec_helper.rb +7 -0
- data/spec/support_spec.rb +5 -116
- data/test_matrix.rb +2 -0
- metadata +65 -12
- data/Gemfile.lock +0 -95
- data/lib/puppet-debugger/support/input_responders.rb +0 -191
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'puppet-debugger/input_responder_plugin'
|
2
|
+
module PuppetDebugger
|
3
|
+
module InputResponders
|
4
|
+
class Reset < InputResponderPlugin
|
5
|
+
COMMAND_WORDS = %w(reset)
|
6
|
+
SUMMARY = 'Reset the debugger to a clean state.'
|
7
|
+
COMMAND_GROUP = :context
|
8
|
+
|
9
|
+
def run(args = [])
|
10
|
+
debugger.set_scope(nil)
|
11
|
+
debugger.set_remote_node_name(nil)
|
12
|
+
debugger.set_node(nil)
|
13
|
+
debugger.set_facts(nil)
|
14
|
+
debugger.set_environment(nil)
|
15
|
+
debugger.set_compiler(nil)
|
16
|
+
#debugger.handle_input(":set loglevel #{debugger.log_level}")
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'puppet-debugger/input_responder_plugin'
|
2
|
+
module PuppetDebugger
|
3
|
+
module InputResponders
|
4
|
+
class Resources < InputResponderPlugin
|
5
|
+
COMMAND_WORDS = %w(resources)
|
6
|
+
SUMMARY = 'List all the resources current in the catalog.'
|
7
|
+
COMMAND_GROUP = :scope
|
8
|
+
|
9
|
+
def run(args = [])
|
10
|
+
res = debugger.scope.compiler.catalog.resources.map do |res|
|
11
|
+
res.to_s.gsub(/\[/, "['").gsub(/\]/, "']") # ensure the title has quotes
|
12
|
+
end
|
13
|
+
if !args.first.nil?
|
14
|
+
res[args.first.to_i].ai
|
15
|
+
else
|
16
|
+
output = "Resources not shown in any specific order\n".warning
|
17
|
+
output += res.ai
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'puppet-debugger/input_responder_plugin'
|
2
|
+
module PuppetDebugger
|
3
|
+
module InputResponders
|
4
|
+
class Set < InputResponderPlugin
|
5
|
+
COMMAND_WORDS = %w(set :set)
|
6
|
+
SUMMARY = 'Set the a puppet debugger config'
|
7
|
+
COMMAND_GROUP = :scope
|
8
|
+
|
9
|
+
def run(args = [])
|
10
|
+
handle_set(args)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def handle_set(input)
|
16
|
+
output = ''
|
17
|
+
# args = input.split(' ')
|
18
|
+
# args.shift # throw away the set
|
19
|
+
case input.shift
|
20
|
+
when /node/
|
21
|
+
if name = input.shift
|
22
|
+
output = "Resetting to use node #{name}"
|
23
|
+
debugger.set_scope(nil)
|
24
|
+
debugger.set_node(nil)
|
25
|
+
debugger.set_facts(nil)
|
26
|
+
debugger.set_environment(nil)
|
27
|
+
debugger.set_compiler(nil)
|
28
|
+
set_log_level(debugger.log_level)
|
29
|
+
debugger.set_remote_node_name(name)
|
30
|
+
else
|
31
|
+
debugger.out_buffer.puts 'Must supply a valid node name'
|
32
|
+
end
|
33
|
+
when /loglevel/
|
34
|
+
if level = input.shift
|
35
|
+
set_log_level(level)
|
36
|
+
output = "loglevel #{Puppet::Util::Log.level} is set"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
output
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_log_level(level)
|
43
|
+
debugger.log_level = level
|
44
|
+
Puppet::Util::Log.level = level.to_sym
|
45
|
+
buffer_log = Puppet::Util::Log.newdestination(:buffer)
|
46
|
+
if buffer_log
|
47
|
+
# if this is already set the buffer_log is nil
|
48
|
+
buffer_log.out_buffer = debugger.out_buffer
|
49
|
+
buffer_log.err_buffer = debugger.out_buffer
|
50
|
+
end
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'puppet-debugger/input_responder_plugin'
|
2
|
+
module PuppetDebugger
|
3
|
+
module InputResponders
|
4
|
+
class Types < InputResponderPlugin
|
5
|
+
COMMAND_WORDS = %w(types)
|
6
|
+
SUMMARY = 'List all the types available in the environment.'
|
7
|
+
COMMAND_GROUP = :environment
|
8
|
+
|
9
|
+
# @return - returns a list of types available to the environment
|
10
|
+
# if a error occurs we we run the types function again
|
11
|
+
def run(args = [])
|
12
|
+
types
|
13
|
+
end
|
14
|
+
|
15
|
+
def types
|
16
|
+
loaded_types = []
|
17
|
+
begin
|
18
|
+
# this loads all the types, if already loaded the file is skipped
|
19
|
+
Puppet::Type.loadall
|
20
|
+
Puppet::Type.eachtype do |t|
|
21
|
+
next if t.name == :component
|
22
|
+
loaded_types << t.name.to_s
|
23
|
+
end
|
24
|
+
loaded_types.ai
|
25
|
+
rescue Puppet::Error => e
|
26
|
+
puts e.message.red
|
27
|
+
Puppet.info(e.message)
|
28
|
+
# prevent more than two calls and recursive loop
|
29
|
+
return if caller_locations(1, 10).find_all { |f| f.label == 'types' }.count > 2
|
30
|
+
types
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'puppet-debugger/input_responder_plugin'
|
2
|
+
module PuppetDebugger
|
3
|
+
module InputResponders
|
4
|
+
class Vars < InputResponderPlugin
|
5
|
+
COMMAND_WORDS = %w(vars ls)
|
6
|
+
SUMMARY = 'List all the variables in the current scopes.'
|
7
|
+
COMMAND_GROUP = :scope
|
8
|
+
|
9
|
+
def run(args = [])
|
10
|
+
# remove duplicate variables that are also in the facts hash
|
11
|
+
variables = debugger.scope.to_hash.delete_if { |key, _value| debugger.node.facts.values.key?(key) }
|
12
|
+
variables['facts'] = 'removed by the puppet-debugger' if variables.key?('facts')
|
13
|
+
output = 'Facts were removed for easier viewing'.ai + "\n"
|
14
|
+
output += variables.ai(sort_keys: true, indent: -1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'puppet-debugger/input_responder_plugin'
|
2
|
+
module PuppetDebugger
|
3
|
+
module InputResponders
|
4
|
+
class Whereami < InputResponderPlugin
|
5
|
+
COMMAND_WORDS = %w(whereami)
|
6
|
+
SUMMARY = 'Show code surrounding the current context.'
|
7
|
+
COMMAND_GROUP = :context
|
8
|
+
|
9
|
+
# source_file and source_line_num instance variables must be set for this
|
10
|
+
# method to show the surrounding code
|
11
|
+
# @return [String] - string output of the code surrounded by the breakpoint or nil if file
|
12
|
+
# or line_num do not exist
|
13
|
+
def run(args = [])
|
14
|
+
file = debugger.source_file
|
15
|
+
line_num = debugger.source_line_num
|
16
|
+
if file && line_num
|
17
|
+
if file == :code
|
18
|
+
source_code = Puppet[:code]
|
19
|
+
code = DebuggerCode.from_string(source_code, :puppet)
|
20
|
+
else
|
21
|
+
code = DebuggerCode.from_file(file, :puppet)
|
22
|
+
end
|
23
|
+
return code.with_marker(line_num).around(line_num, 5)
|
24
|
+
.with_line_numbers.with_indentation(5).with_file_reference.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/puppet-debugger.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative 'puppet-debugger/cli'
|
3
4
|
require_relative 'version'
|
4
5
|
require 'awesome_print'
|
@@ -6,14 +7,24 @@ require_relative 'awesome_print/ext/awesome_puppet'
|
|
6
7
|
require_relative 'trollop'
|
7
8
|
require 'puppet/util/log'
|
8
9
|
require_relative 'puppet-debugger/debugger_code'
|
9
|
-
|
10
10
|
require_relative 'puppet-debugger/support/errors'
|
11
|
+
require 'plugins/puppet-debugger/input_responders/commands'
|
12
|
+
|
13
|
+
|
11
14
|
# monkey patch in some color effects string methods
|
12
15
|
class String
|
13
16
|
def red
|
14
17
|
"\033[31m#{self}\033[0m"
|
15
18
|
end
|
16
19
|
|
20
|
+
def bold
|
21
|
+
"\033[1m#{self}\033[22m"
|
22
|
+
end
|
23
|
+
|
24
|
+
def black
|
25
|
+
"\033[30m#{self}\033[0m"
|
26
|
+
end
|
27
|
+
|
17
28
|
def green
|
18
29
|
"\033[32m#{self}\033[0m"
|
19
30
|
end
|
data/lib/puppet-debugger/cli.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'puppet'
|
3
4
|
require 'readline'
|
4
5
|
require 'json'
|
5
6
|
require_relative 'support'
|
7
|
+
require 'pluginator'
|
8
|
+
require 'puppet-debugger/hooks'
|
9
|
+
require 'forwardable'
|
6
10
|
|
7
11
|
module PuppetDebugger
|
8
12
|
class Cli
|
9
13
|
include PuppetDebugger::Support
|
10
|
-
|
11
|
-
attr_accessor :settings, :log_level, :in_buffer, :out_buffer, :html_mode
|
14
|
+
extend Forwardable
|
15
|
+
attr_accessor :settings, :log_level, :in_buffer, :out_buffer, :html_mode, :extra_prompt, :bench
|
16
|
+
attr_reader :source_file, :source_line_num, :hooks
|
17
|
+
def_delegators :hooks, :exec_hook, :add_hook, :delete_hook
|
12
18
|
|
13
19
|
def initialize(options = {})
|
14
20
|
do_initialize if Puppet[:codedir].nil?
|
@@ -36,6 +42,10 @@ module PuppetDebugger
|
|
36
42
|
}
|
37
43
|
end
|
38
44
|
|
45
|
+
def hooks
|
46
|
+
@hooks ||= PuppetDebugger::Hooks.new
|
47
|
+
end
|
48
|
+
|
39
49
|
# returns a cached list of key words
|
40
50
|
def key_words
|
41
51
|
# because dollar signs don't work we can't display a $ sign in the keyword
|
@@ -90,23 +100,23 @@ module PuppetDebugger
|
|
90
100
|
result
|
91
101
|
end
|
92
102
|
|
103
|
+
def responder_list
|
104
|
+
plugins = Pluginator.find(PuppetDebugger)
|
105
|
+
end
|
106
|
+
|
93
107
|
# this method handles all input and expects a string of text.
|
94
108
|
#
|
95
109
|
def handle_input(input)
|
96
110
|
raise ArgumentError unless input.instance_of?(String)
|
97
111
|
begin
|
98
112
|
output = ''
|
99
|
-
case input
|
100
|
-
when
|
113
|
+
case input.strip
|
114
|
+
when PuppetDebugger::InputResponders::Commands.command_list_regex
|
101
115
|
args = input.split(' ')
|
102
|
-
command = args.shift
|
103
|
-
|
104
|
-
|
116
|
+
command = args.shift
|
117
|
+
plugin = PuppetDebugger::InputResponders::Commands.plugin_from_command(command)
|
118
|
+
output = plugin.execute(args, self)
|
105
119
|
return out_buffer.puts output
|
106
|
-
when /exit/
|
107
|
-
exit 0
|
108
|
-
when /^:set/
|
109
|
-
output = handle_set(input)
|
110
120
|
when '_'
|
111
121
|
output = " => #{@last_item}"
|
112
122
|
else
|
@@ -115,6 +125,8 @@ module PuppetDebugger
|
|
115
125
|
output = normalize_output(result)
|
116
126
|
output = output.nil? ? '' : output.ai
|
117
127
|
end
|
128
|
+
rescue PuppetDebugger::Exception::InvalidCommand => e
|
129
|
+
output = e.message.fatal
|
118
130
|
rescue LoadError => e
|
119
131
|
output = e.message.fatal
|
120
132
|
rescue Errno::ETIMEDOUT => e
|
@@ -137,6 +149,7 @@ module PuppetDebugger
|
|
137
149
|
unless output.empty?
|
138
150
|
out_buffer.print ' => '
|
139
151
|
out_buffer.puts output unless output.empty?
|
152
|
+
exec_hook :after_output, out_buffer, self, self
|
140
153
|
end
|
141
154
|
end
|
142
155
|
|
@@ -146,9 +159,9 @@ Ruby Version: #{RUBY_VERSION}
|
|
146
159
|
Puppet Version: #{Puppet.version}
|
147
160
|
Puppet Debugger Version: #{PuppetDebugger::VERSION}
|
148
161
|
Created by: NWOps <corey@nwops.io>
|
149
|
-
Type "
|
150
|
-
|
151
|
-
|
162
|
+
Type "commands" for a list of debugger commands
|
163
|
+
or "help" to show the help screen.
|
164
|
+
|
152
165
|
|
153
166
|
EOT
|
154
167
|
output
|
@@ -173,11 +186,11 @@ Type "exit", "functions", "vars", "krt", "whereami", "facts", "resources", "clas
|
|
173
186
|
def read_loop
|
174
187
|
line_number = 1
|
175
188
|
full_buffer = ''
|
176
|
-
while buf = Readline.readline("#{line_number}:#{
|
189
|
+
while buf = Readline.readline("#{line_number}:#{extra_prompt}>> ", true)
|
177
190
|
begin
|
178
191
|
full_buffer += buf
|
179
192
|
# unless this is puppet code, otherwise skip repl keywords
|
180
|
-
unless
|
193
|
+
unless PuppetDebugger::InputResponders::Commands.command_list_regex.match(buf)
|
181
194
|
line_number = line_number.next
|
182
195
|
parser.parse_string(full_buffer)
|
183
196
|
end
|
@@ -197,12 +210,14 @@ Type "exit", "functions", "vars", "krt", "whereami", "facts", "resources", "clas
|
|
197
210
|
# or
|
198
211
|
# this is primarily used by the debug::break() module function and the puppet debugger face
|
199
212
|
# @param [Hash] must contain at least the puppet scope object
|
213
|
+
# @option play - must be a path string
|
200
214
|
def self.start_without_stdin(options = { scope: nil })
|
201
215
|
puts print_repl_desc unless options[:quiet]
|
202
216
|
repl_obj = PuppetDebugger::Cli.new(options)
|
217
|
+
options[:play] = options[:play].path if options[:play].respond_to?(:path)
|
203
218
|
# TODO: make the output optional so we can have different output destinations
|
204
|
-
|
205
|
-
repl_obj.
|
219
|
+
repl_obj.handle_input('whereami') if options[:source_file] && options[:source_line]
|
220
|
+
repl_obj.handle_input("play #{options[:play]}") if options[:play]
|
206
221
|
repl_obj.read_loop unless options[:run_once]
|
207
222
|
end
|
208
223
|
|
@@ -219,15 +234,16 @@ Type "exit", "functions", "vars", "krt", "whereami", "facts", "resources", "clas
|
|
219
234
|
end
|
220
235
|
options = opts.merge(options)
|
221
236
|
puts print_repl_desc unless options[:quiet]
|
237
|
+
options[:play] = options[:play].path if options[:play].respond_to?(:path)
|
222
238
|
repl_obj = PuppetDebugger::Cli.new(options)
|
223
239
|
if options[:play]
|
224
|
-
repl_obj.
|
225
|
-
# when the user supplied a file name without using the args (stdin)
|
240
|
+
repl_obj.handle_input("play #{options[:play]}")
|
226
241
|
elsif ARGF.filename != '-'
|
242
|
+
# when the user supplied a file name without using the args (stdin)
|
227
243
|
path = File.expand_path(ARGF.filename)
|
228
|
-
repl_obj.
|
229
|
-
# when the user supplied a file content using stdin, aka. cat,pipe,echo or redirection
|
244
|
+
repl_obj.handle_input("play #{path}")
|
230
245
|
elsif (ARGF.filename == '-') && (!STDIN.tty? && !STDIN.closed?)
|
246
|
+
# when the user supplied a file content using stdin, aka. cat,pipe,echo or redirection
|
231
247
|
input = ARGF.read
|
232
248
|
repl_obj.handle_input(input)
|
233
249
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
|
2
2
|
# frozen_string_literal: true
|
3
|
+
|
3
4
|
class CodeFile
|
4
5
|
class SourceNotFound < RuntimeError
|
5
6
|
end
|
@@ -9,21 +10,21 @@ class CodeFile
|
|
9
10
|
# List of all supported languages.
|
10
11
|
# @return [Hash]
|
11
12
|
EXTENSIONS = {
|
12
|
-
%w
|
13
|
-
%w
|
14
|
-
%w
|
15
|
-
%w
|
16
|
-
%w
|
17
|
-
%w
|
18
|
-
%w
|
19
|
-
%w
|
20
|
-
%w
|
21
|
-
%w
|
22
|
-
%w
|
23
|
-
%w
|
24
|
-
%w
|
25
|
-
%w
|
26
|
-
%w
|
13
|
+
%w[.py] => :python,
|
14
|
+
%w[.js] => :javascript,
|
15
|
+
%w[.pp] => :puppet,
|
16
|
+
%w[.css] => :css,
|
17
|
+
%w[.xml] => :xml,
|
18
|
+
%w[.php] => :php,
|
19
|
+
%w[.html] => :html,
|
20
|
+
%w[.diff] => :diff,
|
21
|
+
%w[.java] => :java,
|
22
|
+
%w[.json] => :json,
|
23
|
+
%w[.c .h] => :c,
|
24
|
+
%w[.rhtml] => :rhtml,
|
25
|
+
%w[.yaml .yml] => :yaml,
|
26
|
+
%w[.cpp .hpp .cc .h cxx] => :cpp,
|
27
|
+
%w[.rb .ru .irbrc .gemspec .pryrc] => :ruby
|
27
28
|
}.freeze
|
28
29
|
|
29
30
|
# @return [Symbol] The type of code stored in this wrapper.
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'puppet-debugger/support/errors'
|
2
|
+
|
3
|
+
module PuppetDebugger
|
4
|
+
# This code was borrowed from Pry hooks file
|
5
|
+
# Implements a hooks system for Puppet Debugger. A hook is a callable that is associated
|
6
|
+
# with an event. A number of events are currently provided by Pry, these
|
7
|
+
# include: `:when_started`, `:before_session`, `:after_session`. A hook must
|
8
|
+
# have a name, and is connected with an event by the `PuppetDebugger::Hooks#add_hook`
|
9
|
+
# method.
|
10
|
+
#
|
11
|
+
# @example Adding a hook for the `:before_session` event.
|
12
|
+
# debugger.hooks.add_hook(:before_session, :say_hi) do
|
13
|
+
# puts "hello"
|
14
|
+
# end
|
15
|
+
class Hooks
|
16
|
+
def initialize
|
17
|
+
@hooks = Hash.new { |h, k| h[k] = [] }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Ensure that duplicates have their @hooks object.
|
21
|
+
def initialize_copy(orig)
|
22
|
+
hooks_dup = @hooks.dup
|
23
|
+
@hooks.each do |k, v|
|
24
|
+
hooks_dup[k] = v.dup
|
25
|
+
end
|
26
|
+
|
27
|
+
@hooks = hooks_dup
|
28
|
+
end
|
29
|
+
|
30
|
+
def errors
|
31
|
+
@errors ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
# Destructively merge the contents of two `PuppetDebugger::Hooks` instances.
|
35
|
+
#
|
36
|
+
# @param [PuppetDebugger::Hooks] other The `PuppetDebugger::Hooks` instance to merge
|
37
|
+
# @return [PuppetDebugger::Hooks] The receiver.
|
38
|
+
# @see {#merge}
|
39
|
+
def merge!(other)
|
40
|
+
@hooks.merge!(other.dup.hooks) do |key, array, other_array|
|
41
|
+
temp_hash, output = {}, []
|
42
|
+
|
43
|
+
(array + other_array).reverse_each do |pair|
|
44
|
+
temp_hash[pair.first] ||= output.unshift(pair)
|
45
|
+
end
|
46
|
+
|
47
|
+
output
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# @example
|
54
|
+
# hooks = PuppetDebugger::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
55
|
+
# PuppetDebugger::Hooks.new.merge(hooks)
|
56
|
+
# @param [PuppetDebugger::Hooks] other The `PuppetDebugger::Hooks` instance to merge
|
57
|
+
# @return [PuppetDebugger::Hooks] a new `PuppetDebugger::Hooks` instance containing a merge of the
|
58
|
+
# contents of two `PuppetDebugger::Hooks` instances.
|
59
|
+
def merge(other)
|
60
|
+
self.dup.tap do |v|
|
61
|
+
v.merge!(other)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add a new hook to be executed for the `event_name` event.
|
66
|
+
# @param [Symbol] event_name The name of the event.
|
67
|
+
# @param [Symbol] hook_name The name of the hook.
|
68
|
+
# @param [#call] callable The callable.
|
69
|
+
# @yield The block to use as the callable (if no `callable` provided).
|
70
|
+
# @return [PuppetDebugger::Hooks] The receiver.
|
71
|
+
def add_hook(event_name, hook_name, callable=nil, &block)
|
72
|
+
event_name = event_name.to_s
|
73
|
+
|
74
|
+
# do not allow duplicates, but allow multiple `nil` hooks
|
75
|
+
# (anonymous hooks)
|
76
|
+
if hook_exists?(event_name, hook_name) && !hook_name.nil?
|
77
|
+
raise ArgumentError, "Hook with name '#{hook_name}' already defined!"
|
78
|
+
end
|
79
|
+
|
80
|
+
if !block && !callable
|
81
|
+
raise ArgumentError, "Must provide a block or callable."
|
82
|
+
end
|
83
|
+
|
84
|
+
# ensure we only have one anonymous hook
|
85
|
+
@hooks[event_name].delete_if { |h, k| h.nil? } if hook_name.nil?
|
86
|
+
|
87
|
+
if block
|
88
|
+
@hooks[event_name] << [hook_name, block]
|
89
|
+
elsif callable
|
90
|
+
@hooks[event_name] << [hook_name, callable]
|
91
|
+
end
|
92
|
+
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Execute the list of hooks for the `event_name` event.
|
97
|
+
# @param [Symbol] event_name The name of the event.
|
98
|
+
# @param [Array] args The arguments to pass to each hook function.
|
99
|
+
# @return [Object] The return value of the last executed hook.
|
100
|
+
def exec_hook(event_name, *args, &block)
|
101
|
+
@hooks[event_name.to_s].map do |hook_name, callable|
|
102
|
+
begin
|
103
|
+
callable.call(*args, &block)
|
104
|
+
rescue PuppetDebugger::Exception::Error, ::RuntimeError => e
|
105
|
+
errors << e
|
106
|
+
e
|
107
|
+
end
|
108
|
+
end.last
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [Symbol] event_name The name of the event.
|
112
|
+
# @return [Fixnum] The number of hook functions for `event_name`.
|
113
|
+
def hook_count(event_name)
|
114
|
+
@hooks[event_name.to_s].size
|
115
|
+
end
|
116
|
+
|
117
|
+
# @param [Symbol] event_name The name of the event.
|
118
|
+
# @param [Symbol] hook_name The name of the hook
|
119
|
+
# @return [#call] a specific hook for a given event.
|
120
|
+
def get_hook(event_name, hook_name)
|
121
|
+
hook = @hooks[event_name.to_s].find do |current_hook_name, callable|
|
122
|
+
current_hook_name == hook_name
|
123
|
+
end
|
124
|
+
hook.last if hook
|
125
|
+
end
|
126
|
+
|
127
|
+
# @param [Symbol] event_name The name of the event.
|
128
|
+
# @return [Hash] The hash of hook names / hook functions.
|
129
|
+
# @note Modifying the returned hash does not alter the hooks, use
|
130
|
+
# `add_hook`/`delete_hook` for that.
|
131
|
+
def get_hooks(event_name)
|
132
|
+
Hash[@hooks[event_name.to_s]]
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param [Symbol] event_name The name of the event.
|
136
|
+
# @param [Symbol] hook_name The name of the hook.
|
137
|
+
# to delete.
|
138
|
+
# @return [#call] The deleted hook.
|
139
|
+
def delete_hook(event_name, hook_name)
|
140
|
+
deleted_callable = nil
|
141
|
+
|
142
|
+
@hooks[event_name.to_s].delete_if do |current_hook_name, callable|
|
143
|
+
if current_hook_name == hook_name
|
144
|
+
deleted_callable = callable
|
145
|
+
true
|
146
|
+
else
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
deleted_callable
|
151
|
+
end
|
152
|
+
|
153
|
+
# Clear all hooks functions for a given event.
|
154
|
+
#
|
155
|
+
# @param [String] event_name The name of the event.
|
156
|
+
# @return [void]
|
157
|
+
def clear_event_hooks(event_name)
|
158
|
+
@hooks[event_name.to_s] = []
|
159
|
+
end
|
160
|
+
|
161
|
+
# @param [Symbol] event_name Name of the event.
|
162
|
+
# @param [Symbol] hook_name Name of the hook.
|
163
|
+
# @return [Boolean] Whether the hook by the name `hook_name`.
|
164
|
+
def hook_exists?(event_name, hook_name)
|
165
|
+
@hooks[event_name.to_s].map(&:first).include?(hook_name)
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
def hooks
|
171
|
+
@hooks
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|