ruport 0.2.9 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/ACKNOWLEDGEMENTS +33 -0
  2. data/AUTHORS +13 -1
  3. data/CHANGELOG +76 -1
  4. data/README +208 -89
  5. data/Rakefile +12 -8
  6. data/TODO +14 -122
  7. data/lib/ruport.rb +58 -0
  8. data/lib/ruport/config.rb +114 -0
  9. data/lib/ruport/data_row.rb +144 -0
  10. data/lib/ruport/data_set.rb +221 -0
  11. data/lib/ruport/format.rb +116 -0
  12. data/lib/ruport/format/builder.rb +29 -5
  13. data/lib/ruport/format/document.rb +77 -0
  14. data/lib/ruport/format/open_node.rb +36 -0
  15. data/lib/ruport/parser.rb +202 -0
  16. data/lib/ruport/query.rb +208 -0
  17. data/lib/ruport/query/sql_split.rb +33 -0
  18. data/lib/ruport/report.rb +116 -0
  19. data/lib/ruport/report/mailer.rb +17 -15
  20. data/test/{addressbook.csv → samples/addressbook.csv} +0 -0
  21. data/test/samples/car_ads.txt +505 -0
  22. data/test/{data.csv → samples/data.csv} +0 -0
  23. data/test/samples/document.xml +22 -0
  24. data/test/samples/five_lines.txt +5 -0
  25. data/test/samples/five_paragraphs.txt +9 -0
  26. data/test/samples/ross_report.txt +58530 -0
  27. data/test/samples/ruport_test.sql +8 -0
  28. data/test/samples/stonecodeblog.sql +279 -0
  29. data/test/{test.sql → samples/test.sql} +2 -1
  30. data/test/{test.yaml → samples/test.yaml} +0 -0
  31. data/test/tc_builder.rb +7 -4
  32. data/test/tc_config.rb +41 -0
  33. data/test/tc_data_row.rb +16 -26
  34. data/test/tc_data_set.rb +60 -41
  35. data/test/tc_database.rb +25 -0
  36. data/test/tc_document.rb +42 -0
  37. data/test/tc_element.rb +18 -0
  38. data/test/tc_page.rb +42 -0
  39. data/test/tc_query.rb +55 -0
  40. data/test/tc_reading.rb +60 -0
  41. data/test/tc_report.rb +31 -0
  42. data/test/tc_section.rb +45 -0
  43. data/test/tc_sql_split.rb +18 -0
  44. data/test/tc_state.rb +142 -0
  45. data/test/ts_all.rb +6 -3
  46. data/test/ts_format.rb +5 -0
  47. data/test/ts_parser.rb +10 -0
  48. metadata +102 -60
  49. data/bin/ruport +0 -104
  50. data/lib/ruport/format/chart.rb +0 -1
  51. data/lib/ruport/report/data_row.rb +0 -79
  52. data/lib/ruport/report/data_set.rb +0 -153
  53. data/lib/ruport/report/engine.rb +0 -201
  54. data/lib/ruport/report/fake_db.rb +0 -54
  55. data/lib/ruport/report/fake_engine.rb +0 -26
  56. data/lib/ruport/report/fake_mailer.rb +0 -23
  57. data/lib/ruport/report/sql.rb +0 -95
  58. data/lib/ruportlib.rb +0 -11
  59. data/test/tc_engine.rb +0 -102
  60. data/test/tc_mailer.rb +0 -21
