kosmas58-cucumber 0.3.102 → 0.3.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/History.txt +8 -2
  2. data/Manifest.txt +6 -1
  3. data/examples/java/README.textile +3 -3
  4. data/examples/self_test/features/sample.feature +1 -1
  5. data/examples/self_test/features/search_sample.feature +1 -1
  6. data/features/custom_formatter.feature +4 -4
  7. data/lib/cucumber/ast.rb +1 -0
  8. data/lib/cucumber/ast/table.rb +4 -4
  9. data/lib/cucumber/ast/visitor.rb +2 -106
  10. data/lib/cucumber/cli/configuration.rb +28 -28
  11. data/lib/cucumber/cli/language_help_formatter.rb +5 -7
  12. data/lib/cucumber/cli/main.rb +3 -3
  13. data/lib/cucumber/formatter/html.rb +203 -113
  14. data/lib/cucumber/formatter/junit.rb +29 -23
  15. data/lib/cucumber/formatter/pdf.rb +74 -69
  16. data/lib/cucumber/formatter/pretty.rb +93 -78
  17. data/lib/cucumber/formatter/profile.rb +2 -2
  18. data/lib/cucumber/formatter/progress.rb +16 -10
  19. data/lib/cucumber/formatter/rerun.rb +4 -5
  20. data/lib/cucumber/formatter/steps.rb +2 -3
  21. data/lib/cucumber/formatter/tag_cloud.rb +7 -6
  22. data/lib/cucumber/formatter/usage.rb +4 -7
  23. data/lib/cucumber/language_support/step_definition_methods.rb +2 -2
  24. data/lib/cucumber/rb_support/rb_language.rb +5 -0
  25. data/lib/cucumber/rb_support/rb_step_definition.rb +3 -3
  26. data/lib/cucumber/rb_support/regexp_argument_matcher.rb +21 -0
  27. data/lib/cucumber/step_argument.rb +9 -0
  28. data/lib/cucumber/step_match.rb +12 -14
  29. data/lib/cucumber/version.rb +1 -1
  30. data/spec/cucumber/ast/background_spec.rb +1 -2
  31. data/spec/cucumber/ast/scenario_outline_spec.rb +3 -2
  32. data/spec/cucumber/ast/scenario_spec.rb +1 -1
  33. data/spec/cucumber/formatter/html_spec.rb +221 -2
  34. data/spec/cucumber/formatter/progress_spec.rb +9 -4
  35. data/spec/cucumber/parser/feature_parser_spec.rb +31 -27
  36. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +18 -0
  37. data/spec/cucumber/step_match_spec.rb +40 -0
  38. metadata +6 -3
  39. data/lib/cucumber/rb_support/rb_group.rb +0 -32
@@ -12,13 +12,13 @@ module Cucumber
12
12
  @step_definition_durations = Hash.new { |h,step_definition| h[step_definition] = [] }
13
13
  end
14
14
 
15
- def visit_step(step)
15
+ def step(step)
16
16
  @step_duration = Time.now
17
17
  @step = step
18
18
  super
19
19
  end
20
20
 
21
- def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
21
+ def step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
22
22
  duration = Time.now - @step_duration
23
23
  super
24
24
 
@@ -3,33 +3,39 @@ require 'cucumber/formatter/console'
3
3
  module Cucumber
4
4
  module Formatter
5
5
  # The formatter used for <tt>--format progress</tt>
6
- class Progress < Ast::Visitor
6
+ class Progress
7
7
  include Console
8
+ attr_reader :step_mother
8
9
 
9
10
  def initialize(step_mother, io, options)
10
- super(step_mother)
11
- @io = io
12
- @options = options
11
+ @step_mother, @io, @options = step_mother, io, options
13
12
  end
14
13
 
15
- def visit_features(features)
16
- super
14
+ def after_features(features)
17
15
  @io.puts
18
16
  @io.puts
19
17
  print_summary(features)
20
18
  end
21
19
 
22
- def visit_feature_element(feature_element)
20
+ def before_feature_element(feature_element)
23
21
  record_tag_occurrences(feature_element, @options)
24
- super
25
22
  end
26
23
 
