Class: Wrong::Chunk

Inherits:
Object show all
Defined in:
lib/wrong/chunk.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, line_number, &block) ⇒ Chunk

line parameter is 1-based



43
44
45
46
47
# File 'lib/wrong/chunk.rb', line 43

def initialize(file, line_number, &block)
  @file = file
  @line_number = line_number.to_i
  @block = block
end

Instance Attribute Details

#blockObject (readonly)

Returns the value of attribute block.



40
41
42
# File 'lib/wrong/chunk.rb', line 40

def block
  @block
end

#fileObject (readonly)

Returns the value of attribute file.



40
41
42
# File 'lib/wrong/chunk.rb', line 40

def file
  @file
end

#line_numberObject (readonly)

Returns the value of attribute line_number.



40
41
42
# File 'lib/wrong/chunk.rb', line 40

def line_number
  @line_number
end

Class Method Details

.command_exists?(command) ⇒ Boolean

Determines if a shell command exists by searching for it in ENV.

Returns:

  • (Boolean)


294
295
296
# File 'lib/wrong/chunk.rb', line 294

def self.command_exists?(command)
  ENV['PATH'].split(File::PATH_SEPARATOR).any? {|d| File.exists? File.join(d, command) }
end

.from_block(block, depth = 0) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/wrong/chunk.rb', line 21

def self.from_block(block, depth = 0)

  as_proc = block.to_proc
  file, line =
          if as_proc.respond_to? :source_location
            # in Ruby 1.9, or with Sourcify, it reads the source location from the block
            as_proc.source_location
          else
            # in Ruby 1.8, it reads the source location from the call stack
            # # $stderr.puts "---"
            # $stderr.puts caller.join("\n")
            relevant_caller = caller[depth]
            # $stderr.puts "*** #{relevant_caller}"
            relevant_caller.split(":")
          end

  new(file, line, &block)
end

.terminal_sizeObject

Returns [width, height] of terminal when detected, nil if not detected. Think of this as a simpler version of Highline’s Highline::SystemExtensions.terminal_size() Lifted from github.com/cldwalker/hirb/blob/master/lib/hirb/util.rb#L59



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/wrong/chunk.rb', line 269

