class Bade::RubyGenerator

Constants

BASE_INDENT_NAME
BUFF_NAME
CURRENT_INDENT_NAME
DEFAULT_BLOCK_NAME
MIXINS_NAME
NEW_LINE_NAME

Public Class Methods

document_to_lambda_string(document, optimize: false) click to toggle source

@param [Document] document

@return [String]

# File lib/bade/generator.rb, line 21
def self.document_to_lambda_string(document, optimize: false)
  generator = new
  generator.generate_lambda_string(document, optimize: optimize)
end

Public Instance Methods

block_definition(block_node) click to toggle source

Generates code for definition of block

@param [MixinCallBlockNode] block_node

@return [nil]

# File lib/bade/generator.rb, line 311
def block_definition(block_node)
  buff_code "__blocks['#{block_node.name}'] = __create_block('#{block_node.name}') do"

  code_indent do
    buff_code '__buffs_push()'

    visit_node_children(block_node)

    buff_code '__buffs_pop()'
  end

  buff_code 'end'
end
block_name_declaration(block_name) click to toggle source

Generates code for block variables declaration in mixin definition

@param [String] block_name

@return [nil]

# File lib/bade/generator.rb, line 331
def block_name_declaration(block_name)
  buff_code "#{block_name} = __blocks.delete('#{block_name}') { __create_block('#{block_name}') }"
end
blocks_name_declaration(mixin_node) click to toggle source

@param [MixinDeclarationNode] mixin_node

@return [nil]

# File lib/bade/generator.rb, line 339
def blocks_name_declaration(mixin_node)
  block_name_declaration(DEFAULT_BLOCK_NAME)

  mixin_node.params.select { |n| n.type == :mixin_block_param }.each do |param|
    block_name_declaration(param.value)
  end
end
buff_code(text) click to toggle source
# File lib/bade/generator.rb, line 77
def buff_code(text)
  @buff << '  ' * @code_indent + text
end
buff_print_static_text(text) click to toggle source

@param [String] text

# File lib/bade/generator.rb, line 68
def buff_print_static_text(text)
  buff_print_value("'#{text.gsub("'", "\\\\'")}'") unless text.empty?
end
buff_print_text(text, indent: false, new_line: false) click to toggle source

@param [String] text

# File lib/bade/generator.rb, line 62
def buff_print_text(text, indent: false, new_line: false) # rubocop:disable Lint/UnusedMethodArgument
  buff_print_value("%Q{#{text}}") unless text.empty?
end
buff_print_value(value) click to toggle source
# File lib/bade/generator.rb, line 72
def buff_print_value(value)
  # buff_code %Q{#{BUFF_NAME} << #{CURRENT_INDENT_NAME}} if indent
  buff_code("#{BUFF_NAME} << #{value}")
end
code_indent(plus = 1) { || ... } click to toggle source

Method for indenting generated code, indent is raised only in passed block

@return [nil]

# File lib/bade/generator.rb, line 256
def code_indent(plus = 1)
  @code_indent += plus
  yield
  @code_indent -= plus
end
escape_double_quotes!(str) click to toggle source

@param [String] str

@return [Void]

