jsanders-ruport 1.7.1

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 (76) hide show
  1. data/AUTHORS +48 -0
  2. data/LICENSE +59 -0
  3. data/README +114 -0
  4. data/Rakefile +93 -0
  5. data/examples/RWEmerson.jpg +0 -0
  6. data/examples/anon.rb +43 -0
  7. data/examples/btree/commaleon/commaleon.rb +263 -0
  8. data/examples/btree/commaleon/sample_data/ticket_count.csv +124 -0
  9. data/examples/btree/commaleon/sample_data/ticket_count2.csv +119 -0
  10. data/examples/centered_pdf_text_box.rb +83 -0
  11. data/examples/data/tattle.dump +82 -0
  12. data/examples/example.csv +3 -0
  13. data/examples/line_plotter.rb +61 -0
  14. data/examples/pdf_report_with_common_base.rb +72 -0
  15. data/examples/png_embed.rb +54 -0
  16. data/examples/roadmap.png +0 -0
  17. data/examples/row_renderer.rb +39 -0
  18. data/examples/simple_pdf_lines.rb +25 -0
  19. data/examples/simple_templating_example.rb +34 -0
  20. data/examples/tattle_ruby_version.rb +39 -0
  21. data/examples/tattle_rubygems_version.rb +37 -0
  22. data/examples/trac_ticket_status.rb +59 -0
  23. data/lib/ruport.rb +127 -0
  24. data/lib/ruport/controller.rb +616 -0
  25. data/lib/ruport/controller/grouping.rb +71 -0
  26. data/lib/ruport/controller/table.rb +54 -0
  27. data/lib/ruport/data.rb +4 -0
  28. data/lib/ruport/data/feeder.rb +111 -0
  29. data/lib/ruport/data/grouping.rb +399 -0
  30. data/lib/ruport/data/record.rb +297 -0
  31. data/lib/ruport/data/table.rb +950 -0
  32. data/lib/ruport/extensions.rb +4 -0
  33. data/lib/ruport/formatter.rb +254 -0
  34. data/lib/ruport/formatter/csv.rb +149 -0
  35. data/lib/ruport/formatter/html.rb +161 -0
  36. data/lib/ruport/formatter/pdf.rb +591 -0
  37. data/lib/ruport/formatter/template.rb +187 -0
  38. data/lib/ruport/formatter/text.rb +231 -0
  39. data/lib/uport.rb +1 -0
  40. data/test/controller_test.rb +743 -0
  41. data/test/csv_formatter_test.rb +164 -0
  42. data/test/data_feeder_test.rb +88 -0
  43. data/test/grouping_test.rb +410 -0
  44. data/test/helpers.rb +11 -0
  45. data/test/html_formatter_test.rb +201 -0
  46. data/test/pdf_formatter_test.rb +354 -0
  47. data/test/record_test.rb +332 -0
  48. data/test/samples/addressbook.csv +6 -0
  49. data/test/samples/data.csv +3 -0
  50. data/test/samples/data.tsv +3 -0
  51. data/test/samples/dates.csv +1409 -0
  52. data/test/samples/erb_test.sql +1 -0
  53. data/test/samples/query_test.sql +1 -0
  54. data/test/samples/ruport_test.sql +8 -0
  55. data/test/samples/test.sql +2 -0
  56. data/test/samples/test.yaml +3 -0
  57. data/test/samples/ticket_count.csv +124 -0
  58. data/test/table_pivot_test.rb +134 -0
  59. data/test/table_test.rb +838 -0
  60. data/test/template_test.rb +48 -0
  61. data/test/text_formatter_test.rb +258 -0
  62. data/util/bench/data/record/bench_as_vs_to.rb +18 -0
  63. data/util/bench/data/record/bench_constructor.rb +46 -0
  64. data/util/bench/data/record/bench_indexing.rb +65 -0
  65. data/util/bench/data/record/bench_reorder.rb +35 -0
  66. data/util/bench/data/record/bench_to_a.rb +19 -0
  67. data/util/bench/data/table/bench_column_manip.rb +103 -0
  68. data/util/bench/data/table/bench_dup.rb +24 -0
  69. data/util/bench/data/table/bench_init.rb +67 -0
  70. data/util/bench/data/table/bench_manip.rb +125 -0
  71. data/util/bench/formatter/bench_csv.rb +14 -0
  72. data/util/bench/formatter/bench_html.rb +14 -0
  73. data/util/bench/formatter/bench_pdf.rb +14 -0
  74. data/util/bench/formatter/bench_text.rb +14 -0
  75. data/util/bench/samples/tattle.csv +1237 -0
  76. metadata +176 -0
