reportbuilder 0.2.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.
data/History.txt CHANGED
@@ -1,3 +1,13 @@
1
+ === 1.0.0 / 2009-03-22
2
+ Change of API:
3
+ * Deleted "add_" before methods
4
+ * Massive use of block after functions, to allow easy creation of reports
5
+ For example, you can create
6
+ ReportBuilder.generate(:name=>"Report 1", :format=>:html) do
7
+ text("A paragraph")
8
+ preformatted("a pre statement")
9
+
10
+ end
1
11
  === 1.0.0 / 2009-08-12
2
12
 
3
13
  * 1 major enhancement
data/README.txt CHANGED
@@ -4,26 +4,54 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Report Abstract Interface. Creates text, html and pdf output, based on a common framework
7
+ Report Abstract Interface. Creates text and html output, based on a common framework.
8
8
 
9
9
  == FEATURES
10
10
 
11
11
  * One interface, multiple outputs
12
+ * You have two interfaces:
13
+ * Generic, based on adding objects to a ReportBuilder object
14
+ * Fine tuning, directly operating on ReportBuilder::Generator interface
12
15
 
13
16
  == SYNOPSIS:
14
17
 
15
- rb=ReportBuilder.new
16
- rb.add("This is a text")
17
- table=rb.table(%w{id name})
18
- table.add_row([1,"Nombre"])
19
- table.add_hr
20
- rb.add(table)
21
- puts rb.to_text
22
- puts rb.to_html
23
- puts rb.to_pdf
18
+ * Using generic ReportBuilder#add, every object will be parsed
19
+ using #report_building_FORMAT, #report_building or #to_s
20
+
21
+ require "reportbuilder"
22
+ rb=ReportBuilder.new
23
+ rb.add(2) # Int#to_s used
24
+ table=ReportBuilder::Table.new(:name=>"Table", :header=>%w{id name})
25
+ table.row([1,"John"])
26
+ rb.add(table) # table have a #report_building method
27
+ rb.add("Another text") # used directly
28
+ rb.name="Text output"
29
+ puts rb.to_text
30
+
31
+ * Using a block, you can control directly the generator
32
+
33
+ require "reportbuilder"
34
+ rb=ReportBuilder.new do
35
+ text("2")
36
+ section(:name=>"Section 1") do
37
+ table(:name=>"Table", :header=>%w{id name}) do
38
+ row([1,"John"])
39
+ end
40
+ end
41
+ preformatted("Another Text")
42
+ end
43
+ rb.name="Html output"
44
+ puts rb.to_html
45
+
46
+ == DEVELOPERS
47
+
48
+ If you want to give support to your class, create a method called #report_building(g), which accept a ReportBuilder::Generator as argument. If you need fine control of output according to format, append the name of format, like #report_building_html, #report_building_text.
49
+
50
+ See ReportBuilder::Generator for API and ReportBuilder::Table, ReportBuilder::Image and ReportBuilder::Section for examples of implementation. Also, Statsample package object uses report_building on almost every class.
24
51
 
25
52
  == REQUIREMENTS:
26
53
 
54
+ * RMagick, only to generate text output of images (see examples/image.rb)
27
55
 
28
56
  == INSTALL:
29
57
 
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ Hoe.spec 'reportbuilder' do
11
11
  self.rubyforge_name = 'ruby-statsample'
12
12
  self.developer('Claudio Bustos', 'clbustos_at_gmail.com')
13
13
  self.url = "http://rubyforge.org/projects/ruby-statsample/"
14
-
14
+ self.extra_dev_deps << ["hpricot", "~>0.8"]
15
15
  end
16
16
 
17
17
  # vim: syntax=ruby
data/lib/reportbuilder.rb CHANGED
@@ -2,55 +2,117 @@ require 'reportbuilder/table'
2
2
  require 'reportbuilder/section'
3
3
  require 'reportbuilder/generator'
