yard-gherkin-turnip 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +3 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +48 -0
  9. data/History.txt +293 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +182 -0
  12. data/Rakefile +24 -0
  13. data/example/README.md +8 -0
  14. data/example/child_feature/README.md +21 -0
  15. data/example/child_feature/child.feature +11 -0
  16. data/example/child_feature/grandchild_feature/grandchild.feature +12 -0
  17. data/example/empty.feature +2 -0
  18. data/example/placeholder.feature +34 -0
  19. data/example/scenario.feature +63 -0
  20. data/example/scenario_outline.feature +100 -0
  21. data/example/scenario_outline_multi.feature +15 -0
  22. data/example/step_definitions/customer.step.rb +16 -0
  23. data/example/step_definitions/duck.step.rb +6 -0
  24. data/example/step_definitions/placeholder.step.rb +70 -0
  25. data/example/step_definitions/text.step.rb +11 -0
  26. data/example/tags.feature +18 -0
  27. data/lib/cucumber/city_builder.rb +412 -0
  28. data/lib/docserver/default/fulldoc/html/js/cucumber.js +88 -0
  29. data/lib/docserver/default/layout/html/headers.erb +13 -0
  30. data/lib/docserver/doc_server/full_list/html/full_list.erb +39 -0
  31. data/lib/docserver/doc_server/full_list/html/setup.rb +18 -0
  32. data/lib/templates/default/feature/html/feature.erb +39 -0
  33. data/lib/templates/default/feature/html/no_steps_defined.erb +1 -0
  34. data/lib/templates/default/feature/html/outline.erb +59 -0
  35. data/lib/templates/default/feature/html/pystring.erb +3 -0
  36. data/lib/templates/default/feature/html/scenario.erb +55 -0
  37. data/lib/templates/default/feature/html/setup.rb +54 -0
  38. data/lib/templates/default/feature/html/steps.erb +39 -0
  39. data/lib/templates/default/feature/html/table.erb +20 -0
  40. data/lib/templates/default/featuredirectory/html/alpha_table.erb +30 -0
  41. data/lib/templates/default/featuredirectory/html/directory.erb +32 -0
  42. data/lib/templates/default/featuredirectory/html/setup.rb +41 -0
  43. data/lib/templates/default/featuretags/html/namespace.erb +159 -0
  44. data/lib/templates/default/featuretags/html/setup.rb +34 -0
  45. data/lib/templates/default/fulldoc/html/css/cucumber.css +226 -0
  46. data/lib/templates/default/fulldoc/html/directories.erb +27 -0
  47. data/lib/templates/default/fulldoc/html/full_list_featuredirectories.erb +11 -0
  48. data/lib/templates/default/fulldoc/html/full_list_features.erb +37 -0
  49. data/lib/templates/default/fulldoc/html/full_list_stepdefinitions.erb +20 -0
  50. data/lib/templates/default/fulldoc/html/full_list_steps.erb +20 -0
  51. data/lib/templates/default/fulldoc/html/full_list_tags.erb +16 -0
  52. data/lib/templates/default/fulldoc/html/js/cucumber.js +331 -0
  53. data/lib/templates/default/fulldoc/html/setup.rb +208 -0
  54. data/lib/templates/default/layout/html/setup.rb +131 -0
  55. data/lib/templates/default/requirements/html/alpha_table.erb +26 -0
  56. data/lib/templates/default/requirements/html/requirements.erb +50 -0
  57. data/lib/templates/default/requirements/html/setup.rb +51 -0
  58. data/lib/templates/default/steptransformers/html/header.erb +12 -0
  59. data/lib/templates/default/steptransformers/html/index.erb +10 -0
  60. data/lib/templates/default/steptransformers/html/placeholders.erb +79 -0
  61. data/lib/templates/default/steptransformers/html/setup.rb +107 -0
  62. data/lib/templates/default/steptransformers/html/step_definitions.erb +79 -0
  63. data/lib/templates/default/steptransformers/html/undefined_steps.erb +26 -0
  64. data/lib/templates/default/tag/html/alpha_table.erb +33 -0
  65. data/lib/templates/default/tag/html/setup.rb +27 -0
  66. data/lib/templates/default/tag/html/tag.erb +35 -0
  67. data/lib/yard-gherkin-turnip.rb +42 -0
  68. data/lib/yard-gherkin-turnip/version.rb +3 -0
  69. data/lib/yard/code_objects/cucumber/base.rb +24 -0
  70. data/lib/yard/code_objects/cucumber/feature.rb +16 -0
  71. data/lib/yard/code_objects/cucumber/namespace_object.rb +55 -0
  72. data/lib/yard/code_objects/cucumber/scenario.rb +22 -0
  73. data/lib/yard/code_objects/cucumber/scenario_outline.rb +68 -0
  74. data/lib/yard/code_objects/cucumber/step.rb +46 -0
  75. data/lib/yard/code_objects/cucumber/tag.rb +31 -0
  76. data/lib/yard/code_objects/placeholder.rb +45 -0
  77. data/lib/yard/code_objects/step_definition.rb +46 -0
  78. data/lib/yard/code_objects/step_transformer.rb +32 -0
  79. data/lib/yard/handlers/cucumber/base.rb +21 -0
  80. data/lib/yard/handlers/cucumber/feature_handler.rb +96 -0
  81. data/lib/yard/handlers/placeholder_handler.rb +28 -0
  82. data/lib/yard/handlers/placeholder_match_handler.rb +17 -0
  83. data/lib/yard/handlers/step_definition_handler.rb +55 -0
  84. data/lib/yard/parser/cucumber/feature.rb +72 -0
  85. data/lib/yard/server/adapter.rb +43 -0
  86. data/lib/yard/server/commands/list_command.rb +31 -0
  87. data/lib/yard/server/router.rb +31 -0
  88. data/lib/yard/templates/helpers/base_helper.rb +26 -0
  89. data/yard-gherkin-turnip.gemspec +67 -0
  90. metadata +216 -0