27
- def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
24
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
28
25
  progress(status)
29
26
  @status = status
30
27
  end
28
+
29
+ def before_outline_table(outline_table)
30
+ @outline_table = outline_table
31
+ end
32
+
33
+ def after_outline_table(outline_table)
34
+ @outline_table = nil
35
+ end
31
36
 
32
- def visit_table_cell_value(value, status)
37
+ def table_cell_value(value, status)
38
+ return unless @outline_table
33
39
  status ||= @status
34
40
  progress(status) unless table_header_cell?(status)
35
41
  end
@@ -10,16 +10,15 @@ module Cucumber
10
10
  # This formatter is used by AutoTest - it will use the output to decide what
11
11
  # to run the next time, simply passing the output string on the command line.
12
12
  #
13
- class Rerun < Ast::Visitor
13
+ class Rerun
14
14
  def initialize(step_mother, io, options)
15
- super(step_mother)
16
15
  @io = io
17
16
  @options = options
18
17
  @file_names = []
19
18
  @file_colon_lines = Hash.new{|h,k| h[k] = []}
20
19
  end
21
20
 
22
- def visit_features(features)
21
+ def features(features)
23
22
  super
24
23
  files = @file_names.uniq.map do |file|
25
24
  lines = @file_colon_lines[file]
@@ -28,7 +27,7 @@ module Cucumber
28
27
  @io.puts files.join(' ')
29
28
  end
30
29
 
31
- def visit_feature_element(feature_element)
30
+ def feature_element(feature_element)
32
31
  @rerun = false
33
32
  super
34
33
  if @rerun
@@ -38,7 +37,7 @@ module Cucumber
38
37
  end
39
38
  end
40
39
 
41
- def visit_step_name(keyword, step_match, status, source_indent, background)
40
+ def step_name(keyword, step_match, status, source_indent, background)
42
41
  @rerun = true if [:failed].index(status)
43
42
  end
44
43
  end
@@ -1,16 +1,15 @@
1
1
  module Cucumber
2
2
  module Formatter
3
3
  # The formatter used for <tt>--format steps</tt>
4
- class Steps < Ast::Visitor
4
+ class Steps
5
5
 
6
6
  def initialize(step_mother, io, options)
7
- super(step_mother)
8
7
  @io = io
9
8
  @options = options
10
9
  @step_definition_files = collect_steps(step_mother)
11
10
  end
12
11
 
13
- def visit_features(features)
12
+ def after_features(features)
14
13
  print_summary
15
14
  end
16
15
 
@@ -2,27 +2,28 @@ module Cucumber
2
2
  module Formatter
3
3
  # The formatter used for <tt>--format tag_cloud</tt>
4
4
  # Custom formatter that prints a tag cloud as a table.
5
- class TagCloud < Cucumber::Ast::Visitor
5
+ class TagCloud
6
6
  def initialize(step_mother, io, options)
7
- super(step_mother)
8
7
  @io = io
9
8
  @options = options
10
9
  @counts = Hash.new{|h,k| h[k] = 0}
11
10
  end
12
11
 
13
- def visit_features(features)
14
- super
12
+ def after_features(features)
15
13
  print_summary(features)
16
14
  end
17
15
 
18
- def visit_tag_name(tag_name)
16
+ def tag_name(tag_name)
19
17
  @counts[tag_name] += 1
20
18
  end
19
+
20
+ private
21
21
 
22
22
  def print_summary(features)
23
23
  matrix = @counts.to_a.sort{|paira, pairb| paira[0] <=> pairb[0]}.transpose
24
24
  table = Cucumber::Ast::Table.new(matrix)
25
- Cucumber::Formatter::Pretty.new(@step_mother, @io, {}).visit_multiline_arg(table)
25
+ formatter = Cucumber::Formatter::Pretty.new(@step_mother, @io, {})
26
+ Cucumber::Ast::TreeWalker.new(@step_mother, [formatter], {}).visit_multiline_arg(table)
26
27
  end
27
28
  end
28
29
  end
@@ -3,11 +3,10 @@ require 'cucumber/formatter/progress'
3
3
  module Cucumber
4
4
  module Formatter
