lean-ruport 0.3.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of lean-ruport might be problematic. Click here for more details.

Files changed (50) hide show
  1. data/ACKNOWLEDGEMENTS +33 -0
  2. data/AUTHORS +19 -0
  3. data/CHANGELOG +206 -0
  4. data/COPYING +340 -0
  5. data/LICENSE +7 -0
  6. data/README +209 -0
  7. data/Rakefile +54 -0
  8. data/TODO +27 -0
  9. data/lib/ruport.rb +58 -0
  10. data/lib/ruport/config.rb +114 -0
  11. data/lib/ruport/data_row.rb +144 -0
  12. data/lib/ruport/data_set.rb +221 -0
  13. data/lib/ruport/format.rb +116 -0
  14. data/lib/ruport/format/builder.rb +89 -0
  15. data/lib/ruport/format/document.rb +77 -0
  16. data/lib/ruport/format/open_node.rb +36 -0
  17. data/lib/ruport/parser.rb +202 -0
  18. data/lib/ruport/query.rb +208 -0
  19. data/lib/ruport/query/sql_split.rb +33 -0
  20. data/lib/ruport/report.rb +116 -0
  21. data/lib/ruport/report/mailer.rb +48 -0
  22. data/test/samples/addressbook.csv +6 -0
  23. data/test/samples/car_ads.txt +505 -0
  24. data/test/samples/data.csv +3 -0
  25. data/test/samples/document.xml +22 -0
  26. data/test/samples/five_lines.txt +5 -0
  27. data/test/samples/five_paragraphs.txt +9 -0
  28. data/test/samples/ross_report.txt +58530 -0
  29. data/test/samples/ruport_test.sql +8 -0
  30. data/test/samples/stonecodeblog.sql +279 -0
  31. data/test/samples/test.sql +2 -0
  32. data/test/samples/test.yaml +3 -0
  33. data/test/tc_builder.rb +116 -0
  34. data/test/tc_config.rb +41 -0
  35. data/test/tc_data_row.rb +36 -0
  36. data/test/tc_data_set.rb +141 -0
  37. data/test/tc_database.rb +25 -0
  38. data/test/tc_document.rb +42 -0
  39. data/test/tc_element.rb +18 -0
  40. data/test/tc_page.rb +42 -0
  41. data/test/tc_query.rb +55 -0
  42. data/test/tc_reading.rb +60 -0
  43. data/test/tc_report.rb +31 -0
  44. data/test/tc_section.rb +45 -0
  45. data/test/tc_sql_split.rb +18 -0
  46. data/test/tc_state.rb +142 -0
  47. data/test/ts_all.rb +9 -0
  48. data/test/ts_format.rb +5 -0
  49. data/test/ts_parser.rb +10 -0
  50. metadata +102 -0
