1#!/usr/bin/ruby
2# encoding: utf-8
3
4require 'spec'
5require 'antlr3/template'
6require 'antlr3/util'
7
8include ANTLR3
9MethodDescription = Struct.new( :name, :body, :arguments )
10TEMPLATE_NAMES = %w( method class_definition attribute )
11SAMPLE_GROUP_FILE = File.join(
12  File.dirname( __FILE__ ), 'sample-input', 'template-group'
13)
14
15describe Template::Context do
16  example "creating an empty context" do
17    context = Template::Context.new
18    context.instance_variables.should be_empty
19  end
20  
21  example "creating a context with a variable map" do
22    context = Template::Context.new(
23      :a => 1, :b => 2
24    )
25    
26    vars = context.instance_variables.map { | i | i.to_s }
27    vars.should include( '@a' )
28    vars.should include( '@b' )
29    
30    context.instance_variable_get( '@a' ).should == 1
31    context.instance_variable_get( '@b' ).should == 2
32  end
33  
34  example "fetching variable values from []" do
35    context = Template::Context.new(
36      :a => 1, :b => 2
37    )
38    
39    context[ :a ].should == 1
40    context[ 'a' ].should == 1
41    context[ :b ].should == 2
42    context[ 'b' ].should == 2
43  end
44  
45  example "defining variables with []=" do
46    context = Template::Context.new( :a => 3 )
47    context[ :a ] = 1
48    context[ 'b' ] = 2
49    
50    context.instance_variable_get( '@a' ).should == 1
51    context.instance_variable_get( '@b' ).should == 2
52  end
53  
54  example "using method missing to assign values" do
55    context = Template::Context.new( :a => 3 )
56    context.a = 1
57    context.b = 2
58    
59    context.instance_variable_get( '@a' ).should == 1
60    context.instance_variable_get( '@b' ).should == 2
61  end
62  
63  example "using method missing to get variable values" do
64    context = Template::Context.new( :a => 1, :b => 2)
65    
66    context.a.should == 1
67    context.b.should == 2
68  end
69  
70end
71
72
73shared_examples_for "template groups" do
74  include ANTLR3::Util
75  
76  example "template definitions" do
77    templates = @group.templates
78    templates.should_not be_empty
79    templates.should equal @group::TEMPLATES
80    
81    names = templates.keys
82    
83    names.should have(3).things
84    for template_name in TEMPLATE_NAMES
85      names.should include template_name
86      template_class = templates[ template_name ]
87      template_class.should be_a_kind_of Class
88      template_class.superclass.should equal Template::Context
89      template_class.should be < @group # it should include the group module
90    end
91  end
92  
93  example "template_defined?( name ) should verify whether a template is defined in a group" do
94    for name in TEMPLATE_NAMES
95      @group.template_defined?( name ).should be_true
96      @group.template_defined?( name.to_s ).should be_true
97    end
98    
99    @group.template_defined?( :something_else ).should be_false
100  end
101  
102  example "template method definitions" do
103    for name in TEMPLATE_NAMES
104      @group.should respond_to( name )
105      @group.should respond_to( "#{ name }!" )
106      if RUBY_VERSION =~ /^1\.9/
107        @group.private_instance_methods.should include name.to_sym
108        @group.private_instance_methods.should include :"#{ name }!"
109      else
110        @group.private_instance_methods.should include name.to_s
111        @group.private_instance_methods.should include "#{ name }!"
112      end
113    end
114  end
115  
116  example "template method operation" do
117    value = @group.class_definition
118    value.should be_a_kind_of Template::Context
119    
120    value = @group.class_definition!
121    value.should be_a_kind_of String
122    
123    value = @group.attribute( :name => 'a' )
124    value.should be_a_kind_of Template::Context
125  end
126end
127
128describe Template::Group, "dynamic template definition" do
129  include ANTLR3::Util
130  
131  before :each do
132    @group = Template::Group.new do
133      extend ANTLR3::Util
134      define_template( :class_definition, tidy( <<-'END'.chomp ) )
135      | class <%= @name %><% if @superclass %> < <%= @superclass %><% end %>
136      | % if @attributes
137      | 
138      | % for attr, access in @attributes 
139      | <%= attribute( :name => attr, :access => ( access || 'rw' ) ).to_s.chomp %>
140      | % end
141      | % end
142      | % if @methods
143      | % for method in ( @methods || [] )
144      | <%= method( method ) %>
145      | % end
146      | % end
147      | end
148      END
149      
150      define_template( :attribute, tidy( <<-'END'.chomp ) )
151      | % case @access.to_s.downcase
152      | % when 'r'
153      |   attr_reader :<%= @name %>
154      | % when 'w'
155      |   attr_writer :<%= @name %>
156      | % else
157      |   attr_accessor :<%= @name %>
158      | % end
159      END
160      
161      define_template( :method, tidy( <<-'END'.chomp ) )
162      |   
163      |   def <%= @name %><% if @arguments and not @arguments.empty? %>( <%= @arguments.join( ', ' ) %> )<% end %>
164      | <%= @body.gsub( /^/, '    ' ) %>
165      |   end
166      END
167    end
168  end
169  
170  it_should_behave_like "template groups"
171  
172  example "template object string rendering" do
173    attributes = [
174      %w( family ),
175      %w( name r )
176    ]
177          
178    methods = [
179      MethodDescription.new( 'eat', %q[puts( "ate %s %s" % [ number, @name ] )], %w( number ) ),
180      MethodDescription.new( :to_s, '@name.to_s.dup' )
181    ]
182    
183    vegetable = @group.class_definition(
184      :name => 'Vegetable',
185      :superclass => 'Food',
186      :attributes => attributes,
187      :methods => methods
188    )
189    
190    vegetable.to_s.should == tidy( <<-END.chomp )
191    | class Vegetable < Food
192    |
193    |   attr_accessor :family
194    |   attr_reader :name
195    |   
196    |   def eat( number )
197    |     puts( "ate %s %s" % [ number, @name ] )
198    |   end
199    |   
200    |   def to_s
201    |     @name.to_s.dup
202    |   end
203    | end
204    END
205  end
206end
207
208describe Template::Group, "loading a template definition file" do
209  
210  before :each do
211    @group = Template::Group.load( SAMPLE_GROUP_FILE )
212  end
213  
214  it_should_behave_like "template groups"
215  
216  example "template object string rendering" do
217    attributes = [
218      %w( family ),
219      %w( name r )
220    ]
221          
222    methods = [
223      MethodDescription.new( 'eat', %q[puts( "ate %s %s" % [ number, @name ] )], %w( number ) ),
224      MethodDescription.new( :to_s, '@name.to_s.dup' )
225    ]
226    
227    vegetable = @group.class_definition(
228      :name => 'Vegetable',
229      :superclass => 'Food',
230      :attributes => attributes,
231      :methods => methods
232    )
233    
234    vegetable.to_s.should == tidy( <<-END.chomp )
235    | class Vegetable < Food
236    |
237    |   attr_accessor :family
238    |   attr_reader :name
239    |   
240    |   def eat( number )
241    |     puts( "ate %s %s" % [ number, @name ] )
242    |   end
243    |   
244    |   def to_s
245    |     @name.to_s.dup
246    |   end
247    | end
248    END
249  end
250end
251