# File lib/bade/generator.rb, line 367
def escape_double_quotes!(str)
  str.gsub!(/"/, '\"')
end
formatted_attributes(tag_node) click to toggle source

@param [TagNode] tag_node

@return [String] formatted attributes

# File lib/bade/generator.rb, line 236
def formatted_attributes(tag_node)
  all_attributes = Hash.new { |hash, key| hash[key] = [] }
  xml_attributes = []

  tag_node.attributes.each do |attr|
    xml_attributes << attr.name unless all_attributes.include?(attr.name)

    all_attributes[attr.name] << attr.value
  end

  xml_attributes.map do |attr_name|
    joined = all_attributes[attr_name].join('), (')
    "\#{__tag_render_attribute('#{attr_name}', (#{joined}))}"
  end.join
end
formatted_mixin_params(mixin_node) click to toggle source

@param [MixinCommonNode] mixin_node

@return [String] formatted params

# File lib/bade/generator.rb, line 266
def formatted_mixin_params(mixin_node)
  params = mixin_node.params
  result = []

  if mixin_node.type == :mixin_call
    blocks = mixin_node.blocks

    other_children = (mixin_node.children - mixin_node.blocks - mixin_node.params)
    if other_children.count { |n| n.type != :newline } > 0
      def_block_node = AST::NodeRegistrator.create(:mixin_block, mixin_node.lineno)
      def_block_node.name = DEFAULT_BLOCK_NAME
      def_block_node.children = other_children

      blocks << def_block_node
    end

    if !blocks.empty?
      buff_code '__blocks = {}'

      blocks.each do |block|
        block_definition(block)
      end

      result << '__blocks.dup'
    else
      result << '{}'
    end
  elsif mixin_node.type == :mixin_decl
    result << '__blocks'
  end


  # normal params
  result += params.select { |n| n.type == :mixin_param }.map(&:value)
  result += params.select { |n| n.type == :mixin_key_param }.map { |param| "#{param.name}: #{param.value}" }

  result.join(', ')
end
generate_lambda_string(document, optimize: false) click to toggle source

@param [Bade::AST::Document] document

@return [String] string to parse with Ruby

# File lib/bade/generator.rb, line 30
def generate_lambda_string(document, optimize: false)
  @document = document
  @buff = []
  @indent = 0
  @code_indent = 0
  @optimize = optimize

  buff_code '# frozen_string_literal: true' # so it can be faster on Ruby 2.3+
  buff_code ''
  buff_code "lambda do |#{NEW_LINE_NAME}: \"\\n\", #{BASE_INDENT_NAME}: '  '|"

  code_indent do
    buff_code "self.#{NEW_LINE_NAME} = #{NEW_LINE_NAME}"
    buff_code "self.#{BASE_INDENT_NAME} = #{BASE_INDENT_NAME}"

    visit_document(document)

    buff_code "output = #{BUFF_NAME}.join"
    buff_code 'self.__reset'
    buff_code 'output'
  end

  buff_code 'end'


  @document = nil

  @buff.join("\n")
end
visit_block_decl(current_node) click to toggle source

@param [MixinDeclarationNode] current_node

@return [nil]

# File lib/bade/generator.rb, line 351
def visit_block_decl(current_node)
  params = formatted_mixin_params(current_node)
  buff_code "#{MIXINS_NAME}['#{current_node.name}'] = __create_mixin('#{current_node.name}', &lambda { |#{params}|"

  code_indent do
    blocks_name_declaration(current_node)
    visit_nodes(current_node.children - current_node.params)
  end

  buff_code '})'
end
visit_document(document) click to toggle source

@param document [Bade::Document]

# File lib/bade/generator.rb, line 83
def visit_document(document)
  document.sub_documents.each do |sub_document|
    visit_document(sub_document)
  end

  buff_code("# ----- start file #{document.file_path}") unless document.file_path.nil?

  new_root = if @optimize
               Optimizer.new(document.root).optimize
             else
               document.root
             end

  visit_node(new_root)

  buff_code("# ----- end file #{document.file_path}") unless document.file_path.nil?
end
visit_node(current_node) click to toggle source

@param current_node [Node]

# File lib/bade/generator.rb, line 117
def visit_node(current_node)
  case current_node.type
  when :root
    visit_node_children(current_node)

  when :static_text
    buff_print_static_text(current_node.value)

  when :tag
    visit_tag(current_node)

  when :code
    buff_code(current_node.value)

  when :html_comment
    buff_print_text '<!-- '
    visit_node_children(current_node)
    buff_print_text ' -->'

  when :comment
    comment_text = '#' + current_node.children.map(&:value).join("\n#")
    buff_code(comment_text)

  when :doctype
    buff_print_text current_node.xml_output

  when :mixin_decl
    visit_block_decl(current_node)

  when :mixin_call
    params = formatted_mixin_params(current_node)
    buff_code "#{MIXINS_NAME}['#{current_node.name}'].call!(#{params})"

  when :output
    data = current_node.value
    output_code = if current_node.escaped
                    "\#{__html_escaped(#{data})}"
                  else
                    "\#{#{data}}"
                  end
    buff_print_text output_code

  when :newline
    # no-op
  when :import
    base_path = File.expand_path(current_node.value, File.dirname(@document.file_path))
    load_path = if base_path.end_with?('.rb') && File.exist?(base_path)
                  base_path
                elsif File.exist?("#{base_path}.rb")
                  "#{base_path}.rb"
                end

    buff_code "load('#{load_path}')" unless load_path.nil?

  else
    raise "Unknown type #{current_node.type}"
  end
end
visit_node_children(current_node) click to toggle source

@param current_node [Node]

# File lib/bade/generator.rb, line 103
def visit_node_children(current_node)
  visit_nodes(current_node.children)
end
visit_nodes(nodes) click to toggle source

@param nodes [Array<Node>]

# File lib/bade/generator.rb, line 109
def visit_nodes(nodes)
  nodes.each do |node|
    visit_node(node)
  end
end
visit_tag(current_node) click to toggle source

@param [TagNode] current_node

@return [nil]

# File lib/bade/generator.rb, line 180
def visit_tag(current_node)
  attributes = formatted_attributes(current_node)
  children_wo_attributes = (current_node.children - current_node.attributes)

  text = "<#{current_node.name}"

  text += attributes.to_s unless attributes.empty?

  other_than_new_lines = children_wo_attributes.any? { |n| n.type != :newline }

  text += if other_than_new_lines
            '>'
          else
            '/>'
          end

  conditional_nodes = current_node.children.select { |n| n.type == :output && n.conditional }

  unless conditional_nodes.empty?
    buff_code "if (#{conditional_nodes.map(&:value).join(') && (')})"

    @code_indent += 1
  end

  buff_print_text(text, new_line: true, indent: true)

  if other_than_new_lines
    last_node = children_wo_attributes.last
    is_last_newline = !last_node.nil? && last_node.type == :newline
    nodes = if is_last_newline
              children_wo_attributes[0...-1]
            else
              children_wo_attributes
            end

    code_indent do
      visit_nodes(nodes)
    end

    buff_print_text("</#{current_node.name}>", new_line: true, indent: true)

    # print new line after the tag
    visit_node(last_node) if is_last_newline
  end

  unless conditional_nodes.empty? # rubocop:disable Style/GuardClause
    @code_indent -= 1

    buff_code 'end'
  end
end