lean-ruport 0.3.8

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.

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
+