reportbuilder 0.2.0 → 1.0.0

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