4
4
  require 'reportbuilder/image'
5
-
5
+ # = Report Abstract Interface.
6
+ # Creates text and html output, based on a common framework.
7
+ # == Use
8
+ #
9
+ #
10
+ # * Using generic ReportBuilder#add, every object will be parsed using #report_building_FORMAT, #report_building or #to_s
11
+ #
12
+ # require "reportbuilder"
13
+ # rb=ReportBuilder.new
14
+ # rb.add(2) # Int#to_s used
15
+ # section=ReportBuilder::Section.new(:name=>"Section 1")
16
+ # table=ReportBuilder::Table.new(:name=>"Table", :header=>%w{id name})
17
+ # table.row([1,"John"])
18
+ # table.hr
19
+ # table.row([2,"Peter"])
20
+ #
21
+ # section.add(table) # Section is a container for other methods
22
+ # rb.add(section) # table have a #report_building method
23
+ # rb.add("Another text") # used directly
24
+ # rb.name="Text output"
25
+ # puts rb.to_text
26
+ # rb.name="Html output"
27
+ # puts rb.to_html
28
+ #
29
+ # * Using a block, you can control directly the generator
30
+ #
31
+ # require "reportbuilder"
32
+ # rb=ReportBuilder.new do
33
+ # text("2")
34
+ # section(:name=>"Section 1") do
35
+ # table(:name=>"Table", :header=>%w{id name}) do
36
+ # row([1,"John"])
37
+ # hr
38
+ # row([2,"Peter"])
39
+ # end
40
+ # end
41
+ # preformatted("Another Text")
42
+ # end
43
+ # rb.name="Text output"
44
+ # puts rb.to_text
45
+ # rb.name="Html output"
46
+ # puts rb.to_html
6
47
  class ReportBuilder
7
48
  attr_reader :elements
8
- attr_reader :name
9
- attr_reader :dir
10
- VERSION = '0.2.0'
49
+ # Name of report
50
+ attr_accessor :name
51
+ # Doesn't print a title if set to true
52
+ attr_accessor :no_title
53
+
54
+ VERSION = '1.0.0'
55
+ # Generates and optionally save the report on one function
56
+ def self.generate(options=Hash.new, &block)
57
+ options[:filename]||=nil
58
+ options[:format]||="text"
59
+
60
+ if options[:filename] and options[:filename]=~/\.(\w+?)$/
61
+ options[:format]=$1
62
+ options[:format]="text" if options[:format]=="txt"
63
+ end
64
+ file=options.delete(:filename)
65
+ format=options.delete(:format).to_s
66
+ format[0]=format[0,1].upcase
67
+
68
+
69
+ rb=ReportBuilder.new(options)
70
+ rb.add(block)
71
+ generator=Generator.const_get(format.to_sym).new(rb, options)
72
+ generator.parse
73
+ out=generator.out
74
+ unless file.nil?
75
+ File.open(file,"wb") do |fp|
76
+ fp.write out
77
+ end
78
+ else
79
+ out
80
+ end
81
+ end
11
82
  # Create a new Report
12
- def initialize(name=nil,dir=nil)
13
- name||="Report "+Time.new.to_s
14
- dir||=Dir.pwd
15
- @dir=dir
16
- @name=name
83
+ def initialize(options=Hash.new,&block)
84
+ options[:name]||="Report "+Time.new.to_s
85
+ @no_title=options.delete :no_title
86
+ @name=options.delete :name
87
+ @options=options
17
88
  @elements=Array.new
89
+ add(block) if block
18
90
  end
19
91
  # Add an element to the report.
20
92
  # If parameters is an object which respond to :to_reportbuilder,
21
93
  # this method will called.
22
94
  # Otherwise, the element itself will be added
23
95
  def add(element)
