sawmill 0.0.1

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.
Files changed (38) hide show
  1. data/History.rdoc +3 -0
  2. data/README.rdoc +81 -0
  3. data/Rakefile +147 -0
  4. data/lib/sawmill/entry.rb +307 -0
  5. data/lib/sawmill/entry_classifier.rb +75 -0
  6. data/lib/sawmill/entry_processor/build_records.rb +157 -0
  7. data/lib/sawmill/entry_processor/conditionals.rb +444 -0
  8. data/lib/sawmill/entry_processor/filter_basic_fields.rb +145 -0
  9. data/lib/sawmill/entry_processor/format.rb +228 -0
  10. data/lib/sawmill/entry_processor/simple_queue.rb +116 -0
  11. data/lib/sawmill/entry_processor.rb +158 -0
  12. data/lib/sawmill/errors.rb +66 -0
  13. data/lib/sawmill/level.rb +264 -0
  14. data/lib/sawmill/log_record_middleware.rb +93 -0
  15. data/lib/sawmill/logger.rb +373 -0
  16. data/lib/sawmill/parser.rb +181 -0
  17. data/lib/sawmill/record.rb +255 -0
  18. data/lib/sawmill/record_processor/conditionals.rb +330 -0
  19. data/lib/sawmill/record_processor/decompose.rb +75 -0
  20. data/lib/sawmill/record_processor/filter_by_attributes.rb +87 -0
  21. data/lib/sawmill/record_processor/filter_by_record_id.rb +77 -0
  22. data/lib/sawmill/record_processor/format.rb +88 -0
  23. data/lib/sawmill/record_processor/simple_queue.rb +117 -0
  24. data/lib/sawmill/record_processor.rb +137 -0
  25. data/lib/sawmill/rotater/base.rb +90 -0
  26. data/lib/sawmill/rotater/date_based_log_file.rb +145 -0
  27. data/lib/sawmill/rotater/shifting_log_file.rb +166 -0
  28. data/lib/sawmill/rotater.rb +236 -0
  29. data/lib/sawmill/util/queue.rb +138 -0
  30. data/lib/sawmill/version.rb +47 -0
  31. data/lib/sawmill.rb +78 -0
  32. data/tests/tc_entry_processors.rb +138 -0
  33. data/tests/tc_formatter_parser.rb +144 -0
  34. data/tests/tc_levels.rb +118 -0
  35. data/tests/tc_logger.rb +315 -0
  36. data/tests/tc_record_processors.rb +117 -0
  37. data/tests/tc_records.rb +206 -0
  38. metadata +116 -0