@@ -0,0 +1,187 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # template.rb provides templating support for Ruby Reports.
4
+ #
5
+ # Copyright August 2007, Gregory Brown / Michael Milner. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+
10
+ class Ruport::Formatter::TemplateNotDefined < StandardError; end
11
+
12
+ # This class provides templating functionality for Ruport.
13
+ # New templates are created using the Template.create method.
14
+ #
15
+ # Example:
16
+ #
17
+ # Ruport::Formatter::Template.create(:simple) do |t|
18
+ # t.page_layout = :landscape
19
+ # t.grouping_style = :offset
20
+ # end
21
+ #
22
+ # You can then determine how the template should be used by defining
23
+ # an <tt>apply_template</tt> method in your formatter.
24
+ #
25
+ # Example:
26
+ #
27
+ # class Ruport::Formatter::PDF
28
+ # def apply_template
29
+ # options.paper_orientation = template.page_layout
30
+ # options.style = template.grouping_style
31
+ # end
32
+ # end
33
+ #
34
+ # When you're ready to render the output, you can set the :template as an
35
+ # option for the formatter. Using the template remains optional and you can
36
+ # still render the report without it.
37
+ #
38
+ # Example:
39
+ #
40
+ # puts g.to_pdf(:template => :simple) #=> uses the template
41
+ # puts g.to_pdf #=> doesn't use the template
42
+ #
43
+ # The built-in formatters all have <tt>apply_template</tt> methods defined that
44
+ # accept a standard set of options. Each option can be set by supplying a hash
45
+ # with the keys/values listed in the tables below.
46
+ #
47
+ # Example:
48
+ #
49
+ # Ruport::Formatter::Template.create(:simple) do |format|
50
+ # format.page = {
51
+ # :size => "LETTER",
52
+ # :layout => :landscape
53
+ # }
54
+ # end
55
+ #
56
+ # If you define a template with the name :default, then it will be used by
57
+ # all formatters unless they either specify a template or explicitly turn off
58
+ # the templating functionality by using :template => false.
59
+ #
60
+ # Example:
61
+ #
62
+ # Ruport::Formatter::Template.create(:simple)
63
+ # Ruport::Formatter::Template.create(:default)
64
+ #
65
+ # puts g.to_pdf #=> uses the :default template
66
+ # puts g.to_pdf(:template => :simple) #=> uses the :simple template
67
+ # puts g.to_pdf(:template => false) #=> doesn't use a template
68
+ #
69
+ # ==== PDF Formatter Options
70
+ #
71
+ # Option Key Value
72
+ #
73
+ # page :size Any size supported by the :paper
74
+ # option to PDF::Writer.new
75
+ #
76
+ # :layout :portrait, :landscape
77
+ #
78
+ # text Any available to Corresponding values
79
+ # PDF::Writer#text
80
+ #
81
+ # table All attributes of Corresponding values
82
+ # PDF::SimpleTable
83
+ #
84
+ # :column_options - All attributes of
85
+ # PDF::SimpleTable::Column
86
+ # except :heading
87
+ # - Hash keyed by a column name, whose
88
+ # value is a hash containing any of
89
+ # the other:column_options (sets values
90
+ # for specific columns)
91
+ # - :heading => { All attributes of
92
+ # PDF::SimpleTable::Column::Heading }
93
+ #
94
+ # column :alignment :left, :right, :center, :full
95
+ #
96
+ # :width column width
97
+ #
98
+ # heading :alignment :left, :right, :center, :full
99
+ #
100
+ # :bold true or false
101
+ #
102
+ # :title heading title (if not set,
103
+ # defaults to column name)
104
+ #
105
+ # grouping :style :inline, :justified, :separated, :offset
106
+ #
107
+ #
108
+ # ==== Text Formatter Options
109
+ #
110
+ # Option Key Value
111
+ #
112
+ # table :show_headings true or false
113
+ # :width Table width
114
+ # :ignore_width true or false
115
+ #
116
+ # column :alignment :center
117
+ # :maximum_width Max column width
118
+ #
119
+ # grouping :show_headings true or false
120
+ #
121
+ #
122
+ # ==== HTML Formatter Options
123
+ #
124
+ # Option Key Value
125
+ #
126
+ # table :show_headings true or false
127
+ #
128
+ # grouping :style :inline, :justified
129
+ # :show_headings true or false
130
+ #
131
+ #
132
+ # ==== CSV Formatter Options
133
+ #
134
+ # Option Key Value
135
+ #
136
+ # table :show_headings true or false
137
+ #
138
+ # grouping :style :inline, :justified, :raw
139
+ # :show_headings true or false
140
+ #
141
+ # format_options All options Corresponding values
142
+ # available to
143
+ # FasterCSV.new
144
+ #
145
+ class Ruport::Formatter::Template < Ruport::Controller::Options
146
+
147
+ # Returns all existing templates in a hash keyed by the template names.
148
+ def self.templates
149
+ @templates ||= Hash.new
150
+ end
151
+
152
+ # Creates a new template with a name given by <tt>label</tt>.
153
+ #
154
+ # Example:
155
+ #
156
+ # Ruport::Formatter::Template.create(:simple) do |t|
157
+ # t.page_layout = :landscape
158
+ # t.grouping_style = :offset
159
+ # end
160
+ #
161
+ # You can inherit all the options set in a template by using the :base option
162
+ # and providing an existing template name to use as the base.
163
+ #
164
+ # Example:
165
+ #
166
+ # Ruport::Formatter::Template.create(:derived, :base => :simple)
167
+ #
168
+ def self.create(label,opts={})
169
+ if opts[:base]
170
+ obj = Marshal.load(Marshal.dump(self[opts[:base]]))
171
+ else
172
+ obj = new
173
+ end
174
+ yield(obj) if block_given?
175
+ templates[label] = obj
176
+ end
177
+
178
+ # Returns an existing template with the provided name (label).
179
+ def self.[](label)
180
+ templates[label] or raise Ruport::Formatter::TemplateNotDefined
181
+ end
182
+
183
+ # Returns the default template.
184
+ def self.default
185
+ templates[:default]
186
+ end
187
+ end
@@ -0,0 +1,231 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # formatter/text.rb provides text formatting for Ruport.
4
+ #
5
+ # Created by Gregory Brown, some time around Spring 2006.
6
+ # Copyright (C) 2006-2007, All Rights Reserved.
7
+ #
8
+ # Mathijs Mohlmann and Marshall T. Vandegrift have provided some patches for
9
+ # this class, see AUTHORS file for details.
10
+ #
11
+ # This is free software distributed under the same terms as Ruby 1.8
12
+ # See LICENSE and COPYING for details.
13
+ module Ruport
14
+
15
+ # This class provides text output for Ruport's Row, Table, Group, and
16
+ # Grouping controllers
17
+ #
18
+ # It handles things like automatically truncating tables that go off the
19
+ # edge of the screen in the console, proper column alignment, and pretty
20
+ # output that looks something like this:
21
+ #
22
+ # +------------------------------+
23
+ # | apple | banana | strawberry |
24
+ # +------------------------------+
25
+ # | yes | no | yes |
26
+ # | yes | yes | red snapper |
27
+ # | what | the | red snapper |
28
+ # +------------------------------+
29
+ #
30
+ # === Supported Options
31
+ #
32
+ # <tt>:max_col_width:</tt> Ordinal array of column widths. Set automatically
33
+ # but can be overridden.
34
+ #
35
+ # <tt>:alignment:</tt> Defaults to left justify text and right justify
36
+ # numbers. Centers all fields when set to :center.
37
+ #
38
+ # <tt>:table_width:</tt> Will truncate rows at this limit.
39
+ #
40
+ # <tt>:show_table_headers:</tt> Defaults to true
41
+ #
42
+ # <tt>:show_group_headers:</tt> Defaults to true
43
+ #
44
+ # <tt>:ignore_table_width:</tt> When set to true, outputs full table without
45
+ # truncating it. Useful for file output.
46
+ class Formatter::Text < Formatter
47
+
48
+ renders [:txt, :text], :for => [ Controller::Row, Controller::Table,
49
+ Controller::Group, Controller::Grouping ]
50
+
51
+ # Hook for setting available options using a template. See the template
52
+ # documentation for the available options and their format.
53
+ def apply_template
54
+ apply_table_format_template(template.table)
55
+ apply_column_format_template(template.column)
56
+ apply_grouping_format_template(template.grouping)
57
+ end
58
+
59
+ # Checks to ensure the table is not empty and then calls
60
+ # calculate_max_col_widths.
61
+ #
62
+ def prepare_table
63
+ raise Ruport::FormatterError, "Can't output table without " +
64
+ "data or column names." if data.empty? && data.column_names.empty?
65
+ calculate_max_col_widths
66
+ end
67
+
68
+ # Uses the column names from the given Data::Table to generate a table
69
+ # header.
70
+ #
71
+ # Calls fit_to_width to truncate the table heading if necessary.
72
+ #
73
+ def build_table_header
74
+ return unless should_render_column_names?
75
+
76
+ c = data.column_names.enum_for(:each_with_index).map { |f,i|
77
+ f.to_s.center(options.max_col_width[i])
78
+ }
79
+
80
+ output << fit_to_width("#{hr}| #{c.join(' | ')} |\n")
81
+ end
82
+
83
+ # Generates the body of the text table.
84
+ #
85
+ # Defaults to numeric values being right justified, and other values being
86
+ # left justified. Can be changed to support centering of output by
87
+ # setting options.alignment to :center
88
+ #
89
+ # Uses fit_to_width to truncate the table if necessary.
90
+ #
91
+ def build_table_body
92
+ output << fit_to_width(hr)
93
+ return if data.empty?
94
+
95
+ calculate_max_col_widths unless options.max_col_width
96
+
97
+ data.each { |row| build_row(row) }
98
+
99
+ output << fit_to_width(hr)
100
+ end
101
+
102
+ # Generates a formatted text row.
103
+ #
104
+ # Defaults to numeric values being right justified, and other values being
105
+ # left justified. Can be changed to support centering of output by
106
+ # setting options.alignment to :center
107
+ #
108
+ # Uses fit_to_width to truncate the row if necessary.
109
+ #
110
+ def build_row(data = self.data)
111
+ max_col_widths_for_row(data) unless options.max_col_width
112
+
113
+ data.enum_for(:each_with_index).inject(line=[]) { |s,e|
114
+ field,index = e
115
+ if options.alignment.eql? :center
116
+ line << field.to_s.center(options.max_col_width[index])
117
+ else
118
+ align = field.is_a?(Numeric) ? :rjust : :ljust
119
+ line << field.to_s.send(align, options.max_col_width[index])
120
+ end
121
+ }
122
+ output << fit_to_width("| #{line.join(' | ')} |\n")
123
+ end
124
+
125
+ # Renders the header for a group using the group name.
126
+ #
127
+ def build_group_header
128
+ output << "#{data.name}:\n\n"
129
+ end
130
+
131
+ # Creates the group body. Since group data is a table, just uses the
132
+ # Table controller.
133
+ #
134
+ def build_group_body
135
+ render_table data, options
136
+ end
137
+
138
+ # Generates the body for a grouping. Iterates through the groups and
139
+ # renders them using the group controller.
140
+ #
141
+ def build_grouping_body
142
+ render_inline_grouping(options)
143
+ end
144
+
145
+ # Returns false if column_names are empty or options.show_table_headers
146
+ # is false/nil. Returns true otherwise.
147
+ #
148
+ def should_render_column_names?
149
+ not data.column_names.empty? || !options.show_table_headers
150
+ end
151
+
152
+ # Generates the horizontal rule by calculating the total table width and
153
+ # then generating a bar that looks like this:
154
+ #
155
+ # "+------------------+"
156
+ def hr
157
+ ref = data.column_names.empty? ? data[0].to_a : data.column_names
158
+ len = options.max_col_width.inject(ref.length * 3) {|s,e|s+e}
159
+ "+" + "-"*(len-1) + "+\n"
160
+ end
161
+
162
+ # Returns options.table_width if specified.
163
+ #
164
+ # Otherwise, uses SystemExtensions to determine terminal width.
165
+ def width
166
+ options.table_width ||= SystemExtensions.terminal_width
167
+ end
168
+
169
+ # Truncates a string so that it does not exceed Text#width
170
+ def fit_to_width(s)
171
+ return s if options.ignore_table_width
172
+ # workaround for Rails setting terminal_width to 1
173
+ max_width = width < 2 ? 80 : width
174
+
175
+ s.split("\n").each { |r|
176
+ r.gsub!(/\A.{#{max_width+1},}/) { |m| m[0,max_width-2] + ">>" }
177
+ }.join("\n") + "\n"
178
+ end
179
+
180
+ # Determines the text widths for each column.
181
+ def calculate_max_col_widths
182
+ # allow override
183
+ return if options.max_col_width
184
+
185
+ options.max_col_width = []
186
+
187
+ unless data.column_names.empty?
188
+ data.column_names.each_index do |i|
189
+ options.max_col_width[i] = data.column_names[i].to_s.length
190
+ end
191
+ end
192
+
193
+ data.each { |r| max_col_widths_for_row(r) }
194
+ end
195
+
196
+ # Used to calculate the <tt>max_col_widths</tt> array.
197
+ # Override this to tweak the automatic column size adjustments.
198
+ def max_col_widths_for_row(row)
199
+ options.max_col_width ||= []
200
+ row.each_with_index do |f,i|
201
+ if !options.max_col_width[i] || f.to_s.length > options.max_col_width[i]
202
+ options.max_col_width[i] = f.to_s.length
203
+ end
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def apply_table_format_template(t)
210
+ t = (t || {}).merge(options.table_format || {})
211
+ options.show_table_headers = t[:show_headings] if
212
+ options.show_table_headers.nil?
213
+ options.table_width ||= t[:width]
214
+ options.ignore_table_width = t[:ignore_width] if
215
+ options.ignore_table_width.nil?
216
+ end
217
+
218
+ def apply_column_format_template(t)
219
+ t = (t || {}).merge(options.column_format || {})
220
+ options.max_col_width ||= t[:maximum_width]
221
+ options.alignment ||= t[:alignment]
222
+ end
223
+
224
+ def apply_grouping_format_template(t)
225
+ t = (t || {}).merge(options.grouping_format || {})
226
+ options.show_group_headers = t[:show_headings] if
227
+ options.show_group_headers.nil?
228
+ end
229
+
230
+ end
231
+ end
data/lib/uport.rb ADDED
@@ -0,0 +1 @@
1
+ require "ruport"
@@ -0,0 +1,743 @@
1
+ #!/usr/bin/env ruby -w
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), "helpers")
3
+
4
+ ###########################################################################
5
+ #
6
+ # NOTE:
7
+ #
8
+ # As it stands, we haven't found a more clever way to test the formatting
9
+ # system than to just create a bunch of renderers and basic formatters for
10
+ # different concepts we're trying to test. Patches and ideas welcome:
11
+ #
12
+ # list.rubyreports.org
13
+ ############################################################################
14
+
15
+ #============================================================================
16
+ # These two renderers represent the two styles that can be used when defining
17
+ # renderers in Ruport. The OldSchoolController approach has largely been
18
+ # deprecated, but still has uses in edge cases that we need to support.
19
+ #============================================================================
20
+
21
+ class OldSchoolController < Ruport::Controller
22
+
23
+ def run
24
+ formatter do
25
+ build_header
26
+ build_body
27
+ build_footer
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ class VanillaController < Ruport::Controller
34
+ stage :header,:body,:footer
35
+ end
36
+
37
+
38
+ # This formatter implements some junk output so we can be sure
39
+ # that the hooks are being set up right. Perhaps these could
40
+ # be replaced by mock objects in the future.
41
+ class DummyText < Ruport::Formatter
42
+
43
+ renders :text, :for => OldSchoolController
44
+
45
+ def prepare_document
46
+ output << "p"
47
+ end
48
+
49
+ def build_header
50
+ output << "header\n"
51
+ end
52
+
53
+ def build_body
54
+ output << "body\n"
55
+ end
56
+
57
+ def build_footer
58
+ output << "footer\n"
59
+ end
60
+
61
+ def finalize_document
62
+ output << "f"
63
+ end
64
+ end
65
+
66
+ # This formatter modifies the (String) data object passed to it
67
+ class Destructive < Ruport::Formatter
68
+
69
+ def prepare_document; end
70
+
71
+ def build_header; end
72
+
73
+ def build_body
74
+ output << "You sent #{data}"
75
+ data.replace("RUBBISH")
76
+ end
77
+
78
+ def build_footer; end
79
+
80
+ def finalize_document; end
81
+ end
82
+
83
+
84
+ class VanillaBinary < Ruport::Formatter
85
+ renders :bin, :for => VanillaController
86
+ save_as_binary_file
87
+ end
88
+
89
+ class SpecialFinalize < Ruport::Formatter
90
+ renders :with_finalize, :for => VanillaController
91
+
92
+ def finalize
93
+ output << "I has been finalized"
94
+ end
95
+ end
96
+
97
+ class TestController < Test::Unit::TestCase
98
+
99
+ def teardown
100
+ Ruport::Formatter::Template.instance_variable_set(:@templates, nil)
101
+ end
102
+
103
+ def test_trivial
104
+ actual = OldSchoolController.render(:text)
105
+ assert_equal "header\nbody\nfooter\n", actual
106
+ end
107
+
108
+ context "when running a formatter with custom a finalize method" do
109
+ def specify_finalize_method_should_be_called
110
+ assert_equal "I has been finalized",
111
+ VanillaController.render_with_finalize
112
+ end
113
+ end
114
+
115
+ context "when using templates" do
116
+ def specify_apply_template_should_be_called
117
+ Ruport::Formatter::Template.create(:stub)
118
+ Table(%w[a b c]).to_csv(:template => :stub) do |r|
119
+ r.formatter.expects(:apply_template)
120
+ end
121
+ end
122
+
123
+ def specify_undefined_template_should_throw_sensible_error
124
+ assert_raises(Ruport::Formatter::TemplateNotDefined) do
125
+ Table(%w[a b c]).to_csv(:template => :sub)
126
+ end
127
+ end
128
+ end
129
+
130
+ context "when using default templates" do
131
+ def specify_default_template_should_be_called
132
+ Ruport::Formatter::Template.create(:default)
133
+ Table(%w[a b c]).to_csv do |r|
134
+ r.formatter.expects(:apply_template)
135
+ assert r.formatter.template == Ruport::Formatter::Template[:default]
136
+ end
137
+ end
138
+
139
+ def specify_specific_should_override_default
140
+ Ruport::Formatter::Template.create(:default)
141
+ Ruport::Formatter::Template.create(:stub)
142
+ Table(%w[a b c]).to_csv(:template => :stub) do |r|
143
+ r.formatter.expects(:apply_template)
144
+ assert r.formatter.template == Ruport::Formatter::Template[:stub]
145
+ end
146
+ end
147
+
148
+ def specify_should_be_able_to_disable_templates
149
+ Ruport::Formatter::Template.create(:default)
150
+ Table(%w[a b c]).to_csv(:template => false) do |r|
151
+ r.formatter.expects(:apply_template).never
152
+ end
153
+ end
154
+ end
155
+
156
+ def test_using_io
157
+ require "stringio"
158
+ out = StringIO.new
159
+ a = OldSchoolController.render(:text) { |r| r.io = out }
160
+ out.rewind
161
+ assert_equal "header\nbody\nfooter\n", out.read
162
+ assert_equal "", out.read
163
+ end
164
+
165
+ def test_using_file
166
+ f = []
167
+ File.expects(:open).yields(f)
168
+ a = OldSchoolController.render(:text, :file => "foo.text")
169
+ assert_equal "header\nbody\nfooter\n", f[0]
170
+
171
+ f = []
172
+ File.expects(:open).with("blah","wb").yields(f)
173
+ VanillaController.render(:bin, :file => "blah")
174
+ end
175
+
176
+ def test_using_file_via_rendering_tools
177
+ f = []
178
+ File.expects(:open).yields(f)
179
+ Table(%w[a b c], :data => [[1,2,3],[4,5,6]]).save_as("foo.csv")
180
+ assert_equal "a,b,c\n1,2,3\n4,5,6\n", f[0]
181
+ end
182
+
183
+
184
+ def test_formats
185
+ assert_equal( {}, Ruport::Controller.formats )
186
+ assert_equal( { :text => DummyText },OldSchoolController.formats )
187
+ end
188
+
189
+ def test_method_missing
190
+ actual = OldSchoolController.render_text
191
+ assert_equal "header\nbody\nfooter\n", actual
192
+ end
193
+
194
+ def test_formatter
195
+ # normal instance mode
196
+ rend = OldSchoolController.new
197
+ rend.send(:use_formatter,:text)
198
+
199
+ assert_kind_of Ruport::Formatter, rend.formatter
200
+ assert_kind_of DummyText, rend.formatter
201
+
202
+ # render mode
203
+ OldSchoolController.render_text do |r|
204
+ assert_kind_of Ruport::Formatter, r.formatter
205
+ assert_kind_of DummyText, r.formatter
206
+ end
207
+
208
+ assert_equal "body\n", rend.formatter { build_body }.output
209
+
210
+ rend.formatter.clear_output
211
+ assert_equal "", rend.formatter.output
212
+ end
213
+
214
+ def test_options_act_like_indifferent_hash
215
+ opts = Ruport::Controller::Options.new
216
+ opts.foo = "bar"
217
+ assert_equal "bar", opts[:foo]
218
+ assert_equal "bar", opts["foo"]
219
+
220
+ opts["f"] = "bar"
221
+ assert_equal "bar", opts[:f]
222
+ assert_equal "bar", opts.f
223
+ assert_equal "bar", opts["f"]
224
+
225
+ opts[:apple] = "banana"
226
+ assert_equal "banana", opts.apple
227
+ assert_equal "banana", opts["apple"]
228
+ assert_equal "banana", opts[:apple]
229
+ end
230
+
231
+ end
232
+
233
+
234
+ class TestFormatterUsingBuild < Test::Unit::TestCase
235
+ # This formatter uses the build syntax
236
+ class UsesBuild < Ruport::Formatter
237
+ renders :text_using_build, :for => VanillaController
238
+
239
+ build :header do
240
+ output << "header\n"
241
+ end
242
+
243
+ build :body do
244
+ output << "body\n"
245
+ end
246
+
247
+ build :footer do
248
+ output << "footer\n"
249
+ end
250
+ end
251
+
252
+ def test_should_render_using_build_syntax
253
+ assert_equal "header\nbody\nfooter\n",
254
+ VanillaController.render_text_using_build
255
+ VanillaController.render_text_using_build do |rend|
256
+ assert rend.formatter.respond_to?(:build_header)
257
+ assert rend.formatter.respond_to?(:build_body)
258
+ assert rend.formatter.respond_to?(:build_footer)
259
+ end
260
+ end
261
+ end
262
+
263
+
264
+ class TestFormatterWithLayout < Test::Unit::TestCase
265
+ # This formatter is meant to check out a special case in Ruport's renderer,
266
+ # in which a layout method is called and yielded to when defined
267
+ class WithLayout < DummyText
268
+ renders :text_with_layout, :for => VanillaController
269
+
270
+ def layout
271
+ output << "---\n"
272
+ yield
273
+ output << "---\n"
274
+ end
275
+
276
+ end
277
+
278
+ def test_layout
279
+ assert_equal "---\nheader\nbody\nfooter\n---\n",
280
+ VanillaController.render_text_with_layout
281
+ end
282
+
283
+ def test_layout_disabled
284
+ assert_equal "header\nbody\nfooter\n",
285
+ VanillaController.render_text_with_layout(:layout => false)
286
+ end
287
+
288
+ end
289
+
290
+
291
+ class TestControllerWithManyHooks < Test::Unit::TestCase
292
+ # This provides a way to check several hooks that controllers supports
293
+ class ControllerWithManyHooks < Ruport::Controller
294
+ add_format DummyText, :text
295
+ add_format Destructive, :destructive
296
+
297
+ prepare :document
298
+
299
+ stage :header
300
+ stage :body
301
+ stage :footer
302
+
303
+ finalize :document
304
+
305
+ def setup
306
+ options.apple = true
307
+ end
308
+
309
+ end
310
+
311
+ def test_hash_options_setters
312
+ a = ControllerWithManyHooks.render(:text, :subtitle => "foo",
313
+ :subsubtitle => "bar") { |r|
314
+ assert_equal "foo", r.options.subtitle
315
+ assert_equal "bar", r.options.subsubtitle
316
+ }
317
+ end
318
+
319
+ def test_data_accessors
320
+ a = ControllerWithManyHooks.render(:text, :data => [1,2,4]) { |r|
321
+ assert_equal [1,2,4], r.data
322
+ }
323
+
324
+ b = ControllerWithManyHooks.render_text(%w[a b c]) { |r|
325
+ assert_equal %w[a b c], r.data
326
+ }
327
+
328
+ c = ControllerWithManyHooks.render_text(%w[a b f],:snapper => :red) { |r|
329
+ assert_equal %w[a b f], r.data
330
+ assert_equal :red, r.options.snapper
331
+ }
332
+ end
333
+
334
+ def test_formatter_data_dup
335
+ source = "some text"
336
+ result = ControllerWithManyHooks.render(:destructive, :data => source)
337
+ assert_equal("You sent some text", result)
338
+ assert_equal("some text", source)
339
+ end
340
+
341
+ def test_stage_helper
342
+ assert ControllerWithManyHooks.stages.include?('body')
343
+ end
344
+
345
+ def test_finalize_helper
346
+ assert_equal :document, ControllerWithManyHooks.final_stage
347
+ end
348
+
349
+ def test_prepare_helper
350
+ assert_equal :document, ControllerWithManyHooks.first_stage
351
+ end
352
+
353
+ def test_finalize_again
354
+ assert_raise(Ruport::Controller::StageAlreadyDefinedError) {
355
+ ControllerWithManyHooks.finalize :report
356
+ }
357
+ end
358
+
359
+ def test_prepare_again
360
+ assert_raise(Ruport::Controller::StageAlreadyDefinedError) {
361
+ ControllerWithManyHooks.prepare :foo
362
+ }
363
+ end
364
+
365
+ def test_renderer_using_helpers
366
+ actual = ControllerWithManyHooks.render(:text)
367
+ assert_equal "pheader\nbody\nfooter\nf", actual
368
+
369
+ actual = ControllerWithManyHooks.render_text
370
+ assert_equal "pheader\nbody\nfooter\nf", actual
371
+ end
372
+
373
+ def test_required_option_helper
374
+ a = ControllerWithManyHooks.dup
375
+ a.required_option :title
376
+
377
+ a.render_text do |r|
378
+ r.title = "Test Report"
379
+ assert_equal "Test Report", r.options.title
380
+ end
381
+
382
+ end
383
+
384
+ def test_without_required_option
385
+ a = ControllerWithManyHooks.dup
386
+ a.required_option :title
387
+
388
+ assert_raise(Ruport::Controller::RequiredOptionNotSet) { a.render(:text) }
389
+ end
390
+
391
+ end
392
+
393
+
394
+ class TestControllerWithRunHook < Test::Unit::TestCase
395
+
396
+ class ControllerWithRunHook < Ruport::Controller
397
+ add_format DummyText, :text
398
+
399
+ required_option :foo,:bar
400
+ stage :header
401
+ stage :body
402
+ stage :footer
403
+
404
+ def run
405
+ formatter.output << "|"
406
+ super
407
+ end
408
+
409
+ end
410
+
411
+ def test_renderer_with_run_hooks
412
+ assert_equal "|header\nbody\nfooter\n",
413
+ ControllerWithRunHook.render_text(:foo => "bar",:bar => "baz")
414
+ end
415
+
416
+ end
417
+
418
+
419
+ class TestControllerWithHelperModule < Test::Unit::TestCase
420
+
421
+ class ControllerWithHelperModule < VanillaController
422
+
423
+ add_format DummyText, :stub
424
+
425
+ module Helpers
426
+ def say_hello
427
+ "Hello Dolly"
428
+ end
429
+ end
430
+ end
431
+
432
+ def test_renderer_helper_module
433
+ ControllerWithHelperModule.render_stub do |r|
434
+ assert_equal "Hello Dolly", r.formatter.say_hello
435
+ end
436
+ end
437
+ end
438
+
439
+
440
+ class TestMultiPurposeFormatter < Test::Unit::TestCase
441
+ # This provides a way to check the multi-format hooks for the Controller
442
+ class MultiPurposeFormatter < Ruport::Formatter
443
+
444
+ renders [:html,:text], :for => VanillaController
445
+
446
+ def build_header
447
+ a = 10
448
+
449
+ text { output << "Foo: #{a}\n" }
450
+ html { output << "<b>Foo: #{a}</b>\n" }
451
+ end
452
+
453
+ def build_body
454
+ html { output << "<pre>\n" }
455
+ output << options.body_text
456
+ html { output << "\n</pre>\n" }
457
+ end
458
+
459
+ end
460
+
461
+ def test_multi_purpose
462
+ text = VanillaController.render_text(:body_text => "foo")
463
+ assert_equal "Foo: 10\nfoo", text
464
+ html = VanillaController.render_html(:body_text => "bar")
465
+ assert_equal "<b>Foo: 10</b>\n<pre>\nbar\n</pre>\n",html
466
+ end
467
+
468
+
469
+ def test_method_missing_hack_formatter
470
+ assert_equal [:html,:text], MultiPurposeFormatter.formats
471
+
472
+ a = MultiPurposeFormatter.new
473
+ a.format = :html
474
+
475
+ visited = false
476
+ a.html { visited = true }
477
+
478
+ assert visited
479
+
480
+ visited = false
481
+ a.text { visited = true }
482
+ assert !visited
483
+
484
+ assert_raises(NoMethodError) do
485
+ a.pdf { 'do nothing' }
486
+ end
487
+ end
488
+
489
+ end
490
+
491
+
492
+ class TestFormatterErbHelper < Test::Unit::TestCase
493
+ class ErbFormatter < Ruport::Formatter
494
+
495
+ renders :terb, :for => VanillaController
496
+
497
+ def build_body
498
+ # demonstrate local binding
499
+ @foo = "bar"
500
+ if options.binding
501
+ output << erb("Binding Override: <%= reverse %>",
502
+ :binding => options.binding)
503
+ else
504
+ output << erb("Default Binding: <%= @foo %>")
505
+ end
506
+ end
507
+
508
+ end
509
+
510
+ #FIXME: need to test file
511
+
512
+ def test_self_bound
513
+ assert_equal "Default Binding: bar", VanillaController.render_terb
514
+ end
515
+
516
+ def test_custom_bound
517
+ a = [1,2,3]
518
+ arr_binding = a.instance_eval { binding }
519
+ assert_equal "Binding Override: 321",
520
+ VanillaController.render_terb(:binding => arr_binding)
521
+ end
522
+ end
523
+
524
+
525
+ class TestOptionReaders < Test::Unit::TestCase
526
+
527
+ class ControllerForCheckingOptionReaders < Ruport::Controller
528
+ required_option :foo
529
+ end
530
+
531
+ class ControllerForCheckingPassivity < Ruport::Controller
532
+ def foo
533
+ "apples"
534
+ end
535
+ required_option :foo
536
+ end
537
+
538
+ def setup
539
+ @renderer = ControllerForCheckingOptionReaders.new
540
+ @renderer.formatter = Ruport::Formatter.new
541
+
542
+ @passive = ControllerForCheckingPassivity.new
543
+ @passive.formatter = Ruport::Formatter.new
544
+ end
545
+
546
+ def test_options_are_readable
547
+ @renderer.foo = 5
548
+ assert_equal 5, @renderer.foo
549
+ end
550
+
551
+ def test_methods_are_not_overridden
552
+ @passive.foo = 5
553
+ assert_equal "apples", @passive.foo
554
+ assert_equal 5, @passive.options.foo
555
+ assert_equal 5, @passive.formatter.options.foo
556
+ end
557
+
558
+ end
559
+
560
+ class TestSetupOrdering < Test::Unit::TestCase
561
+
562
+ class ControllerWithSetup < Ruport::Controller
563
+ stage :bar
564
+ def setup
565
+ options.foo.capitalize!
566
+ end
567
+ end
568
+
569
+ class BasicFormatter < Ruport::Formatter
570
+ renders :text, :for => ControllerWithSetup
571
+
572
+ def build_bar
573
+ output << options.foo
574
+ end
575
+ end
576
+
577
+ def test_render_hash_options_should_be_called_before_setup
578
+ assert_equal "Hello", ControllerWithSetup.render_text(:foo => "hello")
579
+ end
580
+
581
+ def test_render_block_should_be_called_before_setup
582
+ assert_equal "Hello",
583
+ ControllerWithSetup.render_text { |r| r.options.foo = "hello" }
584
+ end
585
+
586
+ end
587
+
588
+ class CustomFormatter < Ruport::Formatter
589
+ def custom_helper
590
+ output << "Custom!"
591
+ end
592
+ end
593
+
594
+ class ControllerWithAnonymousFormatters < Ruport::Controller
595
+
596
+ stage :report
597
+
598
+ formatter :html do
599
+ build :report do
600
+ output << textile("h1. Hi there")
601
+ end
602
+ end
603
+
604
+ formatter :csv do
605
+ build :report do
606
+ build_row([1,2,3])
607
+ end
608
+ end
609
+
610
+ formatter :pdf do
611
+ build :report do
612
+ add_text "hello world"
613
+ end
614
+ end
615
+
616
+ formatter :text do
617
+ build :report do
618
+ output << "Hello world"
619
+ end
620
+ end
621
+
622
+ formatter :custom => CustomFormatter do
623
+
624
+ build :report do
625
+ output << "This is "
626
+ custom_helper
627
+ end
628
+
629
+ end
630
+
631
+ end
632
+
633
+ class TestAnonymousFormatter < Test::Unit::TestCase
634
+ context "When using built in Ruport formatters" do
635
+
636
+ def specify_text_formatter_shortcut_is_accessible
637
+ assert_equal "Hello world", ControllerWithAnonymousFormatters.render_text
638
+ assert_equal "1,2,3\n", ControllerWithAnonymousFormatters.render_csv
639
+ assert_equal "<h1>Hi there</h1>", ControllerWithAnonymousFormatters.render_html
640
+ assert_not_nil ControllerWithAnonymousFormatters.render_pdf
641
+ end
642
+
643
+ end
644
+
645
+ context "When using custom formatters" do
646
+ def specify_custom_formatter_shortcut_is_accessible
647
+ assert_equal "This is Custom!", ControllerWithAnonymousFormatters.render_custom
648
+ end
649
+ end
650
+
651
+ end
652
+
653
+ class TestControllerHooks < Test::Unit::TestCase
654
+
655
+ context "when renderable_data omitted" do
656
+
657
+ require "mocha"
658
+
659
+ class DummyObject
660
+ include Ruport::Controller::Hooks
661
+ renders_as_table
662
+ end
663
+
664
+ def specify_should_return_self
665
+ a = DummyObject.new
666
+ rend = mock("renderer")
667
+ rend.expects(:data=).with(a)
668
+ Ruport::Controller::Table.expects(:render).with(:csv,{}).yields(rend)
669
+ a.as(:csv)
670
+ end
671
+
672
+ end
673
+
674
+ context "when using renderable_data" do
675
+
676
+ class DummyObject2
677
+ include Ruport::Controller::Hooks
678
+ renders_as_table
679
+
680
+ def renderable_data(format)
681
+ 1
682
+ end
683
+ end
684
+
685
+ def specify_should_return_results_of_renderable_data
686
+ a = DummyObject2.new
687
+ rend = mock("renderer")
688
+ rend.expects(:data=).with(1)
689
+ Ruport::Controller::Table.expects(:render).with(:csv,{}).yields(rend)
690
+ a.as(:csv)
691
+ end
692
+
693
+ class DummyObject3
694
+ include Ruport::Controller::Hooks
695
+ renders_as_table
696
+
697
+ def renderable_data
698
+ raise ArgumentError
699
+ end
700
+ end
701
+
702
+ def specify_should_not_mask_errors
703
+ assert_raises(ArgumentError) { DummyObject3.new.as(:csv) }
704
+ end
705
+
706
+ class DummyObject4
707
+ include Ruport::Controller::Hooks
708
+ renders_as_table
709
+
710
+ def renderable_data(format)
711
+ case format
712
+ when :html
713
+ 1
714
+ when :csv
715
+ 2
716
+ end
717
+ end
718
+ end
719
+
720
+ def specify_should_return_results_of_renderable_data_using_format
721
+ a = DummyObject4.new
722
+ rend = mock("renderer")
723
+ rend.expects(:data=).with(2)
724
+ Ruport::Controller::Table.expects(:render).with(:csv,{}).yields(rend)
725
+ a.as(:csv)
726
+ end
727
+
728
+ end
729
+
730
+ context "when attempting to render a format that doesn't exist" do
731
+
732
+ def specify_an_unknown_format_error_should_be_raised
733
+
734
+ assert_raises(Ruport::Controller::UnknownFormatError) do
735
+ Ruport::Controller.render_foo
736
+ end
737
+
738
+ end
739
+ end
740
+
741
+
742
+
743
+ end