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 +10 -0
- data/README.txt +38 -10
- data/Rakefile +1 -1
- data/lib/reportbuilder.rb +93 -31
- data/lib/reportbuilder/generator.rb +57 -18
- data/lib/reportbuilder/generator/html.rb +46 -55
- data/lib/reportbuilder/generator/text.rb +7 -8
- data/lib/reportbuilder/image.rb +22 -14
- data/lib/reportbuilder/section.rb +19 -13
- data/lib/reportbuilder/table.rb +49 -42
- data/lib/reportbuilder/table/htmlgenerator.rb +35 -35
- data/lib/reportbuilder/table/textgenerator.rb +16 -14
- data/test/test_html.rb +43 -22
- data/test/test_image.rb +11 -10
- data/test/test_reportbuilder.rb +70 -11
- data/test/test_section.rb +60 -44
- data/test/test_table.rb +52 -20
- metadata +63 -11
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
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
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(
|
13
|
-
name||="Report "+Time.new.to_s
|
14
|
-
|
15
|
-
@
|
16
|
-
@
|
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
|
-
|
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(
|
39
|
-
gen = Generator::Html.new(self
|
99
|
+
def to_html()
|
100
|
+
gen = Generator::Html.new(self,@options)
|
40
101
|
gen.parse
|
41
102
|
gen.out
|
42
103
|
end
|
43
|
-
|
44
|
-
|
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
|
-
|
47
|
-
fp.write(gen.out)
|
48
|
-
}
|
110
|
+
gen.save(file)
|
49
111
|
end
|
50
112
|
# Returns a Text output
|
51
|
-
def to_text(
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
20
|
-
|
20
|
+
# Parse the output. Could be reimplemented on subclasses
|
21
|
+
def parse
|
22
|
+
parse_cycle(@builder)
|
21
23
|
end
|
22
|
-
#
|
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=("
|
33
|
-
if element.
|
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? :
|
36
|
-
element.send(:
|
55
|
+
elsif element.respond_to? :report_building
|
56
|
+
element.send(:report_building, self)
|
37
57
|
else
|
38
|
-
|
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
|
-
|
74
|
+
# Add a paragraph to the report
|
75
|
+
def text(t)
|
43
76
|
raise "Implement this"
|
44
77
|
end
|
45
|
-
|
78
|
+
# Add html code. Only parsed with generator which understand html
|
79
|
+
def html(t)
|
46
80
|
raise "Implement this"
|
47
81
|
end
|
48
|
-
|
82
|
+
# Add preformatted text
|
83
|
+
def preformatted(t)
|
49
84
|
raise "Implement this"
|
50
85
|
end
|
51
|
-
|
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
|
-
|
94
|
+
|
95
|
+
# Add an entry for table index.
|
58
96
|
# Returns the name of the anchor
|
59
|
-
def
|
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
|
15
|
-
|
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
|
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? @
|
95
|
-
FileUtils.mkdir @
|
96
|
-
FileUtils.cp 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
|
92
|
+
|
93
|
+
def css(css)
|
103
94
|
if(File.exists? css)
|
104
|
-
if(!File.exists? @
|
105
|
-
FileUtils.mkdir @
|
106
|
-
FileUtils.cp 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
|
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
|
108
|
+
def html(t)
|
118
109
|
ws=(" "*parse_level*2)
|
119
110
|
@body << ws << t << "\n"
|
120
111
|
end
|
121
|
-
def
|
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
|