@@ -0,0 +1,221 @@
1
+ # data_set.rb : Ruby Reports core datastructure.
2
+ #
3
+ # Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
4
+ #
5
+ # Copyright (c) 2006, All Rights Reserved.
6
+ #
7
+ # This is free software. You may modify and redistribute this freely under
8
+ # your choice of the GNU General Public License or the Ruby License.
9
+ #
10
+ # See LICENSE and COPYING for details
11
+ module Ruport
12
+
13
+ # The DataSet is the core datastructure for Ruport. It provides methods that
14
+ # allow you to compare and combine query results, data loaded in from CSVs,
15
+ # and user-defined sets of data.
16
+ #
17
+ # It is tightly integrated with Ruport's formatting and query systems, so if
18
+ # you'd like to take advantage of these models, you will probably find DataSet
19
+ # useful.
20
+ #
21
+ # Sample Usage:
22
+ #
23
+ # my_data = [[1,2,3],[4,5,6],[7,8,9]]
24
+ #
25
+ # ds = Ruport::DataSet.new([:col1, :col2, :col3],my_data)
26
+ # ds << [ 10, 11, 12]
27
+ # ds << { :col3 => 15, :col1 => 13, :col2 => 14 }
28
+ # puts ds.select_columns(:col1, :col3).to_csv
29
+ #
30
+ # Output:
31
+ #
32
+ # col1,col3
33
+ # 1,3
34
+ # 4,6
35
+ # 7,9
36
+ # 10,12
37
+ # 13,15
38
+ #
39
+ # The wild and crazy might want to try the Array hack:
40
+ #
41
+ # puts [[1,2,3],[4,5,6],[7,8,9]].to_ds(%w[col1 col2 col3])
42
+ #
43
+ # Output:
44
+ #
45
+ # fields: ( col1, col2, col3 )
46
+ # row0: ( 1, 2, 3 )
47
+ # row1: ( 4, 5, 6 )
48
+ # row2: ( 7, 8, 9 )
49
+ #
50
+ class DataSet
51
+
52
+ include Enumerable
53
+
54
+ # DataSets must be given a set of fields to be defined.
55
+ #
56
+ # These field names will define the columns for the DataSet and how you
57
+ # access them.
58
+ #
59
+ # data = Ruport::DataSet.new %w[ id name phone ]
60
+ #
61
+ # You can optionally pass in some content as well. (Must be Enumerable)
62
+ #
63
+ # content = [ %w[ a1 gregory 203-525-0523 ],
64
+ # %w[ a2 james 555-555-5555 ] ]
65
+ #
66
+ # data = Ruport::DataSet.new(%w[ id name phone],content)
67
+ def initialize(fields=[], content=nil, default=nil)
68
+ @fields = fields
69
+ @data = []
70
+ @default = default
71
+ content.each { |r| self << r } if content
72
+ end
73
+
74
+ #an array which contains column names
75
+ attr_accessor :fields
76
+
77
+ #the default value to fill empty cells with
78
+ attr_accessor :default
79
+
80
+ #data holds the elements of the Row
81
+ attr_reader :data
82
+
83
+ #provides a deep copy of the DataSet.
84
+ def clone
85
+ DataSet.new(@fields,@data)
86
+ end
87
+
88
+ #Allows ordinal access to rows
89
+ #
90
+ # my_data[2] -> Ruport::DataRow
91
+ def [](index)
92
+ @data[index]
93
+ end
94
+
95
+ #allows setting of rows (providing a DataRow is passed in)
96
+ def []=(index,value)
97
+ throw "Invalid object type" unless value.kind_of?(DataRow)
98
+ @data[index] = value
99
+ end
100
+
101
+ # appends a row to the DataSet
102
+ # can be added as an array or a keyed hash-like object.
103
+ #
104
+ # Columns left undefined will be filled with DataSet#default values.
105
+ #
106
+ # data << [ 1, 2, 3 ]
107
+ # data << { :some_field_name => 3, :other => 2, :another => 1 }
108
+ def << ( stuff, filler=@default )
109
+ @data << DataRow.new(stuff,@fields,:filler => filler)
110
+ end
111
+
112
+ # checks if one dataset equals another
113
+ def eql?(data2)
114
+ return false unless ( @data.length == data2.data.length and
115
+ @fields.eql?(data2.fields) )
116
+ @data.each_with_index do |row, r_index|
117
+ row.each_with_index do |field, f_index|
118
+ return false unless field.eql?(data2[r_index][f_index])
119
+ end
120
+ end
121
+
122
+ return true
123
+ end
124
+
125
+ # checks if one dataset equals another
126
+ def ==(data2)
127
+ eql?(data2)
128
+ end
129
+
130
+ # Returns true if DataSet contains no rows, false otherwise.
131
+ def empty?
132
+ return @data.empty?
133
+ end
134
+
135
+ # Allows loading of CSV files or YAML dumps. Returns a DataSet
136
+ #
137
+ # FasterCSV will be used if it is installed.
138
+ #
139
+ # my_data = Ruport::DataSet.load("foo.csv")
140
+ # my_data = Ruport::DataSet.load("foo.yaml")
141
+ # my_data = Ruport::DataSet.load("foo.yml")
142
+ def self.load ( source, default="")
143
+ case source
144
+ when /\.(yaml|yml)/
145
+ return YAML.load(File.open(source))
146
+ when /\.csv/
147
+ csv_klass = defined?(FasterCSV) ? FasterCSV : CSV
148
+ input = csv_klass.read(source) if source =~ /\.csv/
149
+ loaded_data = self.new
150
+ loaded_data.fields = input[0]
151
+ loaded_data.default = default
152
+ input[1..-1].each { |row| loaded_data << row }
153
+ return loaded_data
154
+ else
155
+ raise "Invalid file type"
156
+ end
157
+ end
158
+
159
+ # Iterates through the rows, yielding a DataRow for each.
160
+ def each(&action)
161
+ @data.each(&action)
162
+ end
163
+
164
+ # Returns a new DataSet composed of the fields specified.
165
+ def select_columns(*fields)
166
+ rows = fields.inject([]) { |s,e| s << map { |row| row[e] } }.transpose
167
+ my_data = DataSet.new(fields,rows)
168
+ end
169
+
170
+ # Returns a new DataSet with the specified fields removed
171
+ def remove_columns(*fields)
172
+ select_fields(*(@fields-fields))
173
+ end
174
+
175
+ # removes the specified fields from this DataSet (DESTRUCTIVE!)
176
+ def remove_columns!(*fields)
177
+ @fields -= fields
178
+ @data = select_fields(*(@fields)).to_a
179
+ end
180
+
181
+ # uses Format::Builder to render DataSets in various ready to output
182
+ # formats.
183
+ #
184
+ # data.as(:html) -> String
185
+ #
186
+ # data.as(:text) do |builder|
187
+ # builder.range = 2..4 -> String
188
+ # builder.header = "My Title"
189
+ # end
190
+ #
191
+ # To add new formats to this function, simply re-open Format::Builder
192
+ # and add methods like <tt>render_my_format_name</tt>.
193
+ #
194
+ # This will enable <tt>data.as(:my_format_name)</tt>
195
+ def as(format,&action)
196
+ builder = Format::Builder.new( self )
197
+ builder.format = format
198
+ action.call(builder) if block_given?
199
+ builder.render
200
+ end
201
+
202
+ # Converts a DataSet to CSV
203
+ def to_csv; as(:csv) end
204
+
205
+ # Converts a Dataset to html
206
+ def to_html; as(:html) end
207
+
208
+ # Readable string representation of the DataSet
209
+ def to_s; as(:text) end
210
+ end
211
+ end
212
+
213
+ class Array
214
+
215
+ # Will convert Arrays of Enumerable objects to DataSets.
216
+ # May have dragons.
217
+ def to_ds(fields,default=nil)
218
+ Ruport::DataSet.new(fields,to_a,default)
219
+ end
220
+
221
+ end
@@ -0,0 +1,116 @@
1
+ # format.rb : Ruby Reports formatting module
2
+ #
3
+ # Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
4
+ #
5
+ # Copyright (c) 2006, All Rights Reserved.
6
+ #
7
+ # This is free software. You may modify and redistribute this freely under
8
+ # your choice of the GNU General Public License or the Ruby License.
9
+ #
10
+ # See LICENSE and COPYING for details
11
+ %w[builder open_node document].each { |lib| require "ruport/format/#{lib}" }
12
+ begin; require "faster_csv"; rescue LoadError; require "csv"; end
13
+ begin; require "pdf/writer"; rescue LoadError; nil; end
14
+ module Ruport
15
+
16
+
17
+ # Ruport's Format model is meant to help get your data in a suitable format for
18
+ # output. Rather than make too many assumptions about how you will want your
19
+ # data to look, a number of tools have been built so that you can quickly define
20
+ # those things yourself.
21
+ #
22
+ # There are three main sets of functionality the Ruport::Format model provides.
23
+ # * Structured printable document support ( Format::Document and friends)
24
+ # * Text filter support ( Report#render and the Format class)
25
+ # * Support for DataSet Formatting ( Format::Builder)
26
+ #
27
+ # The support for structured printable documents is currently geared towards PDF
28
+ # support and needs some additional work to be truly useful. Suggestions would
29
+ # be much appreciated.
30
+ #
31
+ # Format::Builder lets you define functions that will be used via DataSet#as
32
+ # This is primary geared towards tabular data output, but there is no reason why
33
+ # DataSet#as and the <tt>render_foo</tt> methods of Format::Builder cannot be
34
+ # adapted to fit whatever needs you may need.
35
+ #
36
+ # The filters implemented in the Format class are meant to process strings or
37
+ # entire templates. The Format class will soon automatically build a
38
+ # Ruport::Parser for any string input. By default, filters are provided to
39
+ # process erb, pure ruby, and redcloth. It is trivial to extend this
40
+ # functionality though.
41
+ #
42
+ # This is best shown by a simple example:
43
+ #
44
+ # a = Ruport::Report.new
45
+ # Ruport::Format.register_filter :reverser do
46
+ # content.reverse
47
+ # end
48
+ # a.render "somestring", :filters => [:reverser]
49
+ #
50
+ # Output: "gnirtsemos"
51
+ #
52
+ # Filters can be combined, and you can run them in different orders to obtain
53
+ # different results.
54
+ #
55
+ # See the source for the built in filters for ideas.
56
+ #
57
+ # Also, see Report#render for how to bind Format objects to your own classes.
58
+ #
59
+ # When combined, filters, data set output templates, and structured printable
60
+ # document facilities create a complete Formatting system.
61
+ #
62
+ # This part of Ruport is under active development. Please do feel free to
63
+ # submit feature requests or suggestions.
64
+ class Format
65
+
66
+ # To hook up a Format object to your current class, you need to pass it a
67
+ # binding. This way, when filters are being processed, they will be
68
+ # evaluated in the context of the object they are being called from, rather
69
+ # than within an instance of Format.
70
+ #
71
+ def initialize(klass_binding)
72
+ @binding = klass_binding
73
+ end
74
+
75
+ # This is the text to be processed by the filters
76
+ attr_accessor :content
77
+
78
+ # This is the binding to the object Format is tied to
79
+ attr_accessor :binding
80
+
81
+ # Processes the ERB text in <tt>@content</tt> in the context
82
+ # of the object that Format is bound to.
83
+ def filter_erb
84
+ ERB.new(@content).result(@binding)
85
+ end
86
+
87
+ # Processes the RedCloth text in <tt>@content</tt> in the context
88
+ # of the object that Format is bound to.
89
+ def filter_red_cloth
90
+ RedCloth.new(@content).to_html
91
+ end
92
+
93
+ # Processes the ruby code in <tt>@content</tt> in the context
94
+ # of the object that Format is bound to.
95
+ #
96
+ # (Does an eval on the binding)
97
+ def filter_ruby
98
+ eval(@content,@binding)
99
+ end
100
+
101
+ # Takes a name and a block and creates a filter method
102
+ # This will define methods in the form of
103
+ # <tt>Format#filter_my_filter_name</tt>.
104
+ #
105
+ # Example:
106
+ #
107
+ # Format.register_filter :no_ohz do
108
+ # content.gsub(/O/i,"")
109
+ # end
110
+ def Format.register_filter(name,&filter_proc)
111
+ define_method "filter_#{name}".to_sym, &filter_proc
112
+ end
113
+
114
+ end
115
+ end
116
+
@@ -1,5 +1,5 @@
1
1
  module Ruport
