ruport 0.12.0 → 1.0.0

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 (36) hide show
  1. data/Rakefile +1 -1
  2. data/examples/btree/commaleon/commaleon.rb +264 -0
  3. data/examples/btree/commaleon/sample_data/ticket_count.csv +124 -0
  4. data/examples/btree/commaleon/sample_data/ticket_count2.csv +119 -0
  5. data/examples/centered_pdf_text_box.rb +43 -16
  6. data/examples/data/tattle.dump +82 -0
  7. data/examples/line_plotter.rb +9 -14
  8. data/examples/pdf_report_with_common_base.rb +46 -43
  9. data/examples/{ruport_list/png_embed.rb → png_embed.rb} +3 -4
  10. data/examples/{ruport_list/roadmap.png → roadmap.png} +0 -0
  11. data/examples/simple_pdf_lines.rb +2 -0
  12. data/examples/tattle_ruby_version.rb +39 -0
  13. data/examples/tattle_rubygems_version.rb +39 -0
  14. data/examples/trac_ticket_status.rb +59 -0
  15. data/lib/ruport.rb +2 -2
  16. data/lib/ruport/data/grouping.rb +13 -8
  17. data/lib/ruport/formatter.rb +1 -1
  18. data/lib/ruport/formatter/html.rb +35 -2
  19. data/lib/ruport/formatter/pdf.rb +6 -4
  20. data/lib/ruport/formatter/text.rb +7 -7
  21. data/lib/ruport/renderer.rb +12 -8
  22. data/lib/ruport/renderer/grouping.rb +10 -0
  23. data/test/acts_as_reportable_test.rb +2 -2
  24. data/test/grouping_test.rb +18 -2
  25. data/test/html_formatter_test.rb +16 -0
  26. data/test/pdf_formatter_test.rb +16 -0
  27. data/test/renderer_test.rb +64 -9
  28. data/test/table_test.rb +2 -2
  29. data/util/bench/data/record/bench_as_vs_to.rb +4 -3
  30. metadata +15 -11
  31. data/examples/pdf_complex_report.rb +0 -39
  32. data/examples/pdf_styles.rb +0 -16
  33. data/examples/pdf_table_with_title.rb +0 -42
  34. data/test/database_test_.rb +0 -25
  35. data/test/samples/document.xml +0 -22
  36. data/test/samples/stonecodeblog.sql +0 -279
@@ -0,0 +1,59 @@
1
+ %w[ruport hpricot open-uri].each { |lib| require lib }
2
+
3
+ class TracSummaryReport
4
+
5
+ include Ruport::Renderer::Hooks
6
+
7
+ renders_as_table
8
+
9
+ def initialize(options={})
10
+ @days = options[:days] || 7
11
+ @timeline_uri = options[:timeline_uri]
12
+ end
13
+
14
+ class TicketStatus < Ruport::Data::Record
15
+
16
+ def closed
17
+ title =~ /Ticket.+(\w+ closed)/ ? 1 : 0
18
+ end
19
+
20
+ def opened
21
+ title =~ /Ticket.+(\w+ created)|(\w+ reopened)/ ? 1 : 0
22
+ end
23
+
24
+ end
25
+
26
+ def feed_data
27
+ uri = @timeline_uri + "?wiki=on&milestone=on&ticket=on&changeset=on"+
28
+ "&max=10000&daysback=#{@days-1}&format=rss"
29
+
30
+ feed = Hpricot(open(uri))
31
+
32
+ table = Table([:title, :date], :record_class => TicketStatus) do |table|
33
+ (feed/"item").each do |r|
34
+ title = (r/"title").innerHTML
35
+ next unless title =~ /Ticket.*(created|closed|reopene)/
36
+ table << { :title => title,
37
+ :date => Date.parse((r/"pubdate").innerHTML) }
38
+ end
39
+ end
40
+
41
+ Grouping(table,:by => :date)
42
+ end
43
+
44
+ def renderable_data
45
+ summary = feed_data.summary :date,
46
+ :opened => lambda { |g| g.sigma { |r| r.opened } },
47
+ :closed => lambda { |g| g.sigma { |r| r.closed } },
48
+ :order => [:date,:opened,:closed]
49
+
50
+ summary.sort_rows_by! { |r| r.date }
51
+ return summary
52
+ end
53
+ end
54
+
55
+ timeline = "http://stonecode.svnrepository.com/ruport/trac.cgi/timeline"
56
+
57
+ report = TracSummaryReport.new(:timeline_uri => timeline, :days => 14)
58
+ puts report.as(:text)
59
+
data/lib/ruport.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
4
4
  #
