From 337d126264058abc415205a1c71c7c803dd4d7e8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 2 Jul 2015 23:15:10 +0200 Subject: [PATCH] Added Ruby::Generator class This class will be used to serialize a Ruby AST back to valid Ruby source code (as a String). --- lib/oga.rb | 1 + lib/oga/ruby/generator.rb | 172 ++++++++++++++++++++++++++++++++ spec/oga/ruby/generator_spec.rb | 137 +++++++++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 lib/oga/ruby/generator.rb create mode 100644 spec/oga/ruby/generator_spec.rb diff --git a/lib/oga.rb b/lib/oga.rb index 2be82d3..7dbfa8f 100644 --- a/lib/oga.rb +++ b/lib/oga.rb @@ -50,6 +50,7 @@ require 'oga/html/sax_parser' require 'oga/html/entities' require 'oga/ruby/node' +require 'oga/ruby/generator' require 'oga/xpath/lexer' require 'oga/xpath/parser' diff --git a/lib/oga/ruby/generator.rb b/lib/oga/ruby/generator.rb new file mode 100644 index 0000000..f2563db --- /dev/null +++ b/lib/oga/ruby/generator.rb @@ -0,0 +1,172 @@ +module Oga + module Ruby + ## + # Class for converting a Ruby AST to a String. + # + # This class takes a {Oga::Ruby::Node} instance and converts it (and its + # child nodes) to a String that in turn can be passed to `eval` and the + # likes. + # + class Generator + ## + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def process(ast) + send(:"on_#{ast.type}", ast) + end + + ## + # Processes a "begin" node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_begin(ast) + ast.to_a.map { |child| process(child) }.join("\n\n") + end + + ## + # Processes an assignment node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_assign(ast) + var, val = *ast + + var_str = process(var) + val_str = process(val) + + "#{var_str} = #{val_str}" + end + + ## + # Processes an equality node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_eq(ast) + left, right = *ast + + left_str = process(left) + right_str = process(right) + + "#{left_str} == #{right_str}" + end + + ## + # Processes a boolean "and" node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_and(ast) + left, right = *ast + + left_str = process(left) + right_str = process(right) + + "#{left_str} && #{right_str}" + end + + ## + # Processes a boolean "or" node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_or(ast) + left, right = *ast + + left_str = process(left) + right_str = process(right) + + "(#{left_str} || #{right_str})" + end + + ## + # Processes an if statement node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_if(ast) + cond, body = *ast + + cond_str = process(cond) + body_str = process(body) + + <<-EOF +if #{cond_str} + #{body_str} +end + EOF + end + + ## + # Processes a method call node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_send(ast) + receiver, name, *args = *ast + + call = name.dup + + unless args.empty? + arg_strs = args.map { |arg| process(arg) } + call = "#{call}(#{arg_strs.join(', ')})" + end + + if receiver + call = "#{process(receiver)}.#{call}" + end + + call + end + + ## + # Processes a block node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_block(ast) + receiver, args, body = *ast + + receiver_str = process(receiver) + body_str = body ? process(body) : nil + arg_strs = args.map { |arg| process(arg) } + + <<-EOF +#{receiver_str} do |#{arg_strs.join(', ')}| + #{body_str} +end + EOF + end + + ## + # Processes a string node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_string(ast) + ast.to_a[0].inspect + end + + ## + # Processes a literal node. + # + # @param [Oga::Ruby::Node] ast + # @return [String] + # + def on_lit(ast) + ast.to_a[0] + end + end # Generator + end # Ruby +end # Oga diff --git a/spec/oga/ruby/generator_spec.rb b/spec/oga/ruby/generator_spec.rb new file mode 100644 index 0000000..973b100 --- /dev/null +++ b/spec/oga/ruby/generator_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' + +describe Oga::Ruby::Generator do + before do + @generator = described_class.new + end + + describe '#on_begin' do + it 'returns a String' do + node1 = Oga::Ruby::Node.new(:lit, %w{10}) + node2 = Oga::Ruby::Node.new(:lit, %w{20}) + joined = node1.followed_by(node2) + + @generator.on_begin(joined).should == "10\n\n20" + end + end + + describe '#on_assign' do + it 'returns a String' do + var = Oga::Ruby::Node.new(:lit, %w{number}) + val = Oga::Ruby::Node.new(:lit, %w{10}) + assign = var.assign(val) + + @generator.on_assign(assign).should == 'number = 10' + end + end + + describe '#on_eq' do + it 'returns a String' do + var = Oga::Ruby::Node.new(:lit, %w{number}) + val = Oga::Ruby::Node.new(:lit, %w{10}) + eq = var.eq(val) + + @generator.on_eq(eq).should == 'number == 10' + end + end + + describe '#on_and' do + it 'returns a String' do + left = Oga::Ruby::Node.new(:lit, %w{foo}) + right = Oga::Ruby::Node.new(:lit, %w{bar}) + condition = left.and(right) + + @generator.on_and(condition).should == 'foo && bar' + end + end + + describe '#on_or' do + it 'returns a String' do + left = Oga::Ruby::Node.new(:lit, %w{foo}) + right = Oga::Ruby::Node.new(:lit, %w{bar}) + condition = left.or(right) + + @generator.on_or(condition).should == '(foo || bar)' + end + end + + describe '#on_if' do + it 'returns a String' do + statement = Oga::Ruby::Node.new(:lit, %w{foo}).if_true do + Oga::Ruby::Node.new(:lit, %w{bar}) + end + + @generator.on_if(statement).should == <<-EOF +if foo + bar +end + EOF + end + end + + describe '#on_send' do + describe 'without arguments' do + it 'returns a String' do + node = Oga::Ruby::Node.new(:lit, %w{number}).foobar + + @generator.on_send(node).should == 'number.foobar' + end + end + + describe 'with arguments' do + it 'returns a String' do + arg = Oga::Ruby::Node.new(:lit, %w{10}) + node = Oga::Ruby::Node.new(:lit, %w{number}).foobar(arg) + + @generator.on_send(node).should == 'number.foobar(10)' + end + end + end + + describe '#on_block' do + describe 'without arguments' do + it 'returns a String' do + node = Oga::Ruby::Node.new(:lit, %w{number}).add_block do + Oga::Ruby::Node.new(:lit, %w{10}) + end + + @generator.on_block(node).should == <<-EOF +number do || + 10 +end + EOF + end + end + + describe 'with arguments' do + it 'returns a String' do + arg = Oga::Ruby::Node.new(:lit, %w{foo}) + node = Oga::Ruby::Node.new(:lit, %w{number}).add_block(arg) do + Oga::Ruby::Node.new(:lit, %w{10}) + end + + @generator.on_block(node).should == <<-EOF +number do |foo| + 10 +end + EOF + end + end + end + + describe '#on_string' do + it 'returns a String' do + node = Oga::Ruby::Node.new(:string, %w{foo}) + + @generator.on_string(node).should == '"foo"' + end + end + + describe '#on_lit' do + it 'returns a String' do + node = Oga::Ruby::Node.new(:lit, %w{foo}) + + @generator.on_lit(node).should == 'foo' + end + end +end