5
5
  # The formatter used for <tt>--format usage</tt>
6
- class Usage < Ast::Visitor
6
+ class Usage
7
7
  include Console
8
8
 
9
9
  def initialize(step_mother, io, options)
10
- super(step_mother)
11
10
  @io = io
12
11
  @options = options
13
12
  @step_definitions = Hash.new { |h,step_definition| h[step_definition] = [] }
@@ -15,17 +14,15 @@ module Cucumber
15
14
  @locations = []
16
15
  end
17
16
 
18
- def visit_features(features)
19
- super
17
+ def after_features(features)
20
18
  print_summary(features)
21
19
  end
22
20
 
23
- def visit_step(step)
21
+ def before_step(step)
24
22
  @step = step
25
- super
26
23
  end
27
24
 
28
- def visit_step_name(keyword, step_match, status, source_indent, background)
25
+ def step_name(keyword, step_match, status, source_indent, background)
29
26
  if step_match.step_definition
30
27
  location = @step.file_colon_line
31
28
  return if @locations.index(location)
@@ -4,8 +4,8 @@ module Cucumber
4
4
  module LanguageSupport
5
5
  module StepDefinitionMethods
6
6
  def step_match(name_to_match, name_to_report)
7
- if(groups = groups(name_to_match))
8
- StepMatch.new(self, name_to_match, name_to_report, groups)
7
+ if(arguments = arguments_from(name_to_match))
8
+ StepMatch.new(self, name_to_match, name_to_report, arguments)
9
9
  else
10
10
  nil
11
11
  end
@@ -3,6 +3,7 @@ require 'cucumber/rb_support/rb_world'
3
3
  require 'cucumber/rb_support/rb_step_definition'
4
4
  require 'cucumber/rb_support/rb_hook'
5
5
  require 'cucumber/rb_support/rb_transform'
6
+ require 'cucumber/rb_support/regexp_argument_matcher'
6
7
 
7
8
  module Cucumber
8
9
  module RbSupport
@@ -55,6 +56,10 @@ module Cucumber
55
56
  end
56
57
  end
57
58
 
59
+ def arguments_from(regexp, step_name)
60
+ @regexp_argument_matcher.arguments_from(regexp, step_name)
61
+ end
62
+
58
63
  def snippet_text(step_keyword, step_name, multiline_arg_class = nil)
59
64
  escaped = Regexp.escape(step_name).gsub('\ ', ' ').gsub('/', '\/')
60
65
  escaped = escaped.gsub(PARAM_PATTERN, ESCAPED_PARAM_PATTERN)
@@ -1,7 +1,7 @@
1
1
  require 'cucumber/step_match'
2
2
  require 'cucumber/core_ext/string'
3
3
  require 'cucumber/core_ext/proc'
4
- require 'cucumber/rb_support/rb_group'
4
+ require 'cucumber/rb_support/regexp_argument_matcher'
5
5
 
6
6
  module Cucumber
7
7
  module RbSupport
@@ -41,8 +41,8 @@ module Cucumber
41
41
  regexp_source == step_definition.regexp_source
42
42
  end
43
43
 
44
- def groups(step_name)
45
- RbGroup.groups_from(@regexp, step_name)
44
+ def arguments_from(step_name)
45
+ RegexpArgumentMatcher.arguments_from(@regexp, step_name)
46
46
  end
47
47
 
48
48
  def invoke(args)
@@ -0,0 +1,21 @@
1
+ require 'cucumber/step_argument'
2
+
3
+ module Cucumber
4
+ module RbSupport
5
+ class RegexpArgumentMatcher
6
+ def self.arguments_from(regexp, step_name)
7
+ match = regexp.match(step_name)
8
+ if match
9
+ n = 0
10
+ match.captures.map do |val|
11
+ n += 1
12
+ start = match.offset(n)[0]
13
+ StepArgument.new(val, start)
14
+ end
15
+ else
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Cucumber
2
+ class StepArgument
3
+ attr_reader :val, :pos
4
+
5
+ def initialize(val, pos)
6
+ @val, @pos = val, pos
7
+ end
8
+ end
9
+ end
@@ -2,12 +2,12 @@ module Cucumber
2
2
  class StepMatch #:nodoc:
3
3
  attr_reader :step_definition
4
4
 
5
- def initialize(step_definition, step_name, formatted_step_name, groups)
6
- @step_definition, @step_name, @formatted_step_name, @groups = step_definition, step_name, formatted_step_name, groups
5
+ def initialize(step_definition, step_name, formatted_step_name, step_arguments)
6
+ @step_definition, @step_name, @formatted_step_name, @step_arguments = step_definition, step_name, formatted_step_name, step_arguments
7
7
  end
8
8
 
9
9
  def args
10
- @groups.map{|g| g.val}
10
+ @step_arguments.map{|g| g.val}
11
11
  end
12
12
 
13
13
  def name
@@ -36,7 +36,7 @@ module Cucumber
36
36
  # lambda { |param| "[#{param}]" }
37
37
  #
38
38
  def format_args(format = lambda{|a| a}, &proc)
39
- @formatted_step_name || gzub(@step_name, @groups, format, &proc)
39
+ @formatted_step_name || replace_arguments(@step_name, @step_arguments, format, &proc)
40
40
  end
41
41
 
42
42
  def file_colon_line
@@ -51,23 +51,21 @@ module Cucumber
51
51
  @step_definition.text_length
52
52
  end
53
53
 
54
- # +groups+ is an array of 2-element arrays, where
55
- # the 1st element is the value of a regexp match group,
56
- # and the 2nd element is its start index.
57
- def gzub(string, groups, format=nil, &proc)
54
+ def replace_arguments(string, step_arguments, format, &proc)
58
55
  s = string.dup
59
56
  offset = 0
60
- groups.each do |group|
57
+ step_arguments.each do |step_argument|
58
+ next if step_argument.pos.nil?
61
59
  replacement = if block_given?
62
- proc.call(group.val)
60
+ proc.call(step_argument.val)
63
61
  elsif Proc === format
64
- format.call(group.val)
62
+ format.call(step_argument.val)
65
63
  else
66
- format % group.val
64
+ format % step_argument.val
67
65
  end
68
66
 
69
- s[group.start + offset, group.val.length] = replacement
70
- offset += replacement.length - group.val.length
67
+ s[step_argument.pos + offset, step_argument.val.jlength] = replacement
68
+ offset += replacement.length - step_argument.val.jlength
71
69
  end
72
70
  s
73
71
  end
@@ -2,7 +2,7 @@ module Cucumber #:nodoc:
2
2
  class VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 3
5
- TINY = 102
5
+ TINY = 103
6
6
  PATCH = nil # Set to nil for official release
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PATCH].compact.join('.')
@@ -22,8 +22,7 @@ module Cucumber
22
22
 
23
23
  register
24
24
 
25
- @visitor = Visitor.new(@step_mother)
26
- @visitor.options = {}
25
+ @visitor = TreeWalker.new(@step_mother)
27
26
 
28
27
  @feature = mock('feature', :visit? => true).as_null_object
29
28
  end
@@ -57,14 +57,15 @@ module Cucumber
57
57
  end
58
58
 
59
59
  it "should replace all variables and call outline once for each table row" do
60
- visitor = Visitor.new(@step_mother)
60
+ visitor = TreeWalker.new(@step_mother)
61
61
  visitor.should_receive(:visit_table_row).exactly(3).times
62
62
  visitor.visit_feature_element(@scenario_outline)
63
63
  end
64
64
 
65
65
  it "should pretty print" do
66
66
  require 'cucumber/formatter/pretty'
67
- visitor = Formatter::Pretty.new(@step_mother, STDOUT, {:comment => true, :tag_names => {}})
67
+ formatter = Formatter::Pretty.new(@step_mother, STDOUT, {:comment => true, :tag_names => {}})
68
+ visitor = TreeWalker.new(@step_mother, [formatter])
68
69
  visitor.visit_feature_element(@scenario_outline)
69
70
  end
70
71
  end
@@ -17,7 +17,7 @@ module Cucumber
17
17
  @dsl.Given /y is (\d+)/ do |n|
18
18
  $y = n.to_i
19
19
  end