5
- # Copyright (c) 2006, All Rights Reserved.
5
+ # Copyright (c) 2005-2007, All Rights Reserved.
6
6
  #
7
7
  # This is free software. You may modify and redistribute this freely under
8
8
  # your choice of the GNU General Public License or the Ruby License.
@@ -12,7 +12,7 @@
12
12
 
13
13
  module Ruport #:nodoc:#
14
14
 
15
- VERSION = "0.12.0"
15
+ VERSION = "1.0.0"
16
16
 
17
17
  class FormatterError < RuntimeError #:nodoc:
18
18
  end
@@ -156,14 +156,19 @@ module Ruport::Data
156
156
  #
157
157
  # grouping = Grouping.new(table, :by => "a")
158
158
  #
159
- def initialize(data,options={})
160
- @grouped_by = options[:by]
161
- cols = Array(options[:by]).dup
162
- @data = data.to_group.send(:grouped_data, cols.shift)
163
- cols.each do |col|
164
- @data.each do |name,group|
165
- group.create_subgroups(col)
166
- end
159
+ def initialize(data={},options={})
160
+ if data.kind_of?(Hash)
161
+ @grouped_by = data[:by]
162
+ @data = {}
163
+ else
164
+ @grouped_by = options[:by]
165
+ cols = Array(options[:by]).dup
166
+ @data = data.to_group.send(:grouped_data, cols.shift)
167
+ cols.each do |col|
168
+ @data.each do |name,group|
169
+ group.create_subgroups(col)
170
+ end
171
+ end
167
172
  end
168
173
  end
169
174
 
@@ -113,7 +113,7 @@ module Ruport
113
113
  # followed by a newline.
114
114
  #
115
115
  def render_inline_grouping(options={},&block)
116
- data.each do |_,group|
116
+ data.each do |_,group|
117
117
  render_group(group, options, &block)
118
118
  output << "\n"
119
119
  end
@@ -21,12 +21,14 @@ module Ruport
21
21
  #
22
22
  # <tt>:show_group_headers</tt> True by default
23
23
  #
24
+ # <tt>:style</tt> Used for grouping (:inline, :justified)
25
+ #
24
26
  class Formatter::HTML < Formatter
25
27
 
26
28
  renders :html, :for => [ Renderer::Row, Renderer::Table,
27
29
  Renderer::Group, Renderer::Grouping ]
28
30
 
29
- opt_reader :show_table_headers, :show_group_headers
31
+ opt_reader :show_table_headers, :show_group_headers, :style
30
32
 
31
33
  # Generates table headers based on the column names of your Data::Table.
32
34
  #
@@ -80,7 +82,12 @@ module Ruport
80
82
  # renders them using the group renderer.
81
83
  #
82
84
  def build_grouping_body
83
- render_inline_grouping(options)
85
+ case style
86
+ when :inline
87
+ render_inline_grouping(options)
88
+ when :justified
89
+ render_justified_grouping
90
+ end
84
91
  end
85
92
 
86
93
  # Generates <table> tags enclosing the yielded content.
@@ -106,6 +113,32 @@ module Ruport
106
113
  rescue LoadError
107
114
  raise RuntimeError, "You need RedCloth!\n gem install RedCloth -v 3.0.3"
108
115
  end
116
+
117
+ private
118
+
119
+ def render_justified_grouping
120
+ output << "\t<table>\n\t\t<tr>\n\t\t\t<th>" +
121
+ "#{data.grouped_by}</th>\n\t\t\t<th>" +
122
+ grouping_columns.join("</th>\n\t\t\t<th>") +
123
+ "</th>\n\t\t</tr>\n"
124
+ data.each do |name, group|
125
+ group.each_with_index do |row, i|
126
+ output << "\t\t<tr>\n\t\t\t"
127
+ if i == 0
128
+ output << "<td class=\"groupName\">#{name}</td>\n\t\t\t<td>"
129
+ else
130
+ output << "<td>&nbsp;</td>\n\t\t\t<td>"
131
+ end
132
+ output << row.to_a.join("</td>\n\t\t\t<td>") +
133
+ "</td>\n\t\t</tr>\n"
134
+ end
135
+ end
136
+ output << "\t</table>"
137
+ end
138
+
139
+ def grouping_columns
140
+ data.data.to_a[0][1].column_names
141
+ end
109
142
 
