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 +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
|