@@ -0,0 +1,255 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Sawmill log record class
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2009 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module Sawmill
38
+
39
+
40
+ # A log record.
41
+ #
42
+ # Log records are sequences of related log entries with a common record
43
+ # ID, beginning with a begin_record and ending with an end_record. Log
44
+ # records therefore can be analyzed as a group, for example to measure the
45
+ # time taken by an aggregate action such as a website request.
46
+ #
47
+ # Log records must follow a particular protocol:
48
+ #
49
+ # * The first entry must be begin_record.
50
+ # * The last entry must be end_record.
51
+ # * No other begin_record or end_record entries may be present.
52
+ # * All entries must have the same non-nil record_id.
53
+ # * Entries must be in nondecreasing order of timestamp.
54
+ #
55
+ # The only exception to these rules are incomplete log records, in which
56
+ # the final end_record is not present.
57
+
58
+ class Record
59
+
60
+
61
+ # Create a log record.
62
+ #
63
+ # You may optionally pass in an array of log entries to populate the
64
+ # record either partially or completely. If you do, note that
65
+ # Errors::IllegalRecordError may be raised if the log entries do not
66
+ # follow the log record protocol-- e.g. if the first entry is not a
67
+ # begin_record, etc.
68
+
69
+ def initialize(entries_=nil)
70
+ @started = false
71
+ @complete = false
72
+ @message_count = 0
73
+ @entries = []
74
+ @attributes = {}
75
+ if entries_ && entries_.size > 0
76
+ entries_.each do |entry_|
77
+ add_entry(entry_)
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ def to_s # :nodoc:
84
+ "#{type}: #{@line}"
85
+ end
86
+
87
+ def inspect # :nodoc:
88
+ "#<#{self.class}:0x#{object_id.to_s(16)} record_id=#{record_id.inspect}>"
89
+ end
90
+
91
+
92
+ # Append a log entry to this record.
93
+ #
94
+ # Entries must be added in order. Raises Errors::IllegalRecordError if
95
+ # the log record protocol is violated
96
+
97
+ def add_entry(entry_)
98
+ empty_ = @entries.size == 0
99
+ if entry_.type == :unknown_data
100
+ raise Errors::IllegalRecordError, "You cannot add an unknown_data entry to a record"
101
+ end
102
+ if empty_ && entry_.type != :begin_record
103
+ raise Errors::IllegalRecordError, "First entry in a record must be a begin_record"
104
+ elsif !empty_ && entry_.type == :begin_record
105
+ raise Errors::IllegalRecordError, "Extra begin_record found"
106
+ end
107
+ if @complete
108
+ raise Errors::IllegalRecordError, "Cannot have entries after end_record"
109
+ end
110
+ if empty_
111
+ if entry_.record_id.nil?
112
+ raise Errors::IllegalRecordError, "Entry has no record_id"
113
+ end
114
+ else
115
+ last_ = @entries.last
116
+ if last_.record_id != entry_.record_id
117
+ raise Errors::IllegalRecordError, "Entry has a mismatching record_id"
118
+ end
119
+ if last_.timestamp > entry_.timestamp
120
+ raise Errors::IllegalRecordError, "Entry's timestamp is earlier than the previous entry"
121
+ end
122
+ end
123
+ case entry_.type
124
+ when :begin_record
125
+ @started = true
126
+ when :end_record
127
+ @complete = true
128
+ when :attribute
129
+ case entry_.operation
130
+ when :set
131
+ @attributes[entry_.key] = entry_.value
132
+ when :append
133
+ val_ = @attributes[entry_.key]
134
+ case val_
135
+ when Array
136
+ val_ << entry_.value
137
+ when String
138
+ @attributes[entry_.key] = [val_, entry_.value]
139
+ when nil
140
+ @attributes[entry_.key] = [entry_.value]
141
+ end
142
+ end
143
+ when :message
144
+ @message_count += 1
145
+ end
146
+ @entries << entry_
147
+ end
148
+
149
+
150
+ # Returns true if the initial begin_record has been added.
151
+
152
+ def started?
153
+ @started
154
+ end
155
+
156
+
157
+ # Returns true if the final end_record has been added.
158
+
159
+ def complete?
160
+ @complete
161
+ end
162
+
163
+
164
+ # Returns the record ID as a string.
165
+
166
+ def record_id
167
+ @entries.size > 0 ? @entries.first.record_id : nil
168
+ end
169
+
170
+
171
+ # Returns the beginning timestamp as a Time object, if the log record
172
+ # has been started, or nil otherwise.
173
+
174
+ def begin_timestamp
175
+ @started ? @entries.first.timestamp : nil
176
+ end
177
+
178
+
179
+ # Returns the ending timestamp as a Time object, if the log record
180
+ # has been completed, or nil otherwise.
181
+
182
+ def end_timestamp
183
+ @complete ? @entries.last.timestamp : nil
184
+ end
185
+
186
+
187
+ # Returns the number of log entries currently in this record.
188
+
189
+ def entry_count
190
+ @entries.size
191
+ end
192
+
193
+ alias_method :size, :entry_count
194
+
195
+
196
+ # Returns the number of message entries currently in this record.
197
+
198
+ def message_count
199
+ @message_count
200
+ end
201
+
202
+
203
+ # Iterate over all log entries, passing each to the given block.
204
+
205
+ def each_entry(&block_)
206
+ @entries.each(&block_)
207
+ end
208
+
209
+
210
+ # Iterate over all log message entries, passing each to the given block.
211
+
212
+ def each_message
213
+ @entries.each do |entry_|
214
+ if entry_.type == :message
215
+ yield entry_
216
+ end
217
+ end
218
+ end
219
+
220
+
221
+ # Returns an array of all log entries.
222
+
223
+ def all_entries
224
+ @entries.dup
225
+ end
226
+
227
+
228
+ # Returns an array of all log message entries.
229
+
230
+ def all_messages
231
+ @entries.find_all{ |entry_| entry_.type == :message }
232
+ end
233
+
234
+
235
+ # Get the value of the given attribute.
236
+ # Returns a string if the attribute has a single value.
237
+ # Returns an array of strings if the attribute has multiple values.
238
+ # Returns nil if the attribute is not set.
239
+
240
+ def attribute(key_)
241
+ @attributes[key_.to_s]
242
+ end
243
+
244
+
245
+ # Get an array of attribute keys present in this log record.
246
+
247
+ def attribute_keys
248
+ @attributes.keys
249
+ end
250
+
251
+
252
+ end
253
+
254
+
255
+ end
@@ -0,0 +1,330 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Sawmill basic entry processors that implement conditionals
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2009 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module Sawmill
38
+
39
+ module RecordProcessor
40
+
41
+
42
+ # An "if" conditional.
43
+ #
44
+ # Takes a boolean condition processor and executes a processor on true
45
+ # (and optionally another one on false).
46
+ #
47
+ # For example, this builds a processor that sends formatted log records
48
+ # to STDOUT only if they have a "user" attribute of "daniel":
49
+ #
50
+ # processor = Sawmill::RecordProcessor.build do
51
+ # If(FilterByAttributes('user' => 'daniel'), Format(STDOUT))
52
+ # end
53
+
54
+ class If < Base
55
+
56
+
57
+ # Create an "if" conditional.
58
+ #
59
+ # The first parameter must be a processor whose methods return a
60
+ # boolean value indicating whether the record should be accepted.
61
+ # The second parameter is a processor to run on accepted records.
62
+ # The optional third parameter is an "else" processor to run on
63
+ # rejected records.
64
+
65
+ def initialize(condition_, on_true_, on_false_=nil)
66
+ @condition = condition_
67
+ @on_true = on_true_
68
+ @on_false = on_false_
69
+ end
70
+
71
+ def record(record_)
72
+ if @condition.record(record_)
73
+ @on_true.record(record_)
74
+ elsif @on_false
75
+ @on_false.record(record_)
76
+ end
77
+ end
78
+
79
+ def extra_entry(entry_)
80
+ if @condition.extra_entry(entry_)
81
+ @on_true.extra_entry(entry_)
82
+ elsif @on_false
83
+ @on_false.extra_entry(entry_)
84
+ end
85
+ end
86
+
87
+ def close
88
+ @on_true.close
89
+ @on_false.close if @on_false
90
+ end
91
+
92
+
93
+ end
94
+
95
+
96
+ # A boolean processor that returns the boolean negation of a given
97
+ # processor.
98
+ #
99
+ # For example, this builds a processor that sends formatted log records
100
+ # to STDOUT only if they do NOT have a "user" attribute of "daniel":
101
+ #
102
+ # processor = Sawmill::RecordProcessor.build do
103
+ # If(Not(FilterByAttributes('user' => 'daniel')), Format(STDOUT))
104
+ # end
105
+
106
+ class Not < Base
107
+
108
+
109
+ # Create a "not" boolean.
110
+ # The parameter is a boolean processor to run. This processor returns
111
+ # the boolean negation of its output.
112
+
113
+ def initialize(child_)
114
+ @child = _interpret_processor(child_)
115
+ end
116
+
117
+ def record(record_)
118
+ !@child.record(record_)
119
+ end
120
+
121
+ def extra_entry(entry_)
122
+ !@child.extra_entry(record_)
123
+ end
124
+
125
+ def close
126
+ @child.close
127
+ end
128
+
129
+
130
+ end
131
+
132
+
133
+ # A boolean processor that returns true if and only if all its child
134
+ # processors return true. This version short-circuits the processing,
135
+ # so once one child returns false, subsequent children are not called
136
+ # at all.
137
+ #
138
+ # For example, this builds a processor that sends formatted log records
139
+ # to STDOUT only if they have a "user" attribute of "daniel" AND their
140
+ # record ID is '12345678':
141
+ #
142
+ # processor = Sawmill::RecordProcessor.build do
143
+ # If(And(FilterByAttributes('user' => 'daniel'),
144
+ # FilterByRecordID('12345678')),
145
+ # Format(STDOUT))
146
+ # end
147
+
148
+ class And < Base
149
+
150
+
151
+ # Create an "and" boolean.
152
+ # The parameters are child processors whose return values should be
153
+ # combined with an AND operation.
154
+
155
+ def initialize(*children_)
156
+ @children = _interpret_processor_array(children_)
157
+ end
158
+
159
+ def record(record_)
160
+ @children.each do |child_|
161
+ return false unless child_.record(record_)
162
+ end
163
+ true
164
+ end
165
+
166
+ def extra_entry(entry_)
167
+ @children.each do |child_|
168
+ return false unless child_.extra_entry(entry_)
169
+ end
170
+ true
171
+ end
172
+
173
+ def close
174
+ @children.each{ |child_| child_.close }
175
+ end
176
+
177
+
178
+ end
179
+
180
+
181
+ # A boolean processor that returns true if and only if any of its child
182
+ # processors returns true. This version short-circuits the processing,
183
+ # so once one child returns true, subsequent children are not called
184
+ # at all.
185
+ #
186
+ # For example, this builds a processor that sends formatted log records
187
+ # to STDOUT only if they have a "user" attribute of "daniel" OR their
188
+ # record ID is '12345678':
189
+ #
190
+ # processor = Sawmill::RecordProcessor.build do
191
+ # If(Or(FilterByAttributes('user' => 'daniel'),
192
+ # FilterByRecordID('12345678')),
193
+ # Format(STDOUT))
194
+ # end
195
+
196
+ class Or < Base
197
+
198
+
199
+ # Create an "or" boolean.
200
+ # The parameters are child processors whose return values should be
201
+ # combined with an OR operation.
202
+
203
+ def initialize(*children_)
204
+ @children = _interpret_processor_array(children_)
205
+ end
206
+
207
+ def record(record_)
208
+ @children.each do |child_|
209
+ return true if child_.record(record_)
210
+ end
211
+ false
212
+ end
213
+
214
+ def extra_entry(entry_)
215
+ @children.each do |child_|
216
+ return true if child_.extra_entry(entry_)
217
+ end
218
+ false
219
+ end
220
+
221
+ def close
222
+ @children.each{ |child_| child_.close }
223
+ end
224
+
225
+
226
+ end
227
+
228
+
229
+ # A boolean processor that returns true if and only if all its child
230
+ # processors return true. This version does not short-circuit the
231
+ # processing, so all children are always called even if an early one
232
+ # returns false. Thus, this processor is also a good one to use as a
233
+ # multiplexor to simply run a bunch of processors.
234
+ #
235
+ # For example, this builds a processor that sends formatted log records
236
+ # to STDOUT only if they have a "user" attribute of "daniel" AND their
237
+ # record ID is '12345678':
238
+ #
239
+ # processor = Sawmill::RecordProcessor.build do
240
+ # If(All(FilterByAttributes('user' => 'daniel'),
241
+ # FilterByRecordID('12345678')),
242
+ # Format(STDOUT))
243
+ # end
244
+ #
245
+ # This processor just formats both to STDOUT and STDERR:
246
+ #
247
+ # processor = Sawmill::RecordProcessor.build do
248
+ # All(Format(STDOUT), Format(STDERR))
249
+ # end
250
+
251
+ class All < Base
252
+
253
+
254
+ # Create an "all" boolean.
255
+ # The parameters are child processors whose return values should be
256
+ # combined with an AND operation.
257
+
258
+ def initialize(*children_)
259
+ @children = _interpret_processor_array(children_)
260
+ end
261
+
262
+ def record(record_)
263
+ @children.inject(true) do |result_, child_|
264
+ child_.record(record_) && result_
265
+ end
266
+ end
267
+
268
+ def extra_entry(entry_)
269
+ @children.inject(true) do |result_, child_|
270
+ child_.extra_entry(entry_) && result_
271
+ end
272
+ end
273
+
274
+ def close
275
+ @children.each{ |child_| child_.close }
276
+ end
277
+
278
+
279
+ end
280
+
281
+
282
+ # A boolean processor that returns true if and only if any of its child
283
+ # processors returns true. This version does not short-circuit the
284
+ # processing, so all children are always called even if an early one
285
+ # returns true.
286
+ #
287
+ # For example, this builds a processor that sends formatted log records
288
+ # to STDOUT only if they have a "user" attribute of "daniel" OR their
289
+ # record ID is '12345678':
290
+ #
291
+ # processor = Sawmill::RecordProcessor.build do
292
+ # If(Any(FilterByAttributes('user' => 'daniel'),
293
+ # FilterByRecordID('12345678')),
294
+ # Format(STDOUT))
295
+ # end
296
+
297
+ class Any < Base
298
+
299
+
300
+ # Create an "any" boolean.
301
+ # The parameters are child processors whose return values should be
302
+ # combined with an OR operation.
303
+
304
+ def initialize(*children_)
305
+ @children = _interpret_processor_array(children_)
306
+ end
307
+
308
+ def record(record_)
309
+ @children.inject(false) do |result_, child_|
310
+ child_.record(record_) || result_
311
+ end
312
+ end
313
+
314
+ def extra_entry(entry_)
315
+ @children.inject(false) do |result_, child_|
316
+ child_.extra_entry(entry_) || result_
317
+ end
318
+ end
319
+
320
+ def close
321
+ @children.each{ |child_| child_.close }
322
+ end
323
+
324
+
325
+ end
326
+
327
+
328
+ end
329
+
330
+ end
@@ -0,0 +1,75 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Sawmill record processor that decomposes a record into entries
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2009 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module Sawmill
38
+
39
+ module RecordProcessor
40
+
41
+
42
+ # A processor that decomposes records into constituent entries and
43
+ # passes those entries to an entry processor.
44
+
45
+ class Decompose < Base
46
+
47
+
48
+ # Create a new decomposer that emits to the given entry processor.
49
+
50
+ def initialize(processor_, opts_={})
51
+ @processor = processor_
52
+ @classifier = ::Sawmill::EntryClassifier.new(processor_)
53
+ end
54
+
55
+
56
+ def record(record_)
57
+ record_.each_entry{ |entry_| @classifier.entry(entry_) }
58
+ true
59
+ end
60
+
61
+ def extra_entry(entry_)
62
+ @classifier.entry(entry_)
63
+ true
64
+ end
65
+
66
+ def close
67
+ @processor.close
68
+ end
69
+
70
+ end
71
+
72
+
73
+ end
74
+
75
+ end