puppet-debugger 0.4.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 +7 -0
- data/.document +5 -0
- data/.gitignore +54 -0
- data/.gitlab-ci.yml +129 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +61 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +67 -0
- data/LICENSE.txt +20 -0
- data/README.md +276 -0
- data/Rakefile +32 -0
- data/bin/pdb +4 -0
- data/lib/awesome_print/ext/awesome_puppet.rb +40 -0
- data/lib/puppet-debugger/cli.rb +247 -0
- data/lib/puppet-debugger/code/code_file.rb +98 -0
- data/lib/puppet-debugger/code/code_range.rb +69 -0
- data/lib/puppet-debugger/code/loc.rb +80 -0
- data/lib/puppet-debugger/debugger_code.rb +318 -0
- data/lib/puppet-debugger/support/compiler.rb +20 -0
- data/lib/puppet-debugger/support/environment.rb +38 -0
- data/lib/puppet-debugger/support/errors.rb +75 -0
- data/lib/puppet-debugger/support/facts.rb +78 -0
- data/lib/puppet-debugger/support/functions.rb +72 -0
- data/lib/puppet-debugger/support/input_responders.rb +136 -0
- data/lib/puppet-debugger/support/node.rb +90 -0
- data/lib/puppet-debugger/support/play.rb +91 -0
- data/lib/puppet-debugger/support/scope.rb +42 -0
- data/lib/puppet-debugger/support.rb +176 -0
- data/lib/puppet-debugger.rb +55 -0
- data/lib/trollop.rb +861 -0
- data/lib/version.rb +3 -0
- data/puppet-debugger.gemspec +36 -0
- data/run_container_test.sh +12 -0
- data/spec/facts_spec.rb +86 -0
- data/spec/fixtures/environments/production/manifests/site.pp +1 -0
- data/spec/fixtures/invalid_node_obj.yaml +8 -0
- data/spec/fixtures/node_obj.yaml +298 -0
- data/spec/fixtures/sample_manifest.pp +2 -0
- data/spec/fixtures/sample_start_debugger.pp +13 -0
- data/spec/pdb_spec.rb +50 -0
- data/spec/puppet-debugger_spec.rb +492 -0
- data/spec/remote_node_spec.rb +170 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/support_spec.rb +190 -0
- data/test_matrix.rb +42 -0
- metadata +148 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module AwesomePrint
|
2
|
+
module Puppet
|
3
|
+
def self.included(base)
|
4
|
+
base.send :alias_method, :cast_without_puppet_resource, :cast
|
5
|
+
base.send :alias_method, :cast, :cast_with_puppet_resource
|
6
|
+
end
|
7
|
+
|
8
|
+
# this tells ap how to cast our object so we can be specific
|
9
|
+
# about printing different puppet objects
|
10
|
+
def cast_with_puppet_resource(object, type)
|
11
|
+
cast = cast_without_puppet_resource(object, type)
|
12
|
+
# check the object to see if it has an acestor (< ) of the specified type
|
13
|
+
if (defined?(::Puppet::Type)) && (object.class < ::Puppet::Type)
|
14
|
+
cast = :puppet_type
|
15
|
+
elsif (defined?(::Puppet::Pops::Types)) && (object.class < ::Puppet::Pops::Types)
|
16
|
+
cast = :puppet_type
|
17
|
+
elsif (defined?(::Puppet::Parser::Resource)) && (object.class < ::Puppet::Parser::Resource)
|
18
|
+
cast = :puppet_resource
|
19
|
+
elsif /Puppet::Pops::Types/.match(object.class.to_s)
|
20
|
+
cast = :puppet_type
|
21
|
+
end
|
22
|
+
cast
|
23
|
+
end
|
24
|
+
|
25
|
+
def awesome_puppet_resource(object)
|
26
|
+
return '' if object.nil?
|
27
|
+
awesome_puppet_type(object.to_ral)
|
28
|
+
end
|
29
|
+
|
30
|
+
def awesome_puppet_type(object)
|
31
|
+
return '' if object.nil?
|
32
|
+
return object.to_s unless object.respond_to?(:name) && object.respond_to?(:title)
|
33
|
+
h = object.to_hash.merge(:name => object.name, :title => object.title)
|
34
|
+
res_str = awesome_hash(h)
|
35
|
+
"#{object.class} #{res_str.gsub(':', '')}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
AwesomePrint::Formatter.send(:include, AwesomePrint::Puppet)
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'puppet'
|
2
|
+
require 'readline'
|
3
|
+
require 'json'
|
4
|
+
require_relative 'support'
|
5
|
+
|
6
|
+
module PuppetDebugger
|
7
|
+
class Cli
|
8
|
+
include PuppetDebugger::Support
|
9
|
+
|
10
|
+
attr_accessor :settings, :log_level, :in_buffer, :out_buffer, :html_mode
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
@log_level = 'notice'
|
14
|
+
@out_buffer = options[:out_buffer] || $stdout
|
15
|
+
@html_mode = options[:html_mode] || false
|
16
|
+
@source_file = options[:source_file] || nil
|
17
|
+
@source_line_num = options[:source_line] || nil
|
18
|
+
@in_buffer = options[:in_buffer] || $stdin
|
19
|
+
comp = Proc.new do |s|
|
20
|
+
key_words.grep(/^#{Regexp.escape(s)}/)
|
21
|
+
end
|
22
|
+
Readline.completion_append_character = ""
|
23
|
+
Readline.basic_word_break_characters = " "
|
24
|
+
Readline.completion_proc = comp
|
25
|
+
AwesomePrint.defaults = {
|
26
|
+
:html => @html_mode,
|
27
|
+
:sort_keys => true,
|
28
|
+
:indent => 2
|
29
|
+
}
|
30
|
+
do_initialize
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns a cached list of key words
|
34
|
+
def key_words
|
35
|
+
# because dollar signs don't work we can't display a $ sign in the keyword
|
36
|
+
# list so its not explicitly clear what the keyword
|
37
|
+
variables = scope.to_hash.keys
|
38
|
+
# prepend a :: to topscope variables
|
39
|
+
scoped_vars = variables.map { |k,v| scope.compiler.topscope.exist?(k) ? "$::#{k}" : "$#{k}" }
|
40
|
+
# append a () to functions so we know they are functions
|
41
|
+
funcs = function_map.keys.map { |k| "#{k.split('::').last}()"}
|
42
|
+
(scoped_vars + funcs + static_responder_list).uniq.sort
|
43
|
+
end
|
44
|
+
|
45
|
+
# looks up the type in the catalog by using the type and title
|
46
|
+
# and returns the resource in ral format
|
47
|
+
def to_resource_declaration(type)
|
48
|
+
if type.respond_to?(:type_name) and type.respond_to?(:title)
|
49
|
+
title = type.title
|
50
|
+
type_name = type.type_name
|
51
|
+
elsif type_result = /(\w+)\['?(\w+)'?\]/.match(type.to_s)
|
52
|
+
# not all types have a type_name and title so we
|
53
|
+
# output to a string and parse the results
|
54
|
+
title = type_result[2]
|
55
|
+
type_name = type_result[1]
|
56
|
+
else
|
57
|
+
return type
|
58
|
+
end
|
59
|
+
res = scope.catalog.resource(type_name, title)
|
60
|
+
if res
|
61
|
+
return res.to_ral
|
62
|
+
end
|
63
|
+
# don't return anything or returns nil if item is not in the catalog
|
64
|
+
end
|
65
|
+
|
66
|
+
# ruturns a formatted array
|
67
|
+
def expand_resource_type(types)
|
68
|
+
output = [types].flatten.map do |t|
|
69
|
+
if t.class.to_s =~ /Puppet::Pops::Types/
|
70
|
+
to_resource_declaration(t)
|
71
|
+
else
|
72
|
+
t
|
73
|
+
end
|
74
|
+
end
|
75
|
+
output
|
76
|
+
end
|
77
|
+
|
78
|
+
def normalize_output(result)
|
79
|
+
if result.instance_of?(Array)
|
80
|
+
output = expand_resource_type(result)
|
81
|
+
if output.count == 1
|
82
|
+
return output.first
|
83
|
+
end
|
84
|
+
return output
|
85
|
+
elsif result.class.to_s =~ /Puppet::Pops::Types/
|
86
|
+
return to_resource_declaration(result)
|
87
|
+
end
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
# this method handles all input and expects a string of text.
|
92
|
+
#
|
93
|
+
def handle_input(input)
|
94
|
+
raise ArgumentError unless input.instance_of?(String)
|
95
|
+
begin
|
96
|
+
output = ''
|
97
|
+
case input
|
98
|
+
when /^play|^classification|^whereami|^facterdb_filter|^facts|^vars|^functions|^classes|^resources|^krt|^environment|^reset|^help/
|
99
|
+
args = input.split(' ')
|
100
|
+
command = args.shift.to_sym
|
101
|
+
if self.respond_to?(command)
|
102
|
+
output = self.send(command, args)
|
103
|
+
end
|
104
|
+
return out_buffer.puts output
|
105
|
+
when /exit/
|
106
|
+
exit 0
|
107
|
+
when /^:set/
|
108
|
+
output = handle_set(input)
|
109
|
+
when '_'
|
110
|
+
output = " => #{@last_item}"
|
111
|
+
else
|
112
|
+
result = puppet_eval(input)
|
113
|
+
@last_item = result
|
114
|
+
output = normalize_output(result)
|
115
|
+
if output.nil?
|
116
|
+
output = ""
|
117
|
+
else
|
118
|
+
output = output.ai
|
119
|
+
end
|
120
|
+
end
|
121
|
+
rescue LoadError => e
|
122
|
+
output = e.message.fatal
|
123
|
+
rescue Errno::ETIMEDOUT => e
|
124
|
+
output = e.message.fatal
|
125
|
+
rescue ArgumentError => e
|
126
|
+
output = e.message.fatal
|
127
|
+
rescue Puppet::ResourceError => e
|
128
|
+
output = e.message.fatal
|
129
|
+
rescue Puppet::Error => e
|
130
|
+
output = e.message.fatal
|
131
|
+
rescue Puppet::ParseErrorWithIssue => e
|
132
|
+
output = e.message.fatal
|
133
|
+
rescue PuppetDebugger::Exception::FatalError => e
|
134
|
+
output = e.message.fatal
|
135
|
+
out_buffer.puts output
|
136
|
+
exit 1 # this can sometimes causes tests to fail
|
137
|
+
rescue PuppetDebugger::Exception::Error => e
|
138
|
+
output = e.message.fatal
|
139
|
+
end
|
140
|
+
unless output.empty?
|
141
|
+
out_buffer.print " => "
|
142
|
+
out_buffer.puts output unless output.empty?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.print_repl_desc
|
147
|
+
output = <<-EOT
|
148
|
+
Ruby Version: #{RUBY_VERSION}
|
149
|
+
Puppet Version: #{Puppet.version}
|
150
|
+
Puppet Debugger Version: #{PuppetDebugger::VERSION}
|
151
|
+
Created by: NWOps <corey@nwops.io>
|
152
|
+
Type "exit", "functions", "vars", "krt", "whereami", "facts", "resources", "classes",
|
153
|
+
"play", "classification", "reset", or "help" for more information.
|
154
|
+
|
155
|
+
EOT
|
156
|
+
output
|
157
|
+
end
|
158
|
+
|
159
|
+
# tries to determine if the input is going to be a multiline input
|
160
|
+
# by reading the parser error message
|
161
|
+
def multiline_input?(e)
|
162
|
+
case e.message
|
163
|
+
when /Syntax error at end of file/i
|
164
|
+
true
|
165
|
+
else
|
166
|
+
false
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# reads input from stdin, since readline requires a tty
|
171
|
+
# we cannot read from other sources as readline requires a file object
|
172
|
+
# we parse the string after each input to determine if the input is a multiline_input
|
173
|
+
# entry. If it is multiline we run through the loop again and concatenate the
|
174
|
+
# input
|
175
|
+
def read_loop
|
176
|
+
line_number = 1
|
177
|
+
full_buffer = ''
|
178
|
+
while buf = Readline.readline("#{line_number}:>> ", true)
|
179
|
+
begin
|
180
|
+
full_buffer += buf
|
181
|
+
# unless this is puppet code, otherwise skip repl keywords
|
182
|
+
unless keyword_expression.match(buf)
|
183
|
+
line_number = line_number.next
|
184
|
+
parser.parse_string(full_buffer)
|
185
|
+
end
|
186
|
+
rescue Puppet::ParseErrorWithIssue => e
|
187
|
+
if multiline_input?(e)
|
188
|
+
out_buffer.print ' '
|
189
|
+
full_buffer += "\n"
|
190
|
+
next
|
191
|
+
end
|
192
|
+
end
|
193
|
+
handle_input(full_buffer)
|
194
|
+
full_buffer = ''
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# used to start a repl without attempting to read from stdin
|
199
|
+
# or
|
200
|
+
# @param [Hash] must contain at least the puppet scope object
|
201
|
+
def self.start_without_stdin(options={:scope => nil})
|
202
|
+
puts print_repl_desc unless options[:quiet]
|
203
|
+
repl_obj = PuppetDebugger::Cli.new(options)
|
204
|
+
repl_obj.remote_node_name = options[:node_name] if options[:node_name]
|
205
|
+
repl_obj.initialize_from_scope(options[:scope])
|
206
|
+
puts repl_obj.whereami if options[:source_file] and options[:source_line]
|
207
|
+
if options[:play]
|
208
|
+
repl_obj.play_back(options)
|
209
|
+
elsif ! options[:run_once]
|
210
|
+
repl_obj.read_loop
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# start reads from stdin or from a file
|
215
|
+
# if from stdin, the repl will process the input and exit
|
216
|
+
# if from a file, the repl will process the file and continue to prompt
|
217
|
+
# @param [Hash] puppet scope object
|
218
|
+
def self.start(options={:scope => nil})
|
219
|
+
opts = Trollop::options do
|
220
|
+
opt :play, "Url or file to load from", :required => false, :type => String
|
221
|
+
opt :run_once, "Evaluate and quit", :required => false, :default => false
|
222
|
+
opt :node_name, "Remote Node to grab facts from", :required => false, :type => String
|
223
|
+
opt :quiet, "Do not display banner", :required => false, :default => false
|
224
|
+
end
|
225
|
+
options = opts.merge(options)
|
226
|
+
puts print_repl_desc unless options[:quiet]
|
227
|
+
repl_obj = PuppetDebugger::Cli.new(options)
|
228
|
+
repl_obj.remote_node_name = opts[:node_name] if opts[:node_name]
|
229
|
+
repl_obj.initialize_from_scope(options[:scope])
|
230
|
+
if options[:play]
|
231
|
+
repl_obj.play_back(opts)
|
232
|
+
# when the user supplied a file name without using the args (stdin)
|
233
|
+
elsif ARGF.filename != "-"
|
234
|
+
path = File.expand_path(ARGF.filename)
|
235
|
+
repl_obj.play_back(:play => path)
|
236
|
+
# when the user supplied a file content using stdin, aka. cat,pipe,echo or redirection
|
237
|
+
elsif ARGF.filename == "-" and (not STDIN.tty? and not STDIN.closed?)
|
238
|
+
input = ARGF.read
|
239
|
+
repl_obj.handle_input(input)
|
240
|
+
end
|
241
|
+
# helper code to make tests exit the loop
|
242
|
+
unless options[:run_once]
|
243
|
+
repl_obj.read_loop
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
class CodeFile
|
3
|
+
class SourceNotFound < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
DEFAULT_EXT = '.pp'
|
7
|
+
|
8
|
+
# List of all supported languages.
|
9
|
+
# @return [Hash]
|
10
|
+
EXTENSIONS = {
|
11
|
+
%w(.py) => :python,
|
12
|
+
%w(.js) => :javascript,
|
13
|
+
%w(.pp) => :puppet,
|
14
|
+
%w(.css) => :css,
|
15
|
+
%w(.xml) => :xml,
|
16
|
+
%w(.php) => :php,
|
17
|
+
%w(.html) => :html,
|
18
|
+
%w(.diff) => :diff,
|
19
|
+
%w(.java) => :java,
|
20
|
+
%w(.json) => :json,
|
21
|
+
%w(.c .h) => :c,
|
22
|
+
%w(.rhtml) => :rhtml,
|
23
|
+
%w(.yaml .yml) => :yaml,
|
24
|
+
%w(.cpp .hpp .cc .h cxx) => :cpp,
|
25
|
+
%w(.rb .ru .irbrc .gemspec .pryrc) => :ruby,
|
26
|
+
}
|
27
|
+
|
28
|
+
# @return [Symbol] The type of code stored in this wrapper.
|
29
|
+
attr_reader :code_type
|
30
|
+
|
31
|
+
# @param [String] filename The name of a file with code to be detected
|
32
|
+
# @param [Symbol] code_type The type of code the `filename` contains
|
33
|
+
def initialize(filename, code_type = type_from_filename(filename))
|
34
|
+
@filename = filename
|
35
|
+
@code_type = code_type
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] The code contained in the current `@filename`.
|
39
|
+
def code
|
40
|
+
path = abs_path
|
41
|
+
@code_type = type_from_filename(path)
|
42
|
+
File.read(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @raise [MethodSource::SourceNotFoundError] if the `filename` is not
|
48
|
+
# readable for some reason.
|
49
|
+
# @return [String] absolute path for the given `filename`.
|
50
|
+
def abs_path
|
51
|
+
code_path.detect { |path| readable?(path) } or
|
52
|
+
raise SourceNotFound,
|
53
|
+
"Cannot open #{ @filename.inspect } for reading."
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param [String] path
|
57
|
+
# @return [Boolean] if the path, with or without the default ext,
|
58
|
+
# is a readable file then `true`, otherwise `false`.
|
59
|
+
def readable?(path)
|
60
|
+
File.readable?(path) && !File.directory?(path) or
|
61
|
+
File.readable?(path << DEFAULT_EXT)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Array] All the paths that contain code that Pry can use for its
|
65
|
+
# API's. Skips directories.
|
66
|
+
def code_path
|
67
|
+
[from_pwd, from_pry_init_pwd, *from_load_path]
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param [String] filename
|
71
|
+
# @param [Symbol] default (:unknown) the file type to assume if none could be
|
72
|
+
# detected.
|
73
|
+
# @return [Symbol, nil] The CodeRay type of a file from its extension, or
|
74
|
+
# `nil` if `:unknown`.
|
75
|
+
def type_from_filename(filename, default = :unknown)
|
76
|
+
_, @code_type = EXTENSIONS.find do |k, _|
|
77
|
+
k.any? { |ext| ext == File.extname(filename) }
|
78
|
+
end
|
79
|
+
|
80
|
+
code_type || default
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [String]
|
84
|
+
def from_pwd
|
85
|
+
File.expand_path(@filename, Dir.pwd)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [String]
|
89
|
+
def from_pry_init_pwd
|
90
|
+
File.expand_path(@filename, Dir.pwd)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [String]
|
94
|
+
def from_load_path
|
95
|
+
$LOAD_PATH.map { |path| File.expand_path(@filename, path) }
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class DebuggerCode
|
2
|
+
|
3
|
+
# Represents a range of lines in a code listing.
|
4
|
+
#
|
5
|
+
# @api private
|
6
|
+
class CodeRange
|
7
|
+
|
8
|
+
# @param [Integer] start_line
|
9
|
+
# @param [Integer?] end_line
|
10
|
+
def initialize(start_line, end_line = nil)
|
11
|
+
@start_line = start_line
|
12
|
+
@end_line = end_line
|
13
|
+
force_set_end_line
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Array<LOC>] lines
|
17
|
+
# @return [Range]
|
18
|
+
def indices_range(lines)
|
19
|
+
Range.new(*indices(lines))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def start_line; @start_line; end
|
25
|
+
def end_line; @end_line; end
|
26
|
+
|
27
|
+
# If `end_line` is equal to `nil`, then calculate it from the first
|
28
|
+
# parameter, `start_line`. Otherwise, leave it as it is.
|
29
|
+
# @return [void]
|
30
|
+
def force_set_end_line
|
31
|
+
if start_line.is_a?(Range)
|
32
|
+
set_end_line_from_range
|
33
|
+
else
|
34
|
+
@end_line ||= start_line
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Finds indices of `start_line` and `end_line` in the given Array of
|
39
|
+
# +lines+.
|
40
|
+
#
|
41
|
+
# @param [Array<LOC>] lines
|
42
|
+
# @return [Array<Integer>]
|
43
|
+
def indices(lines)
|
44
|
+
[find_start_index(lines), find_end_index(lines)]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Integer]
|
48
|
+
def find_start_index(lines)
|
49
|
+
return start_line if start_line < 0
|
50
|
+
lines.index { |loc| loc.lineno >= start_line } || lines.length
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Integer]
|
54
|
+
def find_end_index(lines)
|
55
|
+
return end_line if end_line < 0
|
56
|
+
(lines.index { |loc| loc.lineno > end_line } || 0) - 1
|
57
|
+
end
|
58
|
+
|
59
|
+
# For example, if the range is 4..10, then `start_line` would be equal to
|
60
|
+
# 4 and `end_line` to 10.
|
61
|
+
# @return [void]
|
62
|
+
def set_end_line_from_range
|
63
|
+
@end_line = start_line.last
|
64
|
+
@end_line -= 1 if start_line.exclude_end?
|
65
|
+
@start_line = start_line.first
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class DebuggerCode
|
2
|
+
|
3
|
+
# Represents a line of code. A line of code is a tuple, which consists of a
|
4
|
+
# line and a line number. A `LOC` object's state (namely, the line
|
5
|
+
# parameter) can be changed via instance methods. `Pry::Code` heavily uses
|
6
|
+
# this class.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @example
|
10
|
+
# loc = LOC.new("def example\n :example\nend", 1)
|
11
|
+
# puts loc.line
|
12
|
+
# def example
|
13
|
+
# :example
|
14
|
+
# end
|
15
|
+
# #=> nil
|
16
|
+
#
|
17
|
+
# loc.indent(3)
|
18
|
+
# loc.line #=> " def example\n :example\nend"
|
19
|
+
class LOC
|
20
|
+
|
21
|
+
# @return [Array<String, Integer>]
|
22
|
+
attr_reader :tuple
|
23
|
+
|
24
|
+
# @param [String] line The line of code.
|
25
|
+
# @param [Integer] lineno The position of the +line+.
|
26
|
+
def initialize(line, lineno)
|
27
|
+
@tuple = [line.chomp, lineno.to_i]
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean]
|
31
|
+
def ==(other)
|
32
|
+
other.tuple == tuple
|
33
|
+
end
|
34
|
+
|
35
|
+
def dup
|
36
|
+
self.class.new(line, lineno)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
def line
|
41
|
+
tuple.first
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer]
|
45
|
+
def lineno
|
46
|
+
tuple.last
|
47
|
+
end
|
48
|
+
|
49
|
+
#Prepends the line number `lineno` to the `line`.
|
50
|
+
# @param [Integer] max_width
|
51
|
+
# @return [void]
|
52
|
+
def add_line_number(max_width = 0)
|
53
|
+
padded = lineno.to_s.rjust(max_width)
|
54
|
+
tuple[0] = "#{ padded }: #{ line }"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Prepends a marker "=>" or an empty marker to the +line+.
|
58
|
+
#
|
59
|
+
# @param [Integer] marker_lineno If it is equal to the `lineno`, then
|
60
|
+
# prepend a hashrocket. Otherwise, an empty marker.
|
61
|
+
# @return [void]
|
62
|
+
def add_marker(marker_lineno)
|
63
|
+
tuple[0] =
|
64
|
+
if lineno == marker_lineno
|
65
|
+
" => #{ line }".cyan
|
66
|
+
else
|
67
|
+
" #{ line }"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Indents the `line` with +distance+ spaces.
|
72
|
+
#
|
73
|
+
# @param [Integer] distance
|
74
|
+
# @return [void]
|
75
|
+
def indent(distance)
|
76
|
+
tuple[0] = "#{ ' ' * distance }#{ line }"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|