def self.terminal_size
  @@terminal_size ||= begin
    if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
      [ENV['COLUMNS'].to_i, ENV['LINES'].to_i]
    elsif (RUBY_PLATFORM =~ /java/ || (!STDIN.tty? && ENV['TERM'])) && command_exists?('tput')
      [`tput cols`.to_i, `tput lines`.to_i]
    elsif STDIN.tty? && command_exists?('stty')
      `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse
    else
      nil
    end
  rescue
    nil
  end  
end

.terminal_widthObject



285
286
287
# File 'lib/wrong/chunk.rb', line 285

def self.terminal_width
  (@terminal_width ||= nil) || (terminal_size && terminal_size.first) || 80
end

.terminal_width=(forced_with) ⇒ Object



289
290
291
# File 'lib/wrong/chunk.rb', line 289

def self.terminal_width= forced_with
  @terminal_width = forced_with
end

Instance Method Details

#build_sexpObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/wrong/chunk.rb', line 61

def build_sexp
  sexp = begin
    unless @block.nil? or @block.is_a?(String) or !Object.const_defined?(:Sourcify)
      # first try sourcify
      @block.to_sexp[3] # the [3] is to strip out the "proc {" sourcify adds to everything
    end
  rescue Exception => e
    # sourcify failed, so fall through
  end

  # next try glomming
  sexp ||= glom(if @file == "(irb)"
                  IRB.CurrentContext.all_lines
                else
                  read_source_file(@file)
                end)
end

#claimObject

The claim is the part of the assertion inside the curly braces. E.g. for “assert { x == 5 }” the claim is “x == 5”



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/wrong/chunk.rb', line 108

def claim
  sexp()

  if @sexp.nil?
    raise "Could not parse #{location}"
  else
    assertion = @sexp.assertion
    statement = assertion && assertion[3]
    if statement.nil?
      @sexp
#          raise "Could not find assertion in #{location}\n\t#{@chunk.strip}\n\t#{@sexp}"
    else
      statement
    end
  end
end

#codeObject



125
126
127
128
129
130
131
# File 'lib/wrong/chunk.rb', line 125

def code
  self.claim.to_ruby
rescue => e
  # note: this is untested; it's to recover from when we can't locate the code
  message = "Failed at #{file}:#{line_number} [couldn't retrieve source code due to #{e.inspect}]"
  raise message
end

#detailsObject



163
164
165
# File 'lib/wrong/chunk.rb', line 163

def details
  @details ||= build_details
end

#glom(source) ⇒ Object

Algorithm:

  • try to parse the starting line

  • if it parses OK, then we’re done!

  • if not, then glom the next line and try again

  • repeat until it parses or we’re out of lines



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/wrong/chunk.rb', line 88

def glom(source)
  lines = source.split("\n")
  @parser ||= RubyParser.new
  @chunk = nil
  c = 0
  sexp = nil
  while sexp.nil? && line_index + c < lines.size
    begin
      @chunk = lines[line_index..line_index+c].join("\n")
      sexp = @parser.parse(@chunk)
    rescue Racc::ParseError => e
      # loop and try again
      c += 1
    end
  end
  sexp
end

#indent(indent, *s) ⇒ Object



235
236
237
# File 'lib/wrong/chunk.rb', line 235

def indent(indent, *s)
  "#{" " * indent}#{s.join('')}"
end

#indent_all(amount, s) ⇒ Object



243
244
245
# File 'lib/wrong/chunk.rb', line 243

def indent_all(amount, s)
  s.gsub("\n", "\n#{indent(amount)}")
end

#line_indexObject



49
50
51
# File 'lib/wrong/chunk.rb', line 49

def line_index
  @line_number - 1
end

#locationObject



53
54
55
# File 'lib/wrong/chunk.rb', line 53

def location
  "#{@file}:#{@line_number}"
end

#newline(indent) ⇒ Object



239
240
241
# File 'lib/wrong/chunk.rb', line 239

def newline(indent)
  "\n" + self.indent(indent)
end

#parts(sexp = nil) ⇒ Object



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
# File 'lib/wrong/chunk.rb', line 133

def parts(sexp = nil)
  if sexp.nil?
    parts(self.claim).compact.uniq
  else
    # todo: extract some of this into Sexp
    parts_list = []
    begin
      unless [:arglist, :lasgn, :iter].include? sexp.first
        code = sexp.to_ruby.strip
        parts_list << code unless code == "" || parts_list.include?(code)
      end
    rescue => e
      puts "#{e.class}: #{e.message}"
      puts e.backtrace.join("\n")
    end

    if sexp.first == :iter
      sexp.delete_at(1) # remove the method-call-sans-block subnode
    end

    sexp.each do |sub|
      if sub.is_a?(Sexp)
        parts_list += parts(sub)
      end
    end

    parts_list
  end
end

#pretty_value(value, starting_col = 0, indent_wrapped_lines = 6, width = Chunk.terminal_width) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/wrong/chunk.rb', line 167

def pretty_value(value, starting_col = 0, indent_wrapped_lines = 6, width = Chunk.terminal_width)
  # inspected = value.inspect

  # note that if the first line overflows due to the starting column then pp won't wrap it right
  inspected = PP.pp(value, "", width - starting_col).chomp

  # this bit might be redundant with the pp call now
  indented = indent_all(6, inspected)
  if width
    wrap_and_indent(indented, starting_col, indent_wrapped_lines, width)
  else
    indented
  end
end

#read_source_file(file) ⇒ Object



79
80
81
# File 'lib/wrong/chunk.rb', line 79

def read_source_file(file)
  Config.read_here_or_higher(file)
end

#sexpObject



57
58
59
# File 'lib/wrong/chunk.rb', line 57

def sexp
  @sexp ||= build_sexp
end

#wrap_and_indent(indented, starting_col, indent_wrapped_lines, full_width) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/wrong/chunk.rb', line 247

def wrap_and_indent(indented, starting_col, indent_wrapped_lines, full_width)
  first_line = true
  width = full_width - starting_col # the first line is essentially shorter
  indented.split("\n").map do |line|
    s = ""
    while line.length > width
      s << line[0...width]
      s << newline(indent_wrapped_lines)
      line = line[width..-1]
      if first_line
        width += starting_col - indent_wrapped_lines
        first_line = false
      end
    end
    s << line
    s
  end.join("\n")
end