110
143
  end
111
144
  end
@@ -41,8 +41,6 @@ module Ruport
41
41
  #
42
42
  class Formatter::PDF < Formatter
43
43
 
44
- ## THESE ARE WHY YOU SHOULD NEVER USE PDF::Writer
45
-
46
44
  module PDFWriterMemoryPatch #:nodoc:
47
45
  unless self.class.instance_methods.include?("_post_transaction_rewind")
48
46
  def _post_transaction_rewind
@@ -133,7 +131,7 @@ module Ruport
133
131
 
134
132
  # Determines which style to use and renders the main body for
135
133
  # Renderer::Grouping
136
- def build_grouping_body
134
+ def build_grouping_body
137
135
  case style
138
136
  when :inline
139
137
  render_inline_grouping(options.to_hash.merge(:formatter => pdf_writer,
@@ -311,6 +309,8 @@ module Ruport
311
309
  table_data.rename_columns { |c| c.to_s }
312
310
 
313
311
  format_opts = table_format.merge(format_opts) if table_format
312
+
313
+ old = pdf_writer.font_size
314
314
 
315
315
  ::PDF::SimpleTable.new do |table|
316
316
  table.extend(PDFSimpleTableOrderingPatch)
@@ -322,7 +322,9 @@ module Ruport
322
322
 
323
323
  format_opts.each {|k,v| table.send("#{k}=", v) }
324
324
  table.render_on(pdf_writer)
325
- end
325
+ end
326
+
327
+ pdf_writer.font_size = old
326
328
  end
327
329
 
328
330
  # This module provides tools to simplify some common drawing operations
@@ -18,13 +18,13 @@ module Ruport
18
18
  # edge of the screen in the console, proper column alignment, and pretty
19
19
  # output that looks something like this:
20
20
  #
21
- # +-----------------------------+
22
- # | apple | banana | strawberry |
23
- # +-----------------------------+
24
- # | yes | no | yes |
25
- # | yes | yes | god yes |
26
- # | what | the | f? |
27
- # +-----------------------------+
21
+ # +------------------------------+
22
+ # | apple | banana | strawberry |
23
+ # +------------------------------+
24
+ # | yes | no | yes |
25
+ # | yes | yes | red snapper |
26
+ # | what | the | red snapper |
27
+ # +------------------------------+
28
28
  #
29
29
  # === Supported Options
30
30
  #
@@ -10,8 +10,6 @@
10
10
  # for different kinds of tasks. See Renderer::Table for a tabular data
11
11
  # renderer.
12
12
  #
13
- # This class can easily be extended to build custom formatting systems, but if
14
- # you do not need that, it may not be relevant to study for your use of Ruport.
15
13
  class Ruport::Renderer
16
14
 
17
15
  class RequiredOptionNotSet < RuntimeError #:nodoc:
@@ -318,10 +316,16 @@ class Ruport::Renderer
318
316
  # option :font_size, :font_style
319
317
  # # other details omitted
320
318
  # end
321
- def option(*opts)
322
- opts.each do |opt|
319
+ def option(*opts)
320
+ opts.each do |opt|
321
+ o = opt
322
+ unless instance_methods(false).include?(o.to_s)
323
+ define_method(o) {
324
+ options.send(o.to_s)
325
+ }
326
+ end
323
327
  opt = "#{opt}="
324
- define_method(opt) {|t| options.send(opt, t) }
328
+ define_method(opt) {|t| options.send(opt, t) }
325
329
  end
326
330
  end
327
331
 
@@ -362,8 +366,8 @@ class Ruport::Renderer
362
366
  # sets the data and options, and then does the following process:
363
367
  #
364
368
  # * If the renderer contains a module Helpers, mix it in to the instance.
369
+ # * If a block is given, yield the Renderer instance
365
370
  # * If a setup() method is defined on the Renderer, call it
366
- # * If a block is given, yield the Renderer instance
367
371
  # * If the renderer has defined a run() method, call it, otherwise,
368
372
  # include Renderer::AutoRunner. (you usually won't need a run() method )
369
373
  # * call _run_ if it exists (This is provided by default, by AutoRunner)
@@ -375,9 +379,9 @@ class Ruport::Renderer
375
379
  # Please see the examples/ directory for custom renderer examples, because
376
380
  # this is not nearly as complicated as it sounds in most cases.
377
381
  def render(*args)
378
- rend = build(*args) { |r|
382
+ rend = build(*args) { |r|
383
+ yield(r) if block_given?
379
384
  r.setup if r.respond_to? :setup
380
- yield(r) if block_given?
381
385
  }
382
386
  if rend.respond_to? :run
383
387
  rend.run
@@ -1,3 +1,13 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # renderer/grouping.rb : Group data renderer for Ruby Reports
4
+ #
5
+ # Written by Michael Milner, 2007.
6
+ # Copyright (C) 2007, 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
+ #
1
11
  module Ruport
2
12
 
3
13
  # This class implements the basic renderer for a single group of data.
@@ -11,10 +11,10 @@ rescue LoadError
11
11
  nil
12
12
  end
13
13
 
14
- require "ruport/acts_as_reportable"
15
-
16
14
  if Object.const_defined?(:ActiveRecord) && Object.const_defined?(:Mocha)
17
15
 
16
+ require "ruport/acts_as_reportable"
17
+
18
18
  class Team < ActiveRecord::Base
19
19
  acts_as_reportable :except => 'id', :include => :players
20
20
  has_many :players
@@ -86,7 +86,7 @@ class TestGroupRendering < Test::Unit::TestCase
86
86
  @group = Ruport::Data::Group.new(:name => 'test',
87
87
  :data => [[1,2,3]],
88
88
  :column_names => %w[a b c])
89
- end
89
+ end
90
90
 
91
91
  def test_group_as
92
92
  assert_equal(7, @group.to_text.to_a.length)
@@ -129,7 +129,23 @@ class TestGrouping < Test::Unit::TestCase
129
129
  :column_names => %w[b c],
130
130
  :name => 4 ) }
131
131
  assert_equal c, b.data
132
- end
132
+ end
133
+
134
+ def test_empty_grouping
135
+ a = Ruport::Data::Grouping.new()
136
+ a << Group("foo",:data => [[1,2,3],[4,5,6]],
137
+ :column_names => %w[a b c] )
138
+ assert_equal "foo", a["foo"].name
139
+ assert_nil a.grouped_by
140
+ end
141
+
142
+ def test_empty_grouping_with_grouped_by
143
+ a = Ruport::Data::Grouping.new(:by => "nada")
144
+ a << Group("foo",:data => [[1,2,3],[4,5,6]],
145
+ :column_names => %w[a b c] )
146
+ assert_equal "foo", a["foo"].name
147
+ assert_equal "nada", a.grouped_by
148
+ end
133
149
 
134
150
  def test_grouping_indexing
135
151
  a = [Ruport::Data::Group.new( :data => [[2,3]],
@@ -101,6 +101,22 @@ class TestRenderHTMLGrouping < Test::Unit::TestCase
101
101
  "\t<th>c</th>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td>7</td>\n"+
102
102
  "\t\t\t<td>9</td>\n\t\t</tr>\n\t</table>\n", actual
103
103
  end
104
+
105
+ def test_render_justified_html_grouping
106
+ table = Table(%w[a b c]) << [1,2,3] << [1,1,3] << [2,7,9]
107
+ g = Grouping(table,:by => "a")
108
+ actual = Ruport::Renderer::Grouping.render(:html, :data => g,
109
+ :style => :justified)
110
+
111
+ assert_equal "\t<table>\n\t\t<tr>\n\t\t\t<th>a</th>\n\t\t\t<th>b</th>\n"+
112
+ "\t\t\t<th>c</th>\n\t\t</tr>\n\t\t<tr>\n\t\t\t"+
113
+ "<td class=\"groupName\">1</td>\n\t\t\t<td>"+
114
+ "2</td>\n\t\t\t<td>3</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t"+
115
+ "<td>&nbsp;</td>\n\t\t\t<td>1</td>\n\t\t\t<td>3</td>"+
116
+ "\n\t\t</tr>\n\t\t<tr>\n\t\t\t"+
117
+ "<td class=\"groupName\">2</td>\n\t\t\t<td>7</td>\n"+
118
+ "\t\t\t<td>9</td>\n\t\t</tr>\n\t</table>", actual
119
+ end
104
120
  end
105
121
 
106
122
 
@@ -69,6 +69,22 @@ class TestRenderPDFGrouping < Test::Unit::TestCase
69
69
  assert_raises(NotImplementedError) do
70
70
  grouping.to_pdf(:style => :red_snapper)
71
71
  end
72
+ end
73
+
74
+ def test_grouping_should_have_consistent_font_size
75
+ a = Table(%w[a b c]) << %w[eye like chicken] << %w[eye like liver] <<
76
+ %w[meow mix meow ] << %w[mix please deliver ]
77
+ b = Grouping(a, :by => "a")
78
+ splat = b.to_pdf.split("\n")
79
+ splat.grep(/meow/).each do |m|
80
+ assert_equal '10.0', m.split[5]
81
+ end
82
+ splat.grep(/mix/).each do |m|
83
+ assert_equal '10.0', m.split[5]
84
+ end
85
+ splat.grep(/eye/).each do |m|
86
+ assert_equal '10.0', m.split[5]
87
+ end
72
88
  end
73
89
 
74
90
  end
@@ -229,14 +229,6 @@ class TestRendererWithManyHooks < Test::Unit::TestCase
229
229
  assert_equal "pheader\nbody\nfooter\nf", actual
230
230
  end
231
231
 
232
- def test_setup
233
- actual = false
234
- RendererWithManyHooks.render_text { |r|
235
- actual = r.options.apple
236
- }
237
- assert actual
238
- end
239
-
240
232
  def test_option_helper
241
233
  RendererWithManyHooks.render_text do |r|
242
234
  r.subtitle = "Test Report"
@@ -395,6 +387,69 @@ class TestFormatterErbHelper < Test::Unit::TestCase
395
387
  assert_equal "Binding Override: 321",
396
388
  VanillaRenderer.render_terb(:binding => arr_binding)
397
389
  end
398
- end
390
+ end
399
391
 
400
392
 
393
+ class TestOptionReaders < Test::Unit::TestCase
394
+
395
+ class RendererForCheckingOptionReaders < Ruport::Renderer
396
+ option :foo
397
+ end
398
+
399
+ class RendererForCheckingPassivity < Ruport::Renderer
400
+ def foo
401
+ "apples"
402
+ end
403
+ option :foo
404
+ end
405
+
406
+ def setup
407
+ @renderer = RendererForCheckingOptionReaders.new
408
+ @renderer.formatter = Ruport::Formatter.new
409
+
410
+ @passive = RendererForCheckingPassivity.new
411
+ @passive.formatter = Ruport::Formatter.new
412
+ end
413
+
414
+ def test_options_are_readable
415
+ @renderer.foo = 5
416
+ assert_equal 5, @renderer.foo
417
+ end
418
+
419
+ def test_methods_are_not_overridden
420
+ @passive.foo = 5
421
+ assert_equal "apples", @passive.foo
422
+ assert_equal 5, @passive.options.foo
423
+ assert_equal 5, @passive.formatter.options.foo
424
+ end
425
+
426
+ end
427
+
428
+ class TestSetupOrdering < Test::Unit::TestCase
429
+
430
+ class RendererWithSetup < Ruport::Renderer
431
+ option :foo
432
+ stage :bar
433
+ def setup
434
+ foo.capitalize!
435
+ end
436
+ end
437
+
438
+ class BasicFormatter < Ruport::Formatter
439
+ renders :text, :for => RendererWithSetup
440
+
441
+ def build_bar
442
+ output << options.foo
443
+ end
444
+ end
445
+
446
+ def test_render_hash_options_should_be_called_before_setup
447
+ assert_equal "Hello", RendererWithSetup.render_text(:foo => "hello")
448
+ end
449
+
450
+ def test_render_block_should_be_called_before_setup
451
+ assert_equal "Hello",
452
+ RendererWithSetup.render_text { |r| r.foo = "hello" }
453
+ end
454
+
455
+ end