24
- @elements.push(element)
25
- end
26
- # Returns a Section
27
- def section(options={})
28
- Section.new(options)
29
- end
30
- def image(filename)
31
- Image.new(filename)
32
- end
33
- # Returns a Table
34
- def table(h=[])
35
- Table.new(h)
96
+ @elements.push(element)
36
97
  end
37
98
  # Returns an Html output
38
- def to_html(options={})
39
- gen = Generator::Html.new(self,options)
99
+ def to_html()
100
+ gen = Generator::Html.new(self,@options)
40
101
  gen.parse
41
102
  gen.out
42
103
  end
43
- def save_html(file, options={})
44
- gen=Generator::Html.new(self,options)
104
+ # Save an html file
105
+ def save_html(file)
106
+ options=@options.dup
107
+ options[:directory]=File.dirname(file)
108
+ gen=Generator::Html.new(self, options)
45
109
  gen.parse
46
- File.open(@dir+"/"+file,"wb") {|fp|
47
- fp.write(gen.out)
48
- }
110
+ gen.save(file)
49
111
  end
50
112
  # Returns a Text output
51
- def to_text(options={})
52
- gen=Generator::Text.new(self, options)
53
- gen.parse
113
+ def to_text()
114
+ gen=Generator::Text.new(self, @options)
115
+ gen.parse
54
116
  gen.out
55
117
  end
56
118
  alias_method :to_s, :to_text
@@ -1,11 +1,12 @@
1
1
  class ReportBuilder
2
2
  # Abstract class for generators.
3
- # A generator is a class which control the output for a builder.
4
- # On parse_cycle()....
3
+ # A generator is a class which control the output for a ReportBuilder object
5
4
  #
6
5
  class Generator
6
+ # Level of heading. See ReportBuilder::Section for using it.
7
7
  attr_reader :parse_level
8
- # builder is a ReportBuilder object
8
+ # Options for Generator. Passed by ReportBuilder class on creation
9
+ attr_reader :options
9
10
  def initialize(builder, options)
10
11
  @builder=builder
11
12
  @parse_level=0
@@ -16,53 +17,91 @@ class ReportBuilder
16
17
  @list_tables=[]
17
18
 
18
19
  end
19
- def default_options
20
- {}
20
+ # Parse the output. Could be reimplemented on subclasses
21
+ def parse
22
+ parse_cycle(@builder)
21
23
  end
22
- # parse each element of the parameters
24
+ # Save the output of generator to a file
25
+ def save(filename)
26
+ File.open(filename, "wb") do |fp|
27
+ fp.write(out)
28
+ end
29
+ end
30
+
31
+ def default_options # :nodoc:
32
+ Hash.new
33
+ end
34
+
35
+ # Parse each element of the container
23
36
  def parse_cycle(container)
24
37
  @parse_level+=1
25
38
  container.elements.each do |element|
26
39
  parse_element(element)
27
40
  end
28
41
  @parse_level-=1
29
- end
42
+ end
30
43
 
44
+ # Parse one object, using this workflow
45
+ # * If is a block, evaluate it
46
+ # * Use #report_building_FORMAT
47
+ # * Use #report_building
48
+ # * Use #to_s
31
49
  def parse_element(element)
32
- method=("to_reportbuilder_"+self.class::PREFIX).intern
33
- if element.respond_to? method
50
+ method=("report_building_" + self.class::PREFIX).intern
51
+ if element.is_a? Proc
52
+ element.arity==0 ? instance_eval(&element) : element.call(self)
53
+ elsif element.respond_to? method
34
54
  element.send(method, self)
35
- elsif element.respond_to? :to_reportbuilder
36
- element.send(:to_reportbuilder, self)
55
+ elsif element.respond_to? :report_building
56
+ element.send(:report_building, self)
37
57
  else
38
- add_text(element.to_s)
58
+ text(element.to_s)
39
59
  end
40
60
  end
