jsanders-ruport 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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