module RBeautify
class BlockMatcher
attr_reader :language, :name, :starts, :ends, :options
def initialize(language, name, starts, ends, options = {})
@language = language
@name = name
@starts = starts
@ends = ends.nil? ? starts : ends
@options = options
end
class << self
def parse(language, original_block, line_number, string, current_offset)
block_end = original_block && original_block.parse_block_end(string, current_offset)
if (block_start = first_block_start(language,
original_block,
line_number,
string,
current_offset,
block_end.nil? ? nil : block_end.offset))
block_end = nil
end
if block_end
# Check whether the section of the line which is a block end is also a block start
if block_end.end_can_also_be_start? &&
(block_start_candidate = first_block_start(language, block_end.block_start.parent, line_number, string, current_offset)) &&
block_start_candidate.offset == block_end.offset
block_start = block_start_candidate
end
end
if block_start
if debug
puts "MATCH: '#{string.slice(0, string.length - block_start.match.length - block_start.after_match.length)}#{block_start.match}#{block_start.after_match}'"
end
parse(language, block_start, line_number, block_start.after_match, block_start.end_offset)
elsif block_end
if debug
puts "MATCH: '#{string.slice(0, string.length - block_end.match.length - block_end.after_match.length)}#{block_end.match}#{block_end.after_match}'"
end
parse(language, block_end.block_start.parent, line_number, block_end.after_match, block_end.end_offset)
else
original_block
end
end
def debug=(value)
@debug = value
end
def debug
@debug
end
private
def first_block_start(language, parent_block, line_number, string, offset, maximum_offset = nil)
first_block_start = nil
language.matchers.each do |matcher|
if matcher.can_nest?(parent_block)
if (block_start_candidate = matcher.parse_block_start(string, parent_block, offset, line_number)) &&
(maximum_offset.nil? || maximum_offset > block_start_candidate.offset)
first_block_start = block_start_candidate
maximum_offset = first_block_start.offset
end
end
end
first_block_start
end
end
def parse_block_start(string, parent_block, offset, line_number)
if !string.empty? && (match = starts.match(string))
RBeautify::BlockStart.new(self, parent_block, line_number, offset + match.begin(0), match[0], match.post_match)
end
end
def indent_end_line?(block)
evaluate_option_for_block(:indent_end_line, block)
end
def indent_size(block)
evaluate_option_for_block(:indent_size, block) || language.indent_size
end
# Look for blocks within the content of this one
def parse_content?
options[:parse_content] != false
end
# Indent the content of this block
def format_content?
options[:format_content] != false
end
def can_nest?(parent_block)
return false unless parent_block.nil? || parent_block.parse_content?
if options[:nest_only]
parent_block && options[:nest_only].include?(parent_block.name)
else
parent_block.nil? ||
options[:nest_except].nil? || !options[:nest_except].include?(parent_block.name)
end
end
def ends?
ends != false
end
def end_is_implicit?
options[:end] == :implicit
end
def end_can_also_be_start?
if ends == starts
options[:end_can_also_be_start] == true
else
options[:end_can_also_be_start] != false
end
end
def negate_ends_match?
options[:negate_ends_match]
end
# True if blocks can contain the escape character \ which needs to be
# checked for on end match
def escape_character?
options[:escape_character] == true
end
def inspect
name
end
private
def evaluate_option_for_block(key, block)
if options[key] && options[key].respond_to?(:call)
options[key].call(block)
else
options[key]
end
end
end
end