61
+ # Create and parse a table. Use a block to control the table
62
+ def table(opt=Hash.new, &block)
63
+ parse_element(ReportBuilder::Table.new(opt,&block))
64
+ end
65
+ # Create and parse an image.
66
+ def image(filename,opt=Hash.new)
67
+ parse_element(ReportBuilder::Image.new(filename,opt))
68
+ end
69
+ # Create and parse an image. Use a block to insert element inside the block
70
+ def section(opt=Hash.new, &block)
71
+ parse_element(ReportBuilder::Section.new(opt,&block))
72
+ end
41
73
 
42
- def add_text(t)
74
+ # Add a paragraph to the report
75
+ def text(t)
43
76
  raise "Implement this"
44
77
  end
45
- def add_html(t)
78
+ # Add html code. Only parsed with generator which understand html
79
+ def html(t)
46
80
  raise "Implement this"
47
81
  end
48
- def add_preformatted(t)
82
+ # Add preformatted text
83
+ def preformatted(t)
49
84
  raise "Implement this"
50
85
  end
51
- def add_toc_entry(name)
86
+ # Add a TOC (Table of Contents) entry
87
+ # Return the name of anchor
88
+ def toc_entry(name)
52
89
  anchor="toc_#{@entry_n}"
53
90
  @entry_n+=1
54
91
  @toc.push([anchor, name, parse_level])
55
92
  anchor
56
93
  end
57
- # Add an entry for a table
94
+
95
+ # Add an entry for table index.
58
96
  # Returns the name of the anchor
59
- def add_table_entry(name)
97
+ def table_entry(name)
60
98
  anchor="table_#{@table_n}"
61
99
  @table_n+=1
62
100
  @list_tables.push([anchor,name])
63
101
  anchor
64
102
  end
65
103
  end
104
+
66
105
  class ElementGenerator
67
106
  def initialize(generator,element)
68
107
  @element=element
@@ -1,40 +1,39 @@
1
1
  require 'fileutils'
2
2
  class ReportBuilder
3
3
  class Generator
4
-
5
4
  class Html < Generator
6
5
  PREFIX="html"
7
6
  attr_reader :toc
7
+ attr_reader :directory
8
8
  def initialize(builder, options)
9
9
  super
10
+ @directory = @options.delete :directory
10
11
  @body=""
11
12
  @headers=[]
12
13
  @footers=[]
13
14
  end
14
- def parse
15
- # add_css(File.dirname(__FILE__)+"/../../../data/reportbuilder.css")
16
- # add_js(File.dirname(__FILE__)+"/../../../data/reportbuilder.js")
17
- parse_cycle(@builder)
15
+ def default_options
16
+ {:directory => Dir.pwd}
18
17
  end
19
18
  def basic_css
20
- <<-HERE
21
- <style>
22
- body {
23
- margin:0;
24
- padding:1em;
25
- }
26
- table {
27
- border-collapse: collapse;
19
+ <<-HERE
20
+ <style>
21
+ body {
22
+ margin:0;
23
+ padding:1em;
24
+ }
25
+ table {
26
+ border-collapse: collapse;
28
27
 
29
- }
30
- table td {
31
- border: 1px solid black;
32
- }
33
- .section {
34
- margin:0.5em;
35
- }
36
- </style>
37
- HERE
28
+ }
29
+ table td {
30
+ border: 1px solid black;
31
+ }
32
+ .section {
33
+ margin:0.5em;
34
+ }
35
+ </style>
36
+ HERE
38
37
  end
39
38
  def out
40
39
  out= <<-HERE
@@ -44,14 +43,16 @@ HERE
44
43
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
45
44
  <title>#{@builder.name}</title>
46
45
  #{basic_css}
47
- HERE
46
+ HERE
48
47
  out << @headers.join("\n")
49
48
  out << "</head><body>\n"
50
49
  out << "<h1>#{@builder.name}</h1>"