@@ -0,0 +1,88 @@
1
+ function cucumberSearchFrameLinks() {
2
+ $('#feature_list_link').click(function() {
3
+ toggleSearchFrame(this, '/' + library + '/features');
4
+ });
5
+ $('#step_transformers_list_link').click(function() {
6
+ toggleSearchFrame(this, '/' + library + '/step_transformers');
7
+ });
8
+ $('#tag_list_link').click(function() {
9
+ toggleSearchFrame(this, '/' + library + '/tags');
10
+ });
11
+ }
12
+
13
+ $(cucumberSearchFrameLinks);
14
+
15
+
16
+
17
+ function toggleScenarioExample(id,example) {
18
+
19
+ var element = $("#" + id + "Example" + example + "Steps")[0];
20
+
21
+ $('#' + id + ' tr').each(function(index) {
22
+ this.style.backgroundColor = (index % 2 == 0 ? '#FFFFFF' : '#F0F6F9' );
23
+ });
24
+
25
+ if (element.style.display != 'none') {
26
+ element = $("#" + id + "Steps")[0];
27
+ } else {
28
+ $('#' + id + ' .outline * tr')[example].style.backgroundColor = '#FFCC80';
29
+ }
30
+
31
+ $('#' + id + ' .steps').each(function(index) {
32
+ this.style.display = 'none';
33
+ });
34
+
35
+ element.style.display = 'block';
36
+
37
+ }
38
+
39
+ function determine_tags_used_in_formula(tag_string) {
40
+ //$("#tag_debug")[0].innerHTML = "";
41
+
42
+ tag_string = tag_string.replace(/^(\s+)|(\s+)$/,'').replace(/\s{2,}/,' ');
43
+
44
+ var tags = tag_string.match(/@\w+/g);
45
+
46
+ var return_tags = [];
47
+
48
+ if (tags != null) {
49
+ tags.forEach(function(tag, index, array) {
50
+ //$("#tag_debug")[0].innerHTML += tag + " ";
51
+ if (tag_list.indexOf(tag) != -1) { return_tags.push(tag); }
52
+ });
53
+ }
54
+
55
+ return return_tags;
56
+ }
57
+
58
+
59
+ function display_example_command_line(tags) {
60
+ $("#command_example")[0].innerHTML = "cucumber ";
61
+
62
+ if (tags.length > 0) {
63
+ $("#command_example")[0].innerHTML += "--tags " + tags.join(" --tags ");
64
+ }
65
+ }
66
+
67
+ function display_qualifying_features_and_scenarios(tags) {
68
+ //$("#tag_debug")[0].innerHTML = "";
69
+
70
+ if (tags.length > 0) {
71
+
72
+ $(".feature,.scenario").each(function(feature){
73
+ this.style.display = "none";
74
+ });
75
+
76
+ $(".feature.\\" + tags.join(".\\") + ",.scenario.\\" + tags.join(".\\")).each(function(feature) {
77
+ //$("#tag_debug")[0].innerHTML += feature + " " + this;
78
+ this.style.display = "block";
79
+ });
80
+
81
+
82
+ } else {
83
+ $(".feature,.scenario").each(function(feature){
84
+ this.style.display = "block";
85
+ });
86
+ }
87
+
88
+ }
@@ -0,0 +1,13 @@
1
+ <meta name="Content-Type" content="text/html; charset=UTF-8" />
2
+ <title><%= @page_title %></title>
3
+ <link rel="stylesheet" href="/css/style.css" type="text/css" media="screen" charset="utf-8" />
4
+ <link rel="stylesheet" href="/css/custom.css" type="text/css" media="screen" charset="utf-8" />
5
+ <link rel="stylesheet" href="/css/common.css" type="text/css" media="screen" charset="utf-8" />
6
+ <link rel="stylesheet" href="/css/cucumber.css" type="text/css" media="screen" charset="utf-8" />
7
+ <script type="text/javascript" charset="utf-8" src="/js/jquery.js"></script>
8
+ <script type="text/javascript" charset="utf-8" src="/js/autocomplete.js"></script>
9
+ <script type="text/javascript" charset="utf-8" src="/js/app.js"></script>
10
+ <script type="text/javascript" charset="utf-8" src="/js/cucumber.js"></script>
11
+ <script type="text/javascript" charset="utf-8">
12
+ library = '<%= base_path(router.list_prefix) %>';
13
+ </script>
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html>
4
+ <head>
5
+ <meta name="Content-Type" content="text/html; charset=<%= charset %>" />
6
+ <link rel="stylesheet" href="/css/full_list.css" type="text/css" media="screen" charset="utf-8" />
7
+ <link rel="stylesheet" href="/css/common.css" type="text/css" media="screen" charset="utf-8" />
8
+ <link rel="stylesheet" href="/css/cucumber.css" type="text/css" media="screen" charset="utf-8" />
9
+ <script type="text/javascript" charset="utf-8" src="/js/jquery.js"></script>
10
+ <script type="text/javascript" charset="utf-8" src="/js/full_list.js"></script>
11
+ <script type="text/javascript" charset="utf-8" src="/js/cucumber.js"></script>
12
+ <base id="base_target" target="_parent" />
13
+ </head>
14
+ <body>
15
+ <script type="text/javascript" charset="utf-8">
16
+ if (window.top.frames.main) {
17
+ document.getElementById('base_target').target = 'main';
18
+ document.body.className = 'frames';
19
+ }
20
+ </script>
21
+ <div id="content">
22
+ <h1 id="full_list_header"><%= @list_title %></h1>
23
+ <div id="nav">
24
+ <a target="_self" href="/<%= base_path(router.list_prefix) %>/features">Features</a> |
25
+ <a target="_self" href="/<%= base_path(router.list_prefix) %>/tags">Tags</a> |
26
+ <a target="_self" href="/<%= base_path(router.list_prefix) %>/class">Classes</a> |
27
+ <a target="_self" href="/<%= base_path(router.list_prefix) %>/methods">Methods</a> |
28
+ <a target="_self" href="/<%= base_path(router.list_prefix) %>/files">Files</a>
29
+ </div>
30
+ <div id="search">Search: <input type="text" /></div>
31
+
32
+
33
+ <ul id="full_list" class="<%= @list_class || @list_type %>">
34
+ <%= erb "full_list_#{@list_type}" %>
35
+ </ul>
36
+ </div>
37
+ </body>
38
+ </html>
39
+
@@ -0,0 +1,18 @@
1
+ include T('default/fulldoc/html')
2
+
3
+ def init
4
+ # This is the css class type; here we just default to class
5
+ @list_class = "class"
6
+ case @list_type.to_sym
7
+ when :features; @list_title = "Features"
8
+ when :tags; @list_title = "Tags"
9
+ when :class; @list_title = "Class List"
10
+ when :methods; @list_title = "Method List"
11
+ when :files; @list_title = "File List"
12
+ end
13
+ sections :full_list
14
+ end
15
+
16
+ def all_features_link
17
+ linkify YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE, "All Features"
18
+ end
@@ -0,0 +1,39 @@
1
+ <script type="text/javascript" charset="utf-8">
2
+ $(function() {
3
+ $(".developer").hide();
4
+ $(".scenario .title").css('cursor','pointer');
5
+ });
6
+ </script>
7
+
8
+ <div class="feature">
9
+ <div class="title">
10
+ <a id="view" class="control" href="#">[More Detail]</a>
11
+ <a id="expand" class="control" href="#">[Collapse All]</a>
12
+ <div style="clear: right;"></div>
13
+ <span class="pre"><%= @feature.keyword %>:</span>
14
+ <span class="name"><%= @feature.value %></span>
15
+ </div>
16
+
17
+ <% if @feature.comments.length > 0 %>
18
+ <div class="comments developer">
19
+ <%= htmlify_with_newlines @feature.comments %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <div class="description">
24
+ <%= htmlify_with_newlines @feature.description %>
25
+ </div>
26
+
27
+ <div class="meta">
28
+ <div class="file developer"><%= h(@feature.file) %></div>
29
+ <div style="clear: right;"></div>
30
+ <div class="tags developer">
31
+ <% @feature.tags.each do |tag| %>
32
+ <a href="<%= url_for tag %>"><%= tag.value %></a>
33
+ <% end %>
34
+ </div>
35
+ </div>
36
+
37
+ <%= yieldall %>
38
+ </div>
39
+
@@ -0,0 +1 @@
1
+ <div class="none">No Steps Defined</div>
@@ -0,0 +1,59 @@
1
+ <div class="outline">
2
+
3
+ <% if @scenario.examples? %>
4
+
5
+ <% @scenario.examples.each_with_index do |example,example_index| %>
6
+ <div class="keyword">
7
+ <%= h example.keyword %><%= example.name != "" ? ':' : '' %>
8
+ </div>
9
+ <div class="example-group-name"><%= h example.name %></div>
10
+ <br style="clear: both;"/>
11
+ <table>
12
+ <thead>
13
+ <tr>
14
+ <% example.headers.each_with_index do |header,header_index| %>
15
+ <th><%= h(header) %></th>
16
+ <% end %>
17
+ </tr>
18
+ </thead>
19
+ <% unless example.data.empty? %>
20
+ <% example.data.each_with_index do |row,row_index| %>
21
+ <tr class="<%= (row_index + 1) % 2 == 0 ? "even example#{example_index+1}-#{row_index +1}" : "odd example#{example_index+1}-#{row_index +1}" %>">
22
+ <% row.each_with_index do |column,column_index| %>
23
+ <td><%= h(column.to_s.strip) %></td>
24
+ <% end %>
25
+ </tr>
26
+ <% end %>
27
+ <% else %>
28
+ <!-- Scenario Outline example table is empty -->
29
+ <tr class="odd">
30
+ <td colspan="<%= example.headers.length %>" style="text-align: center;">
31
+ No Examples Defined
32
+ </td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+ <% end %>
37
+
38
+ <% unless @scenario.scenarios.empty? %>
39
+ <% sc = 0 %>
40
+ <% @scenario.examples.each_with_index do |example, example_index| %>
41
+ <% example.data.each_with_index do |row, row_index| %>
42
+ <div class="keyword">Scenario <%= sc+1 %></div>
43
+ <br style="clear: both;"/>
44
+ <div style="display: none;" class="steps developer <%= "example#{example_index + 1}-#{row_index +1}" %>">
45
+ <% scenario = @scenario.scenarios[sc] %>
46
+ <% @scenario_outline = @scenario ; @scenario = scenario ; @steps = scenario.steps %>
47
+ <%= erb(:steps) %>
48
+ <% @scenario = @scenario_outline %>
49
+ <% sc += 1 %>
50
+ </div>
51
+ <% end %>
52
+ <% end %>
53
+ <% end %>
54
+ <% else %>
55
+ <div class="keyword">No Example Table Defined</div>
56
+ <div class="keyword suggestion developer">[!] Did you mean to create a Scenario?</div>
57
+ <br style="clear: both;"/>
58
+ <% end %>
59
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="text">
2
+ <%= htmlify_with_newlines @step.text %>
3
+ </div>
@@ -0,0 +1,55 @@
1
+ <div class="scenario <%= @id %>">
2
+ <a name="<%= @id %>" />
3
+ <div class="title">
4
+ <div style="float: left;">
5
+ <a class="toggle"> - </a>
6
+ <span class="pre"><%= @scenario.keyword %>:</span>
7
+ <span class="name"><%= h @scenario.value %></span>
8
+ </div>
9
+ <a class="link" style="float:right; clear:right;" href="<%= url_for(@scenario.feature, @id) %>">link</a>
10
+ </div>
11
+
12
+ <% if @scenario.description.length > 0 %>
13
+ <div class="description">
14
+ <%= htmlify_with_newlines @scenario.description %>
15
+ </div>
16
+ <% end %>
17
+
18
+ <% if @scenario.comments.length > 0 %>
19
+ <div class="comments developer">
20
+ <%= htmlify_with_newlines @scenario.comments %>
21
+ </div>
22
+ <% end %>
23
+
24
+ <div class="details">
25
+ <div class="meta developer">
26
+ <div class="file"><%= @scenario.location %></div>
27
+ <% unless @scenario.tags.empty? %>
28
+ <div style="clear:right;"></div>
29
+ <div class="tags">
30
+ <% @scenario.tags.each do |tag| %>
31
+ <a href="<%= url_for tag %>"><%= tag.value %></a>
32
+ <% end %>
33
+ </div>
34
+ <% end%>
35
+ <div style="clear: both;"></div>
36
+ </div>
37
+
38
+ <div class="steps">
39
+ <% if @scenario.steps.empty? %>
40
+ <%= erb(:no_steps_defined) %>
41
+ <% else %>
42
+ <%= @steps = @scenario.steps ; erb(:steps) %>
43
+ <% end %>
44
+ </div>
45
+
46
+
47
+ <%= erb(:outline) if @scenario.outline? %>
48
+
49
+ </div>
50
+
51
+ <div class="attributes" style="display:none;">
52
+ <input type="hidden" name="collapsed" value="false">
53
+ </div>
54
+
55
+ </div>
@@ -0,0 +1,54 @@
1
+ def init
2
+ super
3
+ @feature = object
4
+
5
+ sections.push :feature
6
+
7
+ sections.push :scenarios if object.scenarios
8
+
9
+ end
10
+
11
+ def background
12
+ @scenario = @feature.background
13
+ @id = "background"
14
+ erb(:scenario)
15
+ end
16
+
17
+ def scenarios
18
+ scenarios = ""
19
+
20
+ if @feature.background
21
+ @scenario = @feature.background
22
+ @id = "background"
23
+ scenarios += erb(:scenario)
24
+ end
25
+
26
+ @feature.scenarios.each_with_index do |scenario,index|
27
+ @scenario = scenario
28
+ @id = "scenario_#{index}"
29
+ scenarios += erb(:scenario)
30
+ end
31
+
32
+ scenarios
33
+ end
34
+
35
+
36
+ def highlight_matches(step)
37
+ step.value.dup.tap do |value|
38
+ if step.definition
39
+ matches = step.definition.regex.match(step.value)
40
+ if matches
41
+ matches.named_captures.to_a.reverse.each_with_index do |(name,match),index|
42
+ next if match == nil
43
+ next unless name.start_with?("placeholder_")
44
+ highlight = "<span class='match'>#{h(match)}</span>"
45
+ value[matches.begin((matches.size - 1) - index)..(matches.end((matches.size - 1) - index) - 1)] = highlight
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def htmlify_with_newlines(text)
53
+ text.split("\n").collect {|c| h(c).gsub(/\s/,'&nbsp;') }.join("<br/>")
54
+ end
@@ -0,0 +1,39 @@
1
+ <% @steps.each_with_index do |step,index| %>
2
+ <% @step = step %>
3
+
4
+ <% if step.comments && step.comments.length > 0 %>
5
+ <div class="comments developer"><%= htmlify_with_newlines step.comments %></div>
6
+ <% end %>
7
+ <div class="step <%= (index + 1) % 2 == 0 ? 'even' : 'odd' %>">
8
+ <span class="predicate"><%= step.keyword %></span>
9
+
10
+ <% if @scenario.outline? %>
11
+ <%= h step.value %>
12
+ <% else %>
13
+
14
+ <% if step.definition %>
15
+ <span class="defined">
16
+ <%= highlight_matches(step) %>
17
+ <div class="definition developer">
18
+ <a href="<%= url_for step.definition %>"><div class="valid">&nbsp;</div></a>
19
+ </div>
20
+ </span>
21
+ <% else %>
22
+ <span class="undefined">
23
+ <%= h step.value %>
24
+ <div class="definition developer">
25
+ <a href="<%= url_for YARD::CodeObjects::Cucumber::CUCUMBER_STEPTRANSFORM_NAMESPACE %>#undefined_steps">
26
+ <div class="invalid">&nbsp;</div>
27
+ </a>
28
+ </div>
29
+ </span>
30
+ <% end %>
31
+
32
+ <% end %>
33
+ </div>
34
+
35
+ <%= erb(:table) if step.has_table? %>
36
+ <%= erb(:pystring) if step.has_text? %>
37
+
38
+
39
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <div class="multiline">
2
+ <table style="">
3
+
4
+ <thead>
5
+ <tr>
6
+ <% @step.table.first.each_with_index do |column,column_index| %>
7
+ <th class="<%= (column_index + 1) % 2 == 0 ? 'even' : 'odd' %>"><%= h(column.strip) %></th>
8
+ <% end %>
9
+ </tr>
10
+ </thead>
11
+
12
+ <% @step.table[1..-1].each_with_index do |row,row_index| %>
13
+ <tr class="<%= (row_index + 1) % 2 == 0 ? 'even' : 'odd' %>">
14
+ <% row.each_with_index do |column,column_index| %>
15
+ <td><%= h(column.strip) %></td>
16
+ <% end %>
17
+ </tr>
18
+ <% end %>
19
+ </table>
20
+ </div>
@@ -0,0 +1,30 @@
1
+ <% if @elements && !@elements.empty? %>
2
+ <% i = (@elements.length % 2 == 0 ? 1 : 0) %>
3
+ <table style="margin-left: 10px; width: 100%;">
4
+ <tr>
5
+ <td valign='top' width="50%">
6
+ <% @elements.each do |letter, objects| %>
7
+ <% if (i += 1) > (@elements.length / 2 + 1) %>
8
+ </td><td valign='top' width="50%">
9
+ <% i = 0 %>
10
+ <% end %>
11
+ <ul id="alpha_<%= letter %>" class="alpha">
12
+ <li class="letter"><%= letter %></li>
13
+ <ul>
14
+ <% objects.each do |obj| %>
15
+ <li>
16
+ <%= linkify obj, obj.value %>
17
+ <% if obj.is_a? YARD::CodeObjects::Cucumber::FeatureDirectory %>
18
+ <small>(<%= obj.expanded_path %>)</small>
19
+ <% else %>
20
+ <small>(<%= obj.file %>)</small>
21
+ <% end %>
22
+ </li>
23
+ <% end %>
24
+ </ul>
25
+ </ul>
26
+ <% end %>
27
+ </td>
28
+ </tr>
29
+ </table>
30
+ <% end %>