20
- @visitor = Visitor.new(@step_mother)
20
+ @visitor = TreeWalker.new(@step_mother)
21
21
  @visitor.options = {}
22
22
  end
23
23
 
@@ -1,16 +1,235 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
  require 'cucumber/formatter/html'
3
+ require 'nokogiri'
4
+ require 'cucumber/rb_support/rb_language'
3
5
 
4
6
  module Cucumber
5
7
  module Formatter
8
+ module SpecHelperDsl
9
+ attr_reader :feature_content, :step_defs
10
+
11
+ def define_feature(string)
12
+ @feature_content = string
13
+ end
14
+
15
+ def define_steps(&block)
16
+ @step_defs = block
17
+ end
18
+ end
19
+ module SpecHelper
20
+ def load_features(content)
21
+ feature_file = FeatureFile.new(nil, content)
22
+ features = Ast::Features.new
23
+ features.add_feature feature_file.parse(@step_mother, {})
24
+ features
25
+ end
26
+
27
+ def run(features)
28
+ # options = { :verbose => true }
29
+ options = {}
30
+ tree_walker = Cucumber::Ast::TreeWalker.new(@step_mother, [@formatter], options, STDOUT)
31
+ tree_walker.visit_features(features)
32
+ end
33
+
34
+ def define_steps
35
+ return unless step_defs = self.class.step_defs
36
+ rb = @step_mother.load_programming_language('rb')
37
+ dsl = Object.new
38
+ dsl.extend RbSupport::RbDsl
39
+ dsl.instance_exec &step_defs
40
+ @step_mother.register_step_definitions(rb.step_definitions)
41
+ end
42
+
43
+ Spec::Matchers.define :have_css_node do |css, regexp|
44
+ match do |doc|
45
+ nodes = doc.css(css)
46
+ nodes.detect{ |node| node.text =~ regexp }
47
+ end
48
+ end
49
+ end
50
+
6
51
  describe Html do
7
52
  before(:each) do
8
53
  @out = StringIO.new
9
- @html = Html.new(mock("step mother"), @out, {})
54
+ @formatter = Html.new(mock("step mother"), @out, {})
55
+ @step_mother = StepMother.new
10
56
  end
57
+
58
+ extend SpecHelperDsl
59
+ include SpecHelper
11
60
 
12
61
  it "should not raise an error when visiting a blank feature name" do