@@ -0,0 +1,144 @@
1
+ # --
2
+ # data_row.rb : Ruby Reports row abstraction
3
+ #
4
+ # Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
5
+ #
6
+ # Copyright (c) 2006, All Rights Reserved.
7
+ #
8
+ # This is free software. You may modify and redistribute this freely under
9
+ # your choice of the GNU General Public License or the Ruby License.
10
+ #
11
+ # See LICENSE and COPYING for details
12
+ # ++
13
+ module Ruport
14
+
15
+ # DataRows are Enumerable lists which can be accessed by field name or ordinal
16
+ # position.
17
+ #
18
+ # They feature a tagging system, allowing them to be easily
19
+ # compared or recalled.
20
+ #
21
+ # DataRows form the elements of DataSets
22
+ #
23
+ class DataRow
24
+
25
+ include Enumerable
26
+
27
+ # Takes data and field names as well as some optional parameters and
28
+ # constructs a DataRow.
29
+ #
30
+ #
31
+ # <tt>data</tt> can be specified in Hash, Array, or DataRow form
32
+ #
33
+ # Options:
34
+ # <tt>:filler</tt>:: this will be used as a default value for empty
35
+ # <tt>:tags</tt>:: an initial set of tags for the row
36
+ #
37
+ #
38
+ # Examples:
39
+ # >> Ruport::DataRow.new [1,2,3,4,5], [:a,:b,:c,:d,:e],
40
+ # :tags => %w[cat dog]
41
+ # => #<Ruport::DataRow:0xb77e4b04 @fields=[:a, :b, :c, :d, :e],
42
+ # @data=[1, 2, 3, 4, 5], @tags=["cat", "dog"]>
43
+ #
44
+ # >> Ruport::DataRow.new({ :a => 'moo', :c => 'caw'} , [:a,:b,:c,:d,:e],
45
+ # :tags => %w[cat dog])
46
+ # => #<Ruport::DataRow:0xb77c298c @fields=[:a, :b, :c, :d, :e],
47
+ # @data=["moo", nil, "caw", nil, nil], @tags=["cat", "dog"]>
48
+ #
49
+ # >> Ruport::DataRow.new [1,2,3], [:a,:b,:c,:d,:e], :tags => %w[cat dog],
50
+ # :filler => 0
51
+ # => #<Ruport::DataRow:0xb77bb4d4 @fields=[:a, :b, :c, :d, :e],
52
+ # @data=[1, 2, 3, 0, 0], @tags=["cat", "dog"]>
53
+ #
54
+ def initialize( data, fields, options={} )
55
+ @fields = fields
56
+ @tags = options[:tags] || {}
57
+ @data = []
58
+ nr_action =
59
+ if data.kind_of?(Array)
60
+ lambda { |key, index| @data[index] = data.shift || options[:filler] }
61
+ elsif data.kind_of?(DataRow)
62
+ lambda { |key, index| @data = data.to_a }
63
+ else
64
+ lambda { |key, index| @data[index] = data[key] || options[:filler] }
65
+ end
66
+ @fields.each_with_index { |key, index| nr_action.call(key,index) }
67
+ end
68
+
69
+ attr_accessor :fields, :tags
70
+
71
+ # Returns an array of values. Should probably return a DataRow.
72
+ # Loses field information.
73
+ def +(other)
74
+ self.to_a + other.to_a
75
+ end
76
+
77
+ # Lets you access individual fields
78
+ #
79
+ # i.e. row["phone"] or row[4]
80
+ def [](key)
81
+ key.kind_of?(Fixnum) ? @data[key] : @data[@fields.index(key)]
82
+ end
83
+
84
+ # Lets you set field values
85
+ #
86
+ # i.e. row["phone"] = '2038291203', row[7] = "allen"
87
+ def []=(key,value)
88
+ if key.kind_of?(Fixnum)
89
+ @data[key] = value
90
+ else
91
+ @data[@fields.index(key)] = value
92
+ end
93
+ end
94
+
95
+ # Converts the DataRow to a plain old Array
96
+ def to_a
97
+ @data
98
+ end
99
+
100
+ # Converts the DataRow to a string representation
101
+ # for outputting to screen.
102
+ def to_s
103
+ "[" + @data.join(",") + "]"
104
+ end
105
+
106
+ # Checks to see row includes the tag given.
107
+ #
108
+ # Example:
109
+ #
110
+ # >> row.has_tag? :running_balance
111
+ # => true
112
+ #
113
+ def has_tag?(tag)
114
+ @tags.include?(tag)
115
+ end
116
+
117
+ # Iterates through DataRow elements. Accepts a block.
118
+ def each(&action)
119
+ @data.each(&action)
120
+ end
121
+
122
+ # Allows you to add a tag to a row.
123
+ #
124
+ # Examples:
125
+ #
126
+ # row.tag_as(:jay_cross) if row["product"].eql?("im_courier")
127
+ # row.tag_as(:running_balance) if row.fields.include?("RB")
128
+ #
129
+ def tag_as(something)
130
+ @tags[something] = true
131
+ end
132
+
133
+ # Compares two DataRow objects. If values and fields are the same
134
+ # (and in the correct order) returns true. Otherwise returns false.
135
+ def ==(other)
136
+ self.to_a.eql?(other.to_a) && @fields.eql?(other.fields)
137
+ end
138
+
139
+ # Synonym for DataRow#==
140
+ def eql?
141
+ self == other
142
+ end
143
+ end
144
+ end
@@ -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
+