51
- if(@toc.size>0)
52
- out << "<div class='toc'><div class='title'>List of contents</div></div>"
50
+ if(@toc.size>0)
51
+ out << "<div id='toc'><div class='title'>List of contents</div>\n"
53
52
  actual_level=0
53
+
54
54
  @toc.each do |anchor,name,level|
55
+
55
56
  if actual_level!=level
56
57
  if actual_level > level
57
58
  (actual_level-level).times { out << "</ul>\n"}
@@ -59,71 +60,61 @@ HERE
59
60
  (level-actual_level).times { out << "<ul>\n"}
60
61
  end
61
62
  end
63
+
62
64
  out << "<li><a href='##{anchor}'>#{name}</a></li>\n"
63
65
  actual_level=level
64
66
  end
65
67
  actual_level.times { out << "</ul>\n"}
66
68
  out << "</div>\n"
67
69
  end
68
- if (@list_tables.size>0)
70
+ if (@list_tables.size>0)
69
71
  out << "<div class='tot'><div class='title'>List of tables</div><ul>"
70
72
  @list_tables.each {|anchor,name|
71
73
  out << "<li><a href='#"+anchor+"'>#{name}</a></li>"
72
74
  }
73
75
  out << "</ul></div>\n"
74
76
  end
75
-
77
+
76
78
  out << @body
77
79
  out << @footers.join("\n")
78
80
  out << "</body></html>"
81
+ out
79
82
  end
80
-
81
-
82
- def add_image(filename)
83
- if(File.exists? filename)
84
- if(!File.exists? @builder.dir+"/images/"+File.basename(filename))
85
- FileUtils.mkdir @builder.dir+"/images"
86
- FileUtils.cp filename, @builder.dir+"/images/"+File.basename(filename)
87
- end
88
- end
89
- "images/"+File.basename(filename)
90
- end
91
-
92
- def add_js(js)
83
+ def js(js)
93
84
  if(File.exists? js)
94
- if(!File.exists? @builder.dir+"/js/"+File.basename(js))
95
- FileUtils.mkdir @builder.dir+"/js"
96
- FileUtils.cp js,@builder.dir+"/js/"+File.basename(js)
85
+ if(!File.exists? @directory+"/js/"+File.basename(js))
86
+ FileUtils.mkdir @directory+"/js"
87
+ FileUtils.cp js,@directory+"/js/"+File.basename(js)
97
88
  end
98
89
  @headers.push("<script type='text/javascript' src='js/#{File.basename(js)}'></script>")
99
90
  end
100
91
  end
101
-
102
- def add_css(css)
92
+
93
+ def css(css)
103
94
  if(File.exists? css)
104
- if(!File.exists? @builder.dir+"/css/"+File.basename(css))
105
- FileUtils.mkdir @builder.dir+"/css"
106
- FileUtils.cp css,@builder.dir+"/css/"+File.basename(css)
95
+ if(!File.exists? @directory+"/css/"+File.basename(css))
96
+ FileUtils.mkdir @directory+"/css"
97
+ FileUtils.cp css, @directory+"/css/"+File.basename(css)
107
98
  end
108
99
  @headers.push("<link rel='stylesheet' type='text/css' href='css/#{File.basename(css)}' />")
109
100
  end
110
101
  end
111
-
112
-
113
- def add_text(t)
102
+
103
+
104
+ def text(t)
114
105
  ws=(" "*parse_level*2)
115
106
  @body << ws << "<p>#{t}</p>\n"
116
107
  end
117
- def add_html(t)
108
+ def html(t)
118
109
  ws=(" "*parse_level*2)
119
110
  @body << ws << t << "\n"
120
111
  end
121
- def add_preformatted(t)
112
+ def preformatted(t)
122
113
  ws=(" "*parse_level*2)
123
114
  @body << ws << "<pre>#{t}</pre>\n"
124
-
115
+
125
116
  end
126
-
117
+
127
118
  end
128
119
  end
129
120
  end