13
- lambda { @html.visit_feature_name("") }.should_not raise_error
62
+ lambda { @formatter.feature_name("") }.should_not raise_error
63
+ end
64
+
65
+ describe "given a single feature" do
66
+ before(:each) do
67
+ features = load_features(self.class.feature_content || raise("No feature content defined!"))
68
+ define_steps
69
+ run(features)
70
+ @doc = Nokogiri.HTML(@out.string)
71
+ end
72
+
73
+ describe "with a comment" do
74
+ define_feature <<-FEATURE
75
+ # Healthy
76
+ FEATURE
77
+
78
+ it { @out.string.should =~ /^\<!DOCTYPE/ }
79
+ it { @out.string.should =~ /\<\/html\>$/ }
80
+ it { @doc.should have_css_node('.feature .comment', /Healthy/) }
81
+ end
82
+
83
+ describe "with a tag" do
84
+ define_feature <<-FEATURE
85
+ @foo
86
+ FEATURE
87
+
88
+ it { @doc.should have_css_node('.feature .tag', /foo/) }
89
+ end
90
+
91
+ describe "with a narrative" do
92
+ define_feature <<-FEATURE
93
+ Feature: Bananas
94
+ In order to find my inner monkey
95
+ As a human
96
+ I must eat bananas
97
+ FEATURE
98
+
99
+ it { @doc.should have_css_node('.feature h2', /Bananas/) }
100
+ it { @doc.should have_css_node('.feature .narrative', /must eat bananas/) }
101
+ end
102
+
103
+ describe "with a background" do
104
+ define_feature <<-FEATURE
105
+ Feature: Bananas
106
+
107
+ Background:
108
+ Given there are bananas
109
+ FEATURE
110
+
111
+ it { @doc.should have_css_node('.feature .background', /there are bananas/) }
112
+ end
113
+
114
+ describe "with a scenario" do
115
+ define_feature <<-FEATURE
116
+ Scenario: Monkey eats banana
117
+ Given there are bananas
118
+ FEATURE
119
+
120
+ it { @doc.should have_css_node('.feature h3', /Monkey eats banana/) }
121
+ it { @doc.should have_css_node('.feature .scenario .step', /there are bananas/) }
122
+ end
123
+
124
+ describe "with a scenario outline" do
125
+ define_feature <<-FEATURE
126
+ Scenario Outline: Monkey eats a balanced diet
127
+ Given there are <Things>
128
+
129
+ Examples: Fruit
130
+ | Things |
131
+ | apples |
132
+ | bananas |
133
+ Examples: Vegetables
134
+ | Things |
135
+ | broccoli |
136
+ | carrots |
137
+ FEATURE
138
+
139
+ it { @doc.should have_css_node('.feature .scenario.outline h4', /Fruit/) }
140
+ it { @doc.should have_css_node('.feature .scenario.outline h4', /Vegetables/) }
141
+ it { @doc.css('.feature .scenario.outline h4').length.should == 2}
142
+ it { @doc.should have_css_node('.feature .scenario.outline table', //) }
143
+ it { @doc.should have_css_node('.feature .scenario.outline table td', /carrots/) }
144
+ end
145
+
146
+ describe "with a step with a py string" do
147
+ define_feature <<-FEATURE
148
+ Scenario: Monkey goes to town
149
+ Given there is a monkey called:
150
+ """
151
+ foo
152
+ """
153
+ FEATURE
154
+
155
+ it { @doc.should have_css_node('.feature .scenario .val', /foo/) }
156
+ end
157
+
158
+ describe "with a multiline step arg" do
159
+ define_feature <<-FEATURE
160
+ Scenario: Monkey goes to town
161
+ Given there are monkeys:
162
+ | name |
163
+ | foo |
164
+ | bar |
165
+ FEATURE
166
+
167
+ it { @doc.should have_css_node('.feature .scenario table td', /foo/) }
168
+ end
169
+
170
+ describe "with a table in the background and the scenario" do
171
+ define_feature <<-FEATURE
172
+ Background:
173
+ Given table:
174
+ | a | b |
175
+ | c | d |
176
+ Scenario:
177
+ Given another table:
178
+ | e | f |
179
+ | g | h |
180
+ FEATURE
181
+
182
+ it { @doc.css('td').length.should == 8 }
183
+ end
184
+
185
+ describe "with a py string in the background and the scenario" do
186
+ define_feature <<-FEATURE
187
+ Background:
188
+ Given stuff:
189
+ """
190
+ foo
191
+ """
192
+ Scenario:
193
+ Given more stuff:
194
+ """
195
+ bar
196
+ """
197
+ FEATURE
198
+
199
+ it { @doc.css('.feature .background pre.val').length.should == 1 }
200
+ it { @doc.css('.feature .scenario pre.val').length.should == 1 }
201
+ end
202
+
203
+ describe "with a step that fails in the scenario" do
204
+ define_steps do
205
+ Given(/boo/) { raise 'eek' }
206
+ end
207
+
208
+ define_feature(<<-FEATURE)
209
+ Scenario: Monkey gets a fright
210
+ Given boo
211
+ FEATURE
212
+
213
+ it { @doc.should have_css_node('.feature .scenario .step.failed', /eek/) }
214
+ end
215
+
216
+ describe "with a step that fails in the backgound" do
217
+ define_steps do
218
+ Given(/boo/) { raise 'eek' }
219
+ end
220
+
221
+ define_feature(<<-FEATURE)
222
+ Background:
223
+ Given boo
224
+ Scenario:
225
+ Given yay
226
+ FEATURE
227
+
228
+ it { @doc.should have_css_node('.feature .background .step.failed', /eek/) }
229
+ it { @doc.should_not have_css_node('.feature .scenario .step.failed', //) }
230
+ it { @doc.should have_css_node('.feature .scenario .step.undefined', /yay/) }
231
+ end
232
+
14
233
  end
15
234
  end
16
235
  end