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