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,4 @@
1
+ if defined? Gem
2
+ require "gem_plugin"
3
+ GemPlugin::Manager.instance.load "ruport" => GemPlugin::INCLUDE
4
+ end
@@ -0,0 +1,254 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # formatter.rb provides a generalized base class for creating ruport formatters.
4
+ #
5
+ # Created By Gregory Brown
6
+ # Copyright (C) December 2006, All Rights Reserved.
7
+ #
8
+ # This is free software distributed under the same terms as Ruby 1.8
9
+ # See LICENSE and COPYING for details.
10
+ module Ruport
11
+ # Formatter is the base class for Ruport's format implementations.
12
+ #
13
+ # Typically, a Formatter will implement one or more output types,
14
+ # and be registered with one or more Controller classes.
15
+ #
16
+ # This class provides all the necessary base functionality to make
17
+ # use of Ruport's rendering system, including option handling, data
18
+ # access, and basic output wrapping.
19
+ #
20
+ # The following example should provide a general idea of how formatters
21
+ # work, but see the built in formatters for reference implementations.
22
+ #
23
+ # A simple Controller definition is included to help show the example in
24
+ # context, but you can also build your own custom interface to formatter
25
+ # if you wish.
26
+ #
27
+ # class ReverseController < Ruport::Controller
28
+ # stage :reversed_header, :reversed_body
29
+ # end
30
+ #
31
+ # class ReversedText < Ruport::Formatter
32
+ #
33
+ # # Hooks formatter up to controller
34
+ # renders :txt, :for => ReverseController
35
+ #
36
+ # # Implements ReverseController's :reversed_header hook
37
+ # # but can be used by any controller
38
+ # def build_reversed_header
39
+ # output << "#{options.header_text}\n"
40
+ # output << "The reversed text will follow\n"
41
+ # end
42
+ #
43
+ # # Implements ReverseController's :reversed_body hook
44
+ # # but can be used by any controller
45
+ # def build_reversed_body
46
+ # output << data.reverse << "\n"
47
+ # end
48
+ #
49
+ # end
50
+ #
51
+ # puts ReverseController.render_txt(:data => "apple",
52
+ # :header_text => "Hello Mike, Hello Joe!")
53
+ #
54
+ # -----
55
+ # OUTPUT:
56
+ #
57
+ # Hello Mike, Hello Joe!
58
+ # The reversed text will follow
59
+ # elppa
60
+ #
61
+ class Formatter
62
+
63
+ # Provides shortcuts so that you can use Ruport's default rendering
64
+ # capabilities within your custom formatters
65
+ #
66
+ module RenderingTools
67
+ # Uses Controller::Row to render the Row object with the
68
+ # given options.
69
+ #
70
+ # Sets the <tt>:io</tt> attribute by default to the existing
71
+ # formatter's <tt>output</tt> object.
72
+ def render_row(row,options={},&block)
73
+ render_helper(Controller::Row,row,options,&block)
74
+ end
75
+
76
+ # Uses Controller::Table to render the Table object with the
77
+ # given options.
78
+ #
79
+ # Sets the :io attribute by default to the existing formatter's
80
+ # output object.
81
+ def render_table(table,options={},&block)
82
+ render_helper(Controller::Table,table,options,&block)
83
+ end
84
+
85
+ # Uses Controller::Group to render the Group object with the
86
+ # given options.
87
+ #
88
+ # Sets the :io attribute by default to the existing formatter's
89
+ # output object.
90
+ def render_group(group,options={},&block)
91
+ render_helper(Controller::Group,group,options,&block)
92
+ end
93
+
94
+ # Uses Controller::Grouping to render the Grouping object with the
95
+ # given options.
96
+ #
97
+ # Sets the :io attribute by default to the existing formatter's
98
+ # output object.
99
+ def render_grouping(grouping,options={},&block)
100
+ render_helper(Controller::Grouping,grouping,options,&block)
101
+ end
102
+
103
+ # Iterates through the data in the grouping and renders each group
104
+ # followed by a newline.
105
+ #
106
+ def render_inline_grouping(options={},&block)
107
+ data.each do |_,group|
108
+ render_group(group, options, &block)
109
+ output << "\n"
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def render_helper(rend_klass, source_data,options={},&block)
116
+ options = {:data => source_data,
117
+ :io => output,
118
+ :layout => false }.merge(options)
119
+
120
+ options[:io] = "" if self.class.kind_of?(Ruport::Formatter::PDF)
121
+ rend_klass.render(format,options) do |rend|
122
+ block[rend] if block
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ include RenderingTools
129
+
130
+ # Set by the <tt>:data</tt> attribute from Controller#render
131
+ attr_reader :data
132
+
133
+ # Set automatically by Controller#render(format) or Controller#render_format
134
+ attr_accessor :format
135
+
136
+ # Set automatically by Controller#render as a Controller::Options object built
137
+ # by the hash provided.
138
+ attr_writer :options
139
+
140
+ # Registers the formatter with one or more Controllers.
141
+ #
142
+ # renders :pdf, :for => MyController
143
+ # render :text, :for => [MyController,YourController]
144
+ # renders [:csv,:html], :for => YourController
145
+ #
146
+ def self.renders(fmts,options={})
147
+ Array(fmts).each do |format|
148
+ Array(options[:for]).each do |o|
149
+ o.send(:add_format,self,format)
150
+ formats << format unless formats.include?(format)
151
+ end
152
+ end
153
+ end
154
+
155
+ # Allows you to implement stages in your formatter using the
156
+ # following syntax:
157
+ #
158
+ # class ReversedText < Ruport::Formatter
159
+ # renders :txt, :for => ReverseController
160
+ #
161
+ # build :reversed_header do
162
+ # output << "#{options.header_text}\n"
163
+ # output << "The reversed text will follow\n"
164
+ # end
165
+ #
166
+ # build :reversed_body do
167
+ # output << data.reverse << "\n"
168
+ # end
169
+ # end
170
+ #
171
+ def self.build(stage,&block)
172
+ define_method "build_#{stage}", &block
173
+ end
174
+
175
+ # Gives a list of formats registered for this formatter.
176
+ def self.formats
177
+ @formats ||= []
178
+ end
179
+
180
+ # Returns the template currently set for this formatter.
181
+ def template
182
+ Template[options.template] rescue nil || Template[:default]
183
+ end
184
+
185
+ # Stores a string used for outputting formatted data.
186
+ def output
187
+ return options.io if options.io
188
+ @output ||= ""
189
+ end
190
+
191
+ # Provides a Controller::Options object for storing formatting options.
192
+ def options
193
+ @options ||= Controller::Options.new
194
+ end
195
+
196
+ # Sets the data object, making a local copy using #dup. This may have
197
+ # a significant overhead for large tables, so formatters which don't
198
+ # modify the data object may wish to override this.
199
+ def data=(val)
200
+ @data = val.dup
201
+ end
202
+
203
+ # Clears the output.
204
+ def clear_output
205
+ @output.replace("")
206
+ end
207
+
208
+ # Saves the output to a file.
209
+ def save_output(filename)
210
+ File.open(filename,"w") {|f| f << output }
211
+ end
212
+
213
+ # Use to define that your formatter should save in binary format
214
+ def self.save_as_binary_file
215
+ define_method :save_output do |filename|
216
+ File.open(filename,"wb") {|f| f << output }
217
+ end
218
+ end
219
+
220
+ # Evaluates the string using ERB and returns the results.
221
+ #
222
+ # If <tt>:binding</tt> is specified, it will evaluate the template
223
+ # in that context.
224
+ def erb(string,options={})
225
+ require "erb"
226
+ if string =~ /(\.r\w+)|(\.erb)$/
227
+ ERB.new(File.read(string)).result(options[:binding]||binding)
228
+ else
229
+ ERB.new(string).result(options[:binding]||binding)
230
+ end
231
+ end
232
+
233
+ # Provides a shortcut for per-format handlers.
234
+ #
235
+ # Example:
236
+ #
237
+ # # will only be called if formatter is called for html output
238
+ # html { output << "Look, I'm handling html" }
239
+ #
240
+ def method_missing(id,*args)
241
+ if self.class.formats.include?(id)
242
+ yield() if format == id
243
+ else
244
+ super
245
+ end
246
+ end
247
+ end
248
+ end
249
+
250
+ require "ruport/formatter/template"
251
+ require "ruport/formatter/csv"
252
+ require "ruport/formatter/html"
253
+ require "ruport/formatter/text"
254
+ require "ruport/formatter/pdf"
@@ -0,0 +1,149 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # formatter/csv.rb provides csv formatting for Ruport.
4
+ #
5
+ # Original code dates back to the earliest versions of Ruport in August 2005
6
+ # Extended over time, with much of the existing code being added around
7
+ # December 2006.
8
+ #
9
+ # Copyright (C) 2005-2007 Gregory Brown, All Rights Reserved.
10
+ #
11
+ # This is free software distributed under the same terms as Ruby 1.8
12
+ # See LICENSE and COPYING for details.
13
+ #
14
+ module Ruport
15
+
16
+ # This formatter implements the CSV format for Ruport's Row, Table, Group
17
+ # and Grouping controllers. It is a light wrapper around
18
+ # James Edward Gray II's FasterCSV.
19
+ #
20
+ # === Rendering Options
21
+ #
22
+ # <tt>:style</tt> Used for grouping (:inline,:justified,:raw)
23
+ #
24
+ # <tt>:format_options</tt> A hash of FasterCSV options
25
+ #
26
+ # <tt>:formatter</tt> An existing FasterCSV object to write to
27
+ #
28
+ # <tt>:show_table_headers</tt> True by default
29
+ #
30
+ # <tt>:show_group_headers</tt> True by default
31
+ #
32
+ class Formatter::CSV < Formatter
33
+
34
+ renders :csv, :for => [ Controller::Row, Controller::Table,
35
+ Controller::Group, Controller::Grouping ]
36
+
37
+ def initialize
38
+ require "fastercsv" unless RUBY_VERSION > "1.9"
39
+ end
40
+
41
+ attr_writer :csv_writer
42
+
43
+ # Hook for setting available options using a template. See the template
44
+ # documentation for the available options and their format.
45
+ def apply_template
46
+ apply_table_format_template(template.table)
47
+ apply_grouping_format_template(template.grouping)
48
+
49
+ options.format_options ||= template.format_options
50
+ end
51
+
52
+ # Returns the current FCSV object or creates a new one if it has not
53
+ # been set yet. Note that FCSV(sig) has a cache and returns the *same*
54
+ # FCSV object if writing to the same underlying output with the same
55
+ # options.
56
+ #
57
+ def csv_writer
58
+ @csv_writer ||= options.formatter ||
59
+ FCSV(output, options.format_options || {})
60
+ end
61
+
62
+ # Generates table header by turning column_names into a CSV row.
63
+ # Uses the row controller to generate the actual formatted output
64
+ #
65
+ # This method does not do anything if options.show_table_headers is false
66
+ # or the Data::Table has no column names.
67
+ def build_table_header
68
+ unless data.column_names.empty? || !options.show_table_headers
69
+ render_row data.column_names, :format_options => options.format_options,
70
+ :formatter => csv_writer
71
+ end
72
+ end
73
+
74
+ # Calls the row controller for each row in the Data::Table
75
+ def build_table_body
76
+ fcsv = csv_writer
77
+ data.each { |row| fcsv << row }
78
+ end
79
+
80
+ # Produces CSV output for a data row.
81
+ def build_row(data = self.data)
82
+ csv_writer << data
83
+ end
84
+
85
+ # Renders the header for a group using the group name.
86
+ #
87
+ def build_group_header
88
+ csv_writer << [data.name.to_s] << []
89
+ end
90
+
91
+ # Renders the group body - uses the table controller to generate the output.
92
+ #
93
+ def build_group_body
94
+ render_table data, options.to_hash
95
+ end
96
+
97
+ # Generates a header for the grouping using the grouped_by column and the
98
+ # column names.
99
+ #
100
+ def build_grouping_header
101
+ unless options.style == :inline
102
+ csv_writer << [data.grouped_by] + grouping_columns
103
+ end
104
+ end
105
+
106
+ # Determines the proper style to use and renders the Grouping.
107
+ def build_grouping_body
108
+ case options.style
109
+ when :inline
110
+ render_inline_grouping(options)
111
+ when :justified, :raw
112
+ render_justified_or_raw_grouping
113
+ else
114
+ raise NotImplementedError, "Unknown style"
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def grouping_columns
121
+ data.data.to_a[0][1].column_names
122
+ end
123
+
124
+ def render_justified_or_raw_grouping
125
+ data.each do |_,group|
126
+ prefix = [group.name.to_s]
127
+ group.each do |row|
128
+ csv_writer << prefix + row.to_a
129
+ prefix = [nil] if options.style == :justified
130
+ end
131
+ csv_writer << []
132
+ end
133
+ end
134
+
135
+ def apply_table_format_template(t)
136
+ t = (t || {}).merge(options.table_format || {})
137
+ options.show_table_headers = t[:show_headings] if
138
+ options.show_table_headers.nil?
139
+ end
140
+
141
+ def apply_grouping_format_template(t)
142
+ t = (t || {}).merge(options.grouping_format || {})
143
+ options.style ||= t[:style]
144
+ options.show_group_headers = t[:show_headings] if
145
+ options.show_group_headers.nil?
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,161 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # formatter/html.rb provides html formatting for Ruport.
4
+ #
5
+ # Created by Gregory Brown, late 2005. Updated numerous times as needed to
6
+ # fit new formatting systems.
7
+ #
8
+ # Copyright (C) 2005-2007 Gregory Brown, All Rights Reserved.
9
+ #
10
+ # This is free software distributed under the same terms as Ruby 1.8
11
+ # See LICENSE and COPYING for details.
12
+ #
13
+ module Ruport
14
+ # This class produces HTML output for Ruport's Row, Table, Group, and
15
+ # Grouping controllers. It can be subclassed, as it has some helper methods
16
+ # that might be useful for custom output.
17
+ #
18
+ # === Rendering Options
19
+ #
20
+ # <tt>:show_table_headers</tt> True by default
21
+ #
22
+ # <tt>:show_group_headers</tt> True by default
23
+ #
24
+ # <tt>:style</tt> Used for grouping (:inline, :justified)
25
+ #
26
+ class Formatter::HTML < Formatter
27
+
28
+ renders :html, :for => [ Controller::Row, Controller::Table,
29
+ Controller::Group, Controller::Grouping ]
30
+
31
+ # Hook for setting available options using a template. See the template
32
+ # documentation for the available options and their format.
33
+ def apply_template
34
+ apply_table_format_template(template.table)
35
+ apply_grouping_format_template(template.grouping)
36
+ end
37
+
38
+ # Generates table headers based on the column names of your Data::Table.
39
+ #
40
+ # This method does not do anything if options.show_table_headers is false
41
+ # or the Data::Table has no column names.
42
+ def build_table_header
43
+ output << "\t<table>\n"
44
+ unless data.column_names.empty? || !options.show_table_headers
45
+ output << "\t\t<tr>\n\t\t\t<th>" +
46
+ data.column_names.join("</th>\n\t\t\t<th>") +
47
+ "</th>\n\t\t</tr>\n"
48
+ end
49
+ end
50
+
51
+ # Uses the Row controller to build up the table body.
52
+ # Replaces nil and empty strings with "&nbsp;"
53
+ def build_table_body
54
+ data.each do |row|
55
+ build_row(row.map { |e| e.to_s.empty? ? "&nbsp;" : e })
56
+ end
57
+ end
58
+
59
+ # Simply closes the table tag.
60
+ def build_table_footer
61
+ output << "\t</table>\n"
62
+ end
63
+
64
+ # Renders individual rows for the table.
65
+ def build_row(data = self.data)
66
+ output <<
67
+ "\t\t<tr>\n\t\t\t<td>" +
68
+ data.to_a.join("</td>\n\t\t\t<td>") +
69
+ "</td>\n\t\t</tr>\n"
70
+ end
71
+
72
+ # Renders the header for a group using the group name.
73
+ #
74
+ def build_group_header
75
+ output << "\t<p>#{data.name}</p>\n"
76
+ end
77
+
78
+ # Creates the group body. Since group data is a table, just uses the
79
+ # Table controller.
80
+ #
81
+ def build_group_body
82
+ render_table data, options.to_hash
83
+ end
84
+
85
+ # Generates the body for a grouping. Iterates through the groups and
86
+ # renders them using the group controller.
87
+ #
88
+ def build_grouping_body
89
+ case options.style
90
+ when :inline
91
+ render_inline_grouping(options)
92
+ when :justified
93
+ render_justified_grouping
94
+ end
95
+ end
96
+
97
+ # Generates <table> tags enclosing the yielded content.
98
+ #
99
+ # Example:
100
+ #
101
+ # output << html_table { "<tr><td>1</td><td>2</td></tr>\n" }
102
+ # #=> "<table>\n<tr><td>1</td><td>2</td></tr>\n</table>\n"
103
+ #
104
+ def html_table
105
+ "<table>\n" << yield << "</table>\n"
106
+ end
107
+
108
+ # Uses RedCloth to turn a string containing textile markup into HTML.
109
+ #
110
+ # Example:
111
+ #
112
+ # textile "*bar*" #=> "<p><strong>foo</strong></p>"
113
+ #
114
+ def textile(s)
115
+ require "redcloth"
116
+ RedCloth.new(s).to_html
117
+ rescue LoadError
118
+ raise RuntimeError, "You need RedCloth!\n gem install RedCloth -v 3.0.3"
119
+ end
120
+
121
+ private
122
+
123
+ def render_justified_grouping
124
+ output << "\t<table>\n\t\t<tr>\n\t\t\t<th>" +
125
+ "#{data.grouped_by}</th>\n\t\t\t<th>" +
126
+ grouping_columns.join("</th>\n\t\t\t<th>") +
127
+ "</th>\n\t\t</tr>\n"
128
+ data.each do |name, group|
129
+ group.each_with_index do |row, i|
130
+ output << "\t\t<tr>\n\t\t\t"
131
+ if i == 0
132
+ output << "<td class=\"groupName\">#{name}</td>\n\t\t\t<td>"
133
+ else
134
+ output << "<td>&nbsp;</td>\n\t\t\t<td>"
135
+ end
136
+ output << row.to_a.join("</td>\n\t\t\t<td>") +
137
+ "</td>\n\t\t</tr>\n"
138
+ end
139
+ end
140
+ output << "\t</table>\n"
141
+ end
142
+
143
+ def grouping_columns
144
+ data.data.to_a[0][1].column_names
145
+ end
146
+
147
+ def apply_table_format_template(t)
148
+ t = (t || {}).merge(options.table_format || {})
149
+ options.show_table_headers = t[:show_headings] if
150
+ options.show_table_headers.nil?
151
+ end
152
+
153
+ def apply_grouping_format_template(t)
154
+ t = (t || {}).merge(options.grouping_format || {})
155
+ options.style ||= t[:style]
156
+ options.show_group_headers = t[:show_headings] if
157
+ options.show_group_headers.nil?
158
+ end
159
+
160
+ end
161
+ end