2
- module Format
2
+ class Format
3
3
  class Builder
4
4
 
5
5
  def initialize( data_set )
@@ -20,10 +20,12 @@ module Ruport
20
20
  end
21
21
 
22
22
  def render_csv
23
+ csv_klass = defined?(FasterCSV) ? FasterCSV : CSV
24
+ fields = @original.fields
23
25
  ( @header ? "#{@header}\n\n" : "" ) +
24
- @data.inject(FasterCSV.generate_line(@original.fields)) do |out,r|
25
- out << FasterCSV.generate_line(@original.fields.map { |f| r[f] })
26
- end + ( @footer ? "\n#{@footer}\n" : "" )
26
+ @data.inject(csv_klass.generate_line(fields).chomp + "\n" ) { |out,r|
27
+ out << csv_klass.generate_line(fields.map { |f| r[f] }).chomp + "\n"
28
+ } + ( @footer ? "\n#{@footer}\n" : "" )
27
29
  end
28
30
 
29
31
  def render_html
@@ -58,7 +60,29 @@ module Ruport
58
60
  end + (@footer ? "#{@footer}\n" : "" )
59
61
 
60
62
  end
61
-
63
+ def render_pdf
64
+
65
+ return unless defined? PDF::Writer
66
+ pdf = PDF::Writer.new
67
+ pdf.margins_cm(0)
68
+ @data.each do |page|
69
+ unless page.eql?(@data.pages.first)
70
+ pdf.start_new_page
71
+ end
72
+ page.each do |section|
73
+ section.each do |element|
74
+ pdf.y = pdf.cm2pts(element.top)
75
+ pdf.text element.content,
76
+ :left => pdf.cm2pts(element.left),
77
+ :right => pdf.cm2pts(element.right),
78
+ :justification => element.align || :center
79
+
80
+ end
81
+ end
82
+ end
83
+ pdf.render
84
+
85
+ end
62
86
  end
