puppet-debugger 0.6.1 → 0.7.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.
- 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
|