ruport 0.4.19 → 0.4.21

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/CHANGELOG CHANGED
@@ -1,6 +1,23 @@
1
- The current version of Ruby Reports is 0.4.19
1
+ The current version of Ruby Reports is 0.4.21
2
2
 
3
- changes since 0.4.1
3
+ changes since 0.4.19
4
+ - Added a rake task to run rcov
5
+
6
+ - Removed DataSet / DataRow. Farewell good friends
7
+
8
+ - Added Array#to_table, dropped Array#to_ds
9
+
10
+ - You can now convert Sets to Tables and vice-versa
11
+
12
+ - acts_as_reportable converted to use Data::Table
13
+
14
+ - Data::Record's constructor can now take a hash
15
+
16
+ - Added initial Data::Set implementation
17
+
18
+ - Added smart Data::Table.to_format method_missing functionality
19
+
20
+ changes since 0.4.17
4
21
 
5
22
  - Made method_missing a lot more friendly all around.
6
23
 
data/Rakefile CHANGED
@@ -12,8 +12,7 @@ end
12
12
  LEAN=false
13
13
  dir = File.dirname(__FILE__)
14
14
  lib = File.join(dir, "lib", "ruport.rb")
15
- version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d+\.\d+\.d+)\1/,2]
16
-
15
+ version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d+\.\d+\.d+)['"]/,1]
17
16
  task :default => [:test]
18
17
 
19
18
  Rake::TestTask.new do |test|
@@ -24,7 +23,7 @@ end
24
23
 
25
24
  spec = Gem::Specification.new do |spec|
26
25
  spec.name = LEAN ? "lean-ruport" : "ruport"
27
- spec.version = "0.4.19"
26
+ spec.version = "0.4.21"
28
27
  spec.platform = Gem::Platform::RUBY
29
28
  spec.summary = "A generalized Ruby report generation and templating engine."
30
29
  spec.files = Dir.glob("{examples,lib,test}/**/**/*") +
@@ -66,10 +65,16 @@ Rake::RDocTask.new do |rdoc|
66
65
  rdoc.title = "Ruport Documentation"
67
66
  end
68
67
 
69
-
70
68
  Rake::GemPackageTask.new(spec) do |pkg|
71
69
  pkg.need_zip = true
72
70
  pkg.need_tar = true
73
71
  end
74
72
 
75
-
73
+ begin
74
+ require 'rcov/rcovtask'
75
+ Rcov::RcovTask.new do |t|
76
+ t.test_files = [ "test/ts_all.rb" ]
77
+ end
78
+ rescue LoadError
79
+ nil
80
+ end
@@ -11,11 +11,11 @@ class Format::Plugin::Text < Ruport::Format::Plugin
11
11
  end
12
12
 
13
13
  format_field_names do
14
- data.fields.join("---") << "\n"
14
+ data.column_names.join("---") << "\n"
15
15
  end
16
16
 
17
17
  register_on :table_engine
18
18
  end
19
19
 
20
- puts Format.table({ :data => [[1,2],[3,4]].to_ds(%w[a b]),
20
+ puts Format.table({ :data => [[1,2],[3,4]].to_table(:column_names =>%w[a b]),
21
21
  :plugin => :text })
data/lib/ruport.rb CHANGED
@@ -14,7 +14,7 @@ module Ruport
14
14
 
15
15
  #begin; require 'rubygems'; rescue LoadError; nil end
16
16
 
17
- VERSION = "0.4.19"
17
+ VERSION = "0.4.21"
18
18
 
19
19
  # Ruports logging and error interface.
20
20
  # Can generate warnings or raise fatal errors
@@ -67,6 +67,6 @@ module Ruport
67
67
  end
68
68
 
69
69
 
70
- %w[config meta_tools report format query data_row data_set data].each { |lib|
70
+ %w[config meta_tools report format query data].each { |lib|
71
71
  require "ruport/#{lib}"
72
72
  }
data/lib/ruport/data.rb CHANGED
@@ -1 +1 @@
1
- %w[taggable record collection table].each { |l| require "ruport/data/#{l}" }
1
+ %w[taggable record collection table set].each { |l| require "ruport/data/#{l}" }
@@ -12,7 +12,15 @@ module Ruport::Data
12
12
  def as(type)
13
13
  Ruport::Format.table :data => self, :plugin => type
14
14
  end
15
+
16
+ def to_set
17
+ Set.new :data => data
18
+ end
15
19
 
20
+ def to_table(options={})
21
+ Table.new({:data => data.map { |r| r.to_a }}.merge(options))
22
+ end
23
+
16
24
  attr_reader :data
17
25
  def_delegators :@data, :each, :length, :[], :empty?
18
26
  end
@@ -6,8 +6,17 @@ module Ruport::Data
6
6
  include Taggable
7
7
 
8
8
  def initialize(data,options={})
9
- @data = data.dup
10
- @attributes = options[:attributes]
9
+ if data.kind_of?(Hash)
10
+ if options[:attributes]
11
+ @attributes = options[:attributes]
12
+ @data = options[:attributes].map { |k| data[k] }
13
+ else
14
+ @attributes, @data = data.to_a.transpose
15
+ end
16
+ else
17
+ @data = data.dup
18
+ @attributes = options[:attributes]
19
+ end
11
20
  end
12
21
 
13
22
  attr_reader :data
@@ -67,6 +76,10 @@ module Ruport::Data
67
76
  #FIXME: This does not take into account frozen / tainted state
68
77
  alias_method :clone, :dup
69
78
 
79
+ def hash
80
+ (attributes.to_a + data.to_a).hash
81
+ end
82
+
70
83
  def method_missing(id,*args)
71
84
  id = id.to_s.gsub(/=$/,"")
72
85
  if @attributes.include?(id)
@@ -0,0 +1,42 @@
1
+ require 'set'
2
+
3
+ module Ruport::Data
4
+ class Set < Collection
5
+
6
+ def initialize(options={})
7
+ @data = ::Set.new
8
+ options[:data].each {|e| self << e} if options[:data]
9
+ end
10
+
11
+ def <<(other)
12
+ case other
13
+ when Record
14
+ @data << other
15
+ when Array
16
+ @data << Record.new(other)
17
+ end
18
+ end
19
+
20
+ def ==(other)
21
+ @data == other.data
22
+ end
23
+
24
+ def |(other)
25
+ Set.new :data => (@data | other.data)
26
+ end
27
+ alias_method :union, :|
28
+
29
+ def &(other)
30
+ Set.new :data => (@data & other.data)
31
+ end
32
+ alias_method :intersection, :&
33
+
34
+ # Set difference
35
+ def -(other)
36
+ Set.new :data => (@data - other.data)
37
+ end
38
+ alias_method :difference, :-
39
+
40
+ def_delegators :@data, :each
41
+ end
42
+ end
@@ -1,3 +1,9 @@
1
+ class Array
2
+ def to_table(options={})
3
+ Ruport::Data::Table.new({:data => self}.merge(options))
4
+ end
5
+ end
6
+
1
7
  module Ruport::Data
2
8
  class Table < Collection
3
9
  def initialize(options={})
@@ -12,6 +18,15 @@ module Ruport::Data
12
18
  @column_names = other.dup
13
19
  end
14
20
 
21
+ def eql?(other)
22
+ data.eql?(other.data) && column_names.eql?(other.column_names)
23
+ end
24
+ alias_method :==, :eql?
25
+
26
+ def to_s
27
+ as(:text)
28
+ end
29
+
15
30
  def <<(other)
16
31
  case other
17
32
  when Array
@@ -86,6 +101,11 @@ module Ruport::Data
86
101
  Ruport::Data::Record.new data, :attributes => group
87
102
  end
88
103
  end
104
+
105
+ def method_missing(id,*args)
106
+ return as($1.to_sym) if id.to_s =~ /^to_(.*)/
107
+ super
108
+ end
89
109
 
90
110
  end
91
111
  end
data/lib/ruport/format.rb CHANGED
@@ -19,17 +19,11 @@ module Ruport
19
19
  # There are three main sets of functionality the Ruport::Format model provides.
20
20
  # * Structured printable document support ( Format::Document and friends)
21
21
  # * Text filter support ( Report#render and the Format class)
22
- # * Support for DataSet Formatting ( Format::Builder)
23
22
  #
24
23
  # The support for structured printable documents is currently geared towards PDF
25
24
  # support and needs some additional work to be truly useful. Suggestions would
26
25
  # be much appreciated.
27
26
  #
28
- # Format::Builder lets you define functions that will be used via DataSet#as
29
- # This is primary geared towards tabular data output, but there is no reason why
30
- # DataSet#as and the <tt>render_foo</tt> methods of Format::Builder cannot be
31
- # adapted to fit whatever needs you may need.
32
- #
33
27
  # The filters implemented in the Format class are meant to process strings or
34
28
  # entire templates. The Format class will soon automatically build a
35
29
  # Ruport::Parser for any string input. By default, filters are provided to
@@ -94,6 +94,23 @@ module Ruport
94
94
  private_class_method :new
95
95
  end
96
96
 
97
+ class Format::Engine::Invoice < Ruport::Format::Engine
98
+
99
+ # order meta data
100
+ attributes [:customer_info, :company_info]
101
+
102
+ renderer do
103
+ super
104
+ build_company_header
105
+ build_customer_header
106
+ active_plugin.render_invoice
107
+ end
108
+
109
+ alias_engine Invoice, :invoice_engine
110
+ Ruport::Format.build_interface_for Invoice, :invoice
111
+
112
+ end
113
+
97
114
  class Format::Engine::Table < Format::Engine
98
115
 
99
116
  renderer do
@@ -109,7 +126,7 @@ module Ruport
109
126
  def rewrite_column(key,&block)
110
127
  data.each { |r| r[key] = block[r] }
111
128
  end
112
-
129
+
113
130
  def num_cols
114
131
  data[0].to_a.length
115
132
  end
@@ -106,7 +106,7 @@ module Ruport
106
106
 
107
107
  action :max_col_width do |index|
108
108
  f = data.column_names if data.respond_to? :column_names
109
- d = DataSet.new f, :data => data
109
+ d = Data::Table.new :column_names => f, :data => data
110
110
 
111
111
  cw = d.map { |r| r[index].to_s.length }.max
112
112
 
@@ -119,9 +119,11 @@ module Ruport
119
119
 
120
120
  action :table_width do
121
121
  f = data.column_names if data.respond_to? :column_names
122
- d = DataSet.new f, :data => data
122
+ d = Data::Table.new:column_names => f, :data => data
123
123
 
124
- d[0].attributes.inject(0) { |s,e| s + max_col_width(e) }
124
+ f = d[0].attributes || (0...d[0].length)
125
+
126
+ f.inject(0) { |s,e| s + max_col_width(e) }
125
127
  end
126
128
 
127
129
  action :hr do
@@ -136,10 +138,16 @@ module Ruport
136
138
  end
137
139
 
138
140
  class PDFPlugin < Format::Plugin
139
-
141
+ attribute :pdf
142
+ attribute :paper
143
+
144
+ helper(:init_plugin) {
145
+ require "pdf/writer"
146
+ require "pdf/simpletable"
147
+ self.pdf = PDF::Writer.new( :paper => paper || "LETTER" )
148
+ }
149
+
140
150
  renderer :table do
141
- require "pdf/writer"; require "pdf/simpletable";
142
- pdf = PDF::Writer.new
143
151
  pre[pdf] if pre
144
152
  PDF::SimpleTable.new do |table|
145
153
  table.maximum_width = 500
@@ -156,8 +164,78 @@ module Ruport
156
164
 
157
165
  format_field_names { data.column_names }
158
166
 
167
+ renderer :invoice do
168
+ return unless defined? PDF::Writer
169
+
170
+ pdf.start_page_numbering(500, 20, 8, :right)
171
+
172
+ # order contents
173
+ pdf.y = 620
174
+
175
+ PDF::SimpleTable.new do |table|
176
+ table.width = 450
177
+ table.orientation = :center
178
+ table.data = data
179
+ table.show_lines = :outer
180
+ table.column_order = data.column_names
181
+ table.render_on(pdf)
182
+ table.font_size = 12
183
+ end
184
+
185
+
186
+ # footer
187
+ pdf.open_object do |footer|
188
+ pdf.save_state
189
+ pdf.stroke_color! Color::Black
190
+ pdf.stroke_style! PDF::Writer::StrokeStyle::DEFAULT
191
+
192
+ pdf.add_text_wrap( 50, 20, 200, "Printed at " +
193
+ Time.now.strftime("%H:%M %d/%m/%Y"), 8)
194
+
195
+ pdf.restore_state
196
+ pdf.close_object
197
+ pdf.add_object(footer, :all_pages)
198
+ end
199
+
200
+ pdf.stop_page_numbering(true, :current)
201
+ pdf.render
202
+ end
203
+
204
+ # Company Information in top lefthand corner
205
+ helper(:build_company_header) { |eng|
206
+ text_box(eng.company_info)
207
+ }
208
+
209
+
210
+
211
+ helper(:build_order_helper) { }
212
+
213
+ # Order details
214
+ helper(:build_customer_header) { |eng|
215
+ pdf.y -= 10
216
+ text_box(eng.customer_info)
217
+ }
218
+
219
+ def self.text_box(content,options={})
220
+ PDF::SimpleTable.new do |table|
221
+ table.data = content.to_a.inject([]) do |s,line|
222
+ s << { "value" => line }
223
+ end
224
+ table.column_order = "value"
225
+ table.show_headings = false
226
+ table.show_lines = :outer
227
+ table.shade_rows = :none
228
+ table.width = options[:width] || 200
229
+ table.orientation = options[:orientation] || :right
230
+ table.position = options[:position] || :left
231
+ table.font_size = options[:font_size] || 10
232
+ table.render_on(pdf)
233
+ end
234
+ end
235
+
159
236
  plugin_name :pdf
160
237
  register_on :table_engine
238
+ register_on :invoice_engine
161
239
  end
162
240
 
163
241
  class HTMLPlugin < Format::Plugin
data/lib/ruport/query.rb CHANGED
@@ -4,7 +4,7 @@ require "ruport/query/sql_split"
4
4
  module Ruport
5
5
 
6
6
  # Query offers a way to interact with databases via DBI. It supports
7
- # returning result sets in either Ruport's native DataSets, or in their raw
7
+ # returning result sets in either Ruport's native Data::Table, or in their raw
8
8
  # form as DBI::Rows.
9
9
  #
10
10
  # It offers basic caching support, the ability to instantiate a generator for
@@ -25,7 +25,7 @@ module Ruport
25
25
  # This kind of laziness is supposed to be A Good Thing, and
26
26
  # as long as you keep it in mind, it should not cause any problems.
27
27
  #
28
- # The SQL can be single or multistatement, but the resulting DataSet will
28
+ # The SQL can be single or multistatement, but the resulting Data::Table will
29
29
  # consist only of the result of the last statement which returns something.
30
30
  #
31
31
  # Options:
@@ -106,7 +106,7 @@ module Ruport
106
106
  fetch &action
107
107
  end
108
108
 
109
- # Grabs the result set as a DataSet or if in raw_data mode, an array of
109
+ # Grabs the result set as a Data::Table or if in raw_data mode, an array of
110
110
  # DBI:Row objects
111
111
  def result; fetch; end
112
112
 
@@ -137,7 +137,7 @@ module Ruport
137
137
  @cache_enabled = false
138
138
  end
139
139
 
140
- # Returns a DataSet, even if in raw_data mode
140
+ # Returns a Data::Table, even if in raw_data mode
141
141
  # Does not work with raw data if cache is enabled and filled
142
142
  def to_dataset
143
143
  data_flag, @raw_data = @raw_data, false
@@ -160,7 +160,7 @@ module Ruport
160
160
 
161
161
  require "dbi"
162
162
 
163
- data = @raw_data ? [] : DataSet.new
163
+ data = @raw_data ? [] : Data::Table.new
164
164
  DBI.connect(@dsn, @user, @password) do |dbh|
165
165
  dbh.execute(query_text) do |sth|
166
166
  return unless sth.fetchable?