63
87
  end
64
88
  end
@@ -0,0 +1,77 @@
1
+ require "ostruct"
2
+ require "rexml/document"
3
+ module Ruport
4
+ class Format
5
+ class Document < OpenStruct
6
+ include Enumerable
7
+
8
+ def initialize(name,options={})
9
+ super(options)
10
+ self.name = name
11
+ self.pages ||= []
12
+ end
13
+
14
+ def each
15
+ self.pages.each { |p| yield(p) }
16
+ end
17
+
18
+ def add_page(name,options={})
19
+ options[:document] = self
20
+ self.pages << Format::Page.new(name,options)
21
+ end
22
+
23
+ def <<(page)
24
+ page.document = self
25
+ self.pages << page.dup
26
+ end
27
+
28
+ def [](page_name)
29
+ return self.pages[page_name] if page_name.kind_of? Integer
30
+ self.pages.find { |p| p.name.eql?(page_name) }
31
+ end
32
+
33
+ def clone
34
+ cloned = self.clone
35
+ cloned.pages = self.pages.clone
36
+ return cloned
37
+ end
38
+ end
39
+
40
+ class Page < Format::OpenNode
41
+
42
+ def initialize(name,options={})
43
+ super(:page,:document,:sections,name,options)
44
+ end
45
+
46
+ def add_section(name,options={})
47
+ add_child(Format::Section,name,options)
48
+ end
49
+
50
+ end
51
+
52
+ class Section < Format::OpenNode
53
+
54
+ def initialize(name, options={})
55
+ super(:section,:page,:elements,name,options)
56
+ end
57
+
58
+ def add_element(name,options={})
59
+ add_child(Format::Element,name,options)
60
+ end
61
+
62
+ end
63
+
64
+ class Element < OpenStruct
65
+
66
+ def initialize(name,options={})
67
+ super(options)
68
+ self.name = name
69
+ end
70
+
71
+ def to_s
72
+ self.content
73
+ end
74
+
75
+ end
76
+ end
77
+ end