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
@@ -9,20 +9,19 @@ class ReportBuilder
|
|
9
9
|
@out=""
|
10
10
|
end
|
11
11
|
def parse
|
12
|
-
@out="
|
12
|
+
@out="#{@builder.name}\n" unless @builder.no_title
|
13
13
|
parse_cycle(@builder)
|
14
|
-
@out << "\n"
|
15
14
|
end
|
16
|
-
def
|
17
|
-
ws=" "*parse_level*2
|
15
|
+
def text(t)
|
16
|
+
ws=" "*((parse_level-1)*2)
|
18
17
|
@out << ws << t << "\n"
|
19
18
|
end
|
20
|
-
def
|
21
|
-
@out << t
|
19
|
+
def preformatted(t)
|
20
|
+
@out << t << "\n"
|
22
21
|
end
|
23
|
-
def
|
22
|
+
def html(t)
|
24
23
|
# Nothing printed
|
25
24
|
end
|
26
25
|
end
|
27
26
|
end
|
28
|
-
end
|
27
|
+
end
|
data/lib/reportbuilder/image.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# Creates
|
1
|
+
# Creates an Image
|
2
|
+
#
|
2
3
|
class ReportBuilder::Image
|
3
4
|
@@n=1
|
4
5
|
attr_reader :name
|
@@ -20,12 +21,12 @@ class ReportBuilder::Image
|
|
20
21
|
@filename=filename
|
21
22
|
end
|
22
23
|
# Based on http://rubyquiz.com/quiz50.html
|
23
|
-
def
|
24
|
+
def report_building_text(generator)
|
24
25
|
require 'RMagick'
|
25
|
-
|
26
|
-
|
26
|
+
|
27
|
+
|
27
28
|
img = Magick::Image.read(@filename).first
|
28
|
-
|
29
|
+
|
29
30
|
# Resize too-large images. The resulting image is going to be
|
30
31
|
# about twice the size of the input, so if the original image is too
|
31
32
|
# large we need to make it smaller so the ASCII version won't be too
|
@@ -38,7 +39,7 @@ class ReportBuilder::Image
|
|
38
39
|
img.change_geometry('320x320>') do |cols, rows|
|
39
40
|
img.resize!(cols, rows) if cols != img.columns || rows != img.rows
|
40
41
|
end
|
41
|
-
|
42
|
+
|
42
43
|
# Compute the image size in ASCII "pixels" and resize the image to have
|
43
44
|
# those dimensions. The resulting image does not have the same aspect
|
44
45
|
# ratio as the original, but since our "pixels" are twice as tall as
|
@@ -46,10 +47,10 @@ class ReportBuilder::Image
|
|
46
47
|
pr = img.rows / @options[:font_rows]
|
47
48
|
pc = img.columns / @options[:font_cols]
|
48
49
|
img.resize!(pc, pr)
|
49
|
-
|
50
|
+
|
50
51
|
img = img.quantize(@options[:chars].size, Magick::GRAYColorspace)
|
51
52
|
img = img.normalize
|
52
|
-
|
53
|
+
|
53
54
|
out=""
|
54
55
|
# Draw the image surrounded by a border. The `view' method is slow but
|
55
56
|
# it makes it easy to address individual pixels. In grayscale images,
|
@@ -62,17 +63,24 @@ class ReportBuilder::Image
|
|
62
63
|
pr.times do |i|
|
63
64
|
out+= '|'
|
64
65
|
pc.times do |j|
|
65
|
-
out+= @options[:chars][view[i][j].red / (2**16/@options[:chars].size)]
|
66
|
+
out+= @options[:chars][view[i][j].red / (2**16/@options[:chars].size)]
|
66
67
|
end
|
67
68
|
out+= '|'+"\n"
|
68
69
|
end
|
69
70
|
end
|
70
71
|
out+= border
|
71
|
-
generator.
|
72
|
+
generator.preformatted(out)
|
72
73
|
end
|
73
|
-
|
74
|
-
def
|
75
|
-
|
76
|
-
|
74
|
+
|
75
|
+
def report_building_html(generator)
|
76
|
+
basedir=generator.directory+"/images"
|
77
|
+
out=basedir+"/"+File.basename(@filename)
|
78
|
+
if(File.exists? @filename)
|
79
|
+
if !File.exists? out
|
80
|
+
FileUtils.mkdir_p basedir
|
81
|
+
FileUtils.cp @filename, out
|
82
|
+
end
|
83
|
+
end
|
84
|
+
generator.html("<img src='images/#{File.basename(@filename)}' alt='#{@options[:alt]}' />")
|
77
85
|
end
|
78
86
|
end
|
@@ -1,16 +1,22 @@
|
|
1
|
-
# Creates a Section
|
1
|
+
# Creates a Section.
|
2
|
+
# A section have a name and contains other elements.
|
3
|
+
# Sections could be nested inside anothers
|
4
|
+
|
2
5
|
class ReportBuilder::Section
|
3
6
|
@@n=1
|
4
7
|
attr_reader :parent, :elements, :name
|
5
|
-
def initialize(options=
|
8
|
+
def initialize(options=Hash.new, &block)
|
6
9
|
if !options.has_key? :name
|
7
|
-
@name="Section #{
|
8
|
-
|
10
|
+
@name="Section #{@@n}"
|
11
|
+
@@n+=1
|
9
12
|
else
|
10
13
|
@name=options[:name]
|
11
14
|
end
|
12
15
|
@parent = nil
|
13
16
|
@elements = []
|
17
|
+
if block
|
18
|
+
add(block)
|
19
|
+
end
|
14
20
|
end
|
15
21
|
def parent=(sect)
|
16
22
|
if sect.is_a? ReportBuilder::Section
|
@@ -19,20 +25,20 @@ class ReportBuilder::Section
|
|
19
25
|
raise ArgumentError("Parent should be a Section")
|
20
26
|
end
|
21
27
|
end
|
22
|
-
|
23
|
-
def
|
24
|
-
generator.
|
28
|
+
|
29
|
+
def report_building_text(generator)
|
30
|
+
generator.text(("="*generator.parse_level)+" "+name)
|
25
31
|
generator.parse_cycle(self)
|
26
32
|
end
|
27
|
-
|
28
|
-
def
|
33
|
+
|
34
|
+
def report_building_html(generator)
|
29
35
|
htag="h#{generator.parse_level+1}"
|
30
|
-
anchor=generator.
|
31
|
-
generator.
|
36
|
+
anchor=generator.toc_entry(name)
|
37
|
+
generator.html "<div class='section'><#{htag}>#{name}</#{htag}><a name='#{anchor}'></a>"
|
32
38
|
generator.parse_cycle(self)
|
33
|
-
generator.
|
39
|
+
generator.html "</div>"
|
34
40
|
end
|
35
|
-
|
41
|
+
|
36
42
|
def add(element)
|
37
43
|
if element.is_a? ReportBuilder::Section
|
38
44
|
element.parent=self
|
data/lib/reportbuilder/table.rb
CHANGED
@@ -2,11 +2,11 @@ class ReportBuilder
|
|
2
2
|
# Creates a table.
|
3
3
|
# Use:
|
4
4
|
# table=ReportBuilder::Table.new(:header =>["id","city","name","code1","code2"])
|
5
|
-
# table.
|
6
|
-
# table.
|
7
|
-
# table.
|
8
|
-
# table.
|
9
|
-
# table.
|
5
|
+
# table.row([1, Table.rowspan("New York",3), "Ringo", Table.colspan("no code",2),nil])
|
6
|
+
# table.row([2, nil,"John", "ab-1","ab-2"])
|
7
|
+
# table.row([3, nil,"Paul", "ab-3"])
|
8
|
+
# table.hr
|
9
|
+
# table.row([4, "London","George", Table.colspan("ab-4",2),nil])
|
10
10
|
# puts table
|
11
11
|
# ==>
|
12
12
|
# -----------------------------------------
|
@@ -20,7 +20,7 @@ class ReportBuilder
|
|
20
20
|
# -----------------------------------------
|
21
21
|
class Table
|
22
22
|
@@n=1 # :nodoc:
|
23
|
-
|
23
|
+
|
24
24
|
DEFAULT_OPTIONS={
|
25
25
|
:header => [],
|
26
26
|
:name => nil
|
@@ -35,7 +35,7 @@ class ReportBuilder
|
|
35
35
|
# Options: :name, :header
|
36
36
|
# Use:
|
37
37
|
# table=ReportBuilder::Table.new(:header =>["var1","var2"])
|
38
|
-
def initialize(opts=Hash.new)
|
38
|
+
def initialize(opts=Hash.new, &block)
|
39
39
|
raise ArgumentError,"opts should be a Hash" if !opts.is_a? Hash
|
40
40
|
opts=DEFAULT_OPTIONS.merge opts
|
41
41
|
if opts[:name].nil?
|
@@ -47,18 +47,21 @@ class ReportBuilder
|
|
47
47
|
@header=opts[:header]
|
48
48
|
@rows=[]
|
49
49
|
@max_cols=[]
|
50
|
+
if block
|
51
|
+
block.arity<1 ? self.instance_eval(&block) : block.call(self)
|
52
|
+
end
|
50
53
|
end
|
51
54
|
# Adds a row
|
52
55
|
# table.add_row(%w{1 2})
|
53
|
-
def
|
56
|
+
def row(row)
|
54
57
|
@rows.push(row)
|
55
58
|
end
|
56
59
|
# Adds a horizontal rule
|
57
60
|
# table.add_hr
|
58
|
-
def
|
61
|
+
def hr
|
59
62
|
@rows.push(:hr)
|
60
63
|
end
|
61
|
-
alias_method :
|
64
|
+
alias_method :horizontal_line, :hr
|
62
65
|
# Adds a rowspan on a cell
|
63
66
|
# table.add_row(["a",table.rowspan("b",2)])
|
64
67
|
def rowspan(data,n)
|
@@ -66,72 +69,76 @@ class ReportBuilder
|
|
66
69
|
end
|
67
70
|
# Adds a colspan on a cell
|
68
71
|
# table.add_row(["a",table.colspan("b",2)])
|
69
|
-
|
72
|
+
|
70
73
|
def colspan(data,n)
|
71
74
|
Colspan.new(data,n)
|
72
75
|
end
|
73
76
|
def calculate_widths # :nodoc:
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
@max_cols=[]
|
78
|
+
rows_cal=[header]+@rows
|
79
|
+
rows_cal.each{|row|
|
80
|
+
next if row==:hr
|
81
|
+
row.each_index{|i|
|
79
82
|
if row[i].nil?
|
80
|
-
|
83
|
+
next
|
81
84
|
elsif row[i].is_a? Colspan
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
size_total=row[i].data.to_s.size
|
86
|
+
size_per_column=(size_total / row[i].cols)+1
|
87
|
+
for mi in i...i+row[i].cols
|
88
|
+
@max_cols[mi] = size_per_column if @max_cols[mi].nil? or @max_cols[mi]<size_per_column
|
89
|
+
end
|
87
90
|
elsif row[i].is_a? Rowspan
|
88
|
-
|
89
|
-
|
91
|
+
size=row[i].data.to_s.size
|
92
|
+
@max_cols[i]= size if @max_cols[i].nil? or @max_cols[i] < size
|
90
93
|
else
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
+
|
95
|
+
size=row[i].to_s.size
|
96
|
+
@max_cols[i]= size if @max_cols[i].nil? or @max_cols[i] < size
|
94
97
|
end
|
98
|
+
}
|
95
99
|
}
|
96
|
-
}
|
97
100
|
end
|
98
|
-
def
|
101
|
+
def report_building_text(generator)
|
99
102
|
require 'reportbuilder/table/textgenerator'
|
100
103
|
table_generator=ReportBuilder::Table::TextGenerator.new( generator, self)
|
101
104
|
table_generator.generate
|
102
105
|
end
|
103
|
-
def
|
106
|
+
def report_building_html(generator)
|
104
107
|
require 'reportbuilder/table/htmlgenerator'
|
105
108
|
table_generator=ReportBuilder::Table::HtmlGenerator.new(generator, self)
|
106
109
|
table_generator.generate
|
107
110
|
end
|
108
111
|
|
109
112
|
def total_width # :nodoc:
|
110
|
-
@max_cols.
|
113
|
+
if @max_cols.size>0
|
114
|
+
@max_cols.inject(0){|a,v| a+(v+3)}+1
|
115
|
+
else
|
116
|
+
0
|
117
|
+
end
|
111
118
|
end
|
112
|
-
######################
|
113
|
-
# INTERNAL CLASSES #
|
114
|
-
######################
|
115
|
-
|
119
|
+
######################
|
120
|
+
# INTERNAL CLASSES #
|
121
|
+
######################
|
122
|
+
|
116
123
|
class Rowspan # :nodoc:
|
117
124
|
attr_accessor :data, :rows
|
118
125
|
def initialize(data,rows)
|
119
|
-
|
120
|
-
|
126
|
+
@data=data
|
127
|
+
@rows=rows
|
121
128
|
end
|
122
129
|
def to_s
|
123
|
-
|
130
|
+
@data.to_s
|
124
131
|
end
|
125
132
|
end
|
126
|
-
|
133
|
+
|
127
134
|
class Colspan # :nodoc:
|
128
135
|
attr_accessor :data, :cols
|
129
136
|
def initialize(data,cols)
|
130
|
-
|
131
|
-
|
137
|
+
@data=data
|
138
|
+
@cols=cols
|
132
139
|
end
|
133
140
|
def to_s
|
134
|
-
|
141
|
+
@data.to_s
|
135
142
|
end
|
136
143
|
end
|
137
144
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
class ReportBuilder
|
2
2
|
class Table
|
3
3
|
class HtmlGenerator < ElementGenerator
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
def generate()
|
5
|
+
t=@element
|
6
|
+
anchor=@generator.table_entry(t.name)
|
7
|
+
out="<a name='#{anchor}'></a><table><caption>#{t.name}</caption>"
|
8
|
+
@rowspans=[]
|
9
|
+
if t.header.size>0
|
10
|
+
out+="<thead>"+parse_row(t,t.header,"th")+"</thead>\n"
|
11
|
+
end
|
12
|
+
out+="<tbody>\n"
|
13
|
+
next_class=""
|
14
|
+
t.rows.each{|row|
|
15
15
|
if row==:hr
|
16
16
|
next_class="top"
|
17
17
|
else
|
@@ -19,32 +19,32 @@ class ReportBuilder
|
|
19
19
|
out+="<tr#{class_tag}>"+parse_row(t,row)+"</tr>\n"
|
20
20
|
next_class=""
|
21
21
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
22
|
+
}
|
23
|
+
out+="</tbody>\n</table>\n"
|
24
|
+
@generator.html(out)
|
25
|
+
end
|
26
|
+
def parse_row(t,row,tag="td")
|
27
|
+
row_ary=[]
|
28
|
+
colspan_i=0
|
29
|
+
row.each_index do |i|
|
30
|
+
if !@rowspans[i].nil? and @rowspans[i]>0
|
31
|
+
@rowspans[i]-=1
|
32
|
+
elsif colspan_i>0
|
33
|
+
colspan_i-=1
|
34
|
+
elsif row[i].is_a? Table::Colspan
|
35
|
+
row_ary.push(sprintf("<%s colspan=\"%d\">%s</%s>",tag, row[i].cols, row[i].data,tag))
|
36
|
+
colspan_i=row[i].cols-1
|
37
|
+
elsif row[i].nil?
|
38
|
+
row_ary.push("<#{tag}></#{tag}>")
|
39
|
+
elsif row[i].is_a? Table::Rowspan
|
40
|
+
row_ary.push(sprintf("<%s rowspan=\"%d\">%s</%s>", tag, row[i].rows, row[i].data, tag))
|
41
|
+
@rowspans[i]=row[i].rows-1
|
42
|
+
else
|
43
|
+
row_ary.push("<#{tag}>#{row[i]}</#{tag}>")
|
45
44
|
end
|
46
|
-
row_ary.join("")
|
47
45
|
end
|
46
|
+
row_ary.join("")
|
47
|
+
end
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -1,26 +1,28 @@
|
|
1
1
|
class ReportBuilder
|
2
2
|
class Table
|
3
3
|
class TextGenerator < ElementGenerator
|
4
|
-
|
4
|
+
|
5
5
|
def generate()
|
6
6
|
t=@element
|
7
7
|
t.calculate_widths
|
8
8
|
total_width=t.total_width
|
9
|
-
out="
|
10
|
-
if
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
t.rows.each do |row|
|
16
|
-
if row==:hr
|
9
|
+
out="#{t.name}\n"
|
10
|
+
if total_width>0
|
11
|
+
if t.header.size>0
|
12
|
+
out+=parse_hr(total_width)+"\n"
|
13
|
+
out+=parse_row(t,t.header)+"\n"
|
17
14
|
out+=parse_hr(total_width)+"\n"
|
18
|
-
else
|
19
|
-
out+=parse_row(t,row)+"\n"
|
20
15
|
end
|
16
|
+
t.rows.each do |row|
|
17
|
+
if row==:hr
|
18
|
+
out+=parse_hr(total_width)+"\n"
|
19
|
+
else
|
20
|
+
out+=parse_row(t,row)+"\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
out+=parse_hr(total_width)+"\n"
|
21
24
|
end
|
22
|
-
out
|
23
|
-
@generator.add_text(out)
|
25
|
+
@generator.text(out)
|
24
26
|
end
|
25
27
|
# Parse a row
|
26
28
|
def parse_row(t,row)
|
@@ -48,6 +50,6 @@ class ReportBuilder
|
|
48
50
|
def parse_hr(l)
|
49
51
|
"-"*l
|
50
52
|
end
|
51
|
-
end
|
52
53
|
end
|
54
|
+
end
|
53
55
|
end
|