athena 0.2.1 → 0.2.2

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/README CHANGED
@@ -2,12 +2,22 @@
2
2
 
3
3
  == VERSION
4
4
 
5
- This documentation refers to athena version 0.2.1
5
+ This documentation refers to athena version 0.2.2
6
6
 
7
7
 
8
8
  == DESCRIPTION
9
9
 
10
- TODO: well, the description... ;-)
10
+ Athena is a library to convert "database" files between various formats.
11
+ It's accompanied by a corresponding executable that gives access to its
12
+ converting features from the command-line. See Athena::Formats for the
13
+ supported database formats and how you can add your own.
14
+
15
+ === Plugins
16
+
17
+ Athena has a plugin system that allows you to implement additional database
18
+ formats and have them available easily. Just create a file +athena_plugin.rb+
19
+ (Athena::PLUGIN_FILENAME) in your Gem's +lib+ directory or any directory that's
20
+ placed in <tt>$LOAD_PATH</tt>. See Athena::Formats on how to define custom formats.
11
21
 
12
22
 
13
23
  == LINKS
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ begin
14
14
  :summary => %q{Convert database files to various formats.},
15
15
  :author => %q{Jens Wille},
16
16
  :email => %q{jens.wille@uni-koeln.de},
17
- :dependencies => %w[builder xmlstreamin] << ['ruby-nuggets', '>= 0.7.4']
17
+ :dependencies => %w[builder xmlstreamin] << ['ruby-nuggets', '>= 0.8.1']
18
18
  }
19
19
  }}
20
20
  rescue LoadError => err
data/bin/athena CHANGED
@@ -1,4 +1,4 @@
1
- #! /usr/bin/ruby
1
+ #! /usr/bin/env ruby
2
2
 
3
3
  #--
4
4
  ###############################################################################
@@ -0,0 +1,13 @@
1
+ class Bla < Athena::Formats::Base
2
+
3
+ def parse
4
+ # ...
5
+ end
6
+
7
+ def convert
8
+ # ...
9
+ end
10
+
11
+ register_format :blub
12
+
13
+ end
@@ -26,55 +26,83 @@
26
26
  ###############################################################################
27
27
  #++
28
28
 
29
- # Athena is a library to convert (mainly) prometheus database files to various
30
- # output formats. It's accompanied by a corresponding script that gives access
31
- # to all its converting features.
32
- #
33
- # In order to support additional input and/or output formats, Athena::Formats::Base
34
- # needs to be sub-classed and, respectively, an instance method _parse_ or an
35
- # instance method _convert_ supplied. This way, a specific format can even function
36
- # as both input and output format.
37
-
38
29
  require 'athena/version'
39
30
 
31
+ # See README.
32
+
40
33
  module Athena
41
34
 
42
- autoload :Parser, 'athena/parser'
43
35
  autoload :Record, 'athena/record'
44
36
  autoload :Formats, 'athena/formats'
45
37
 
46
38
  extend self
47
39
 
48
- def parser(config, format)
49
- Parser.new(config, format)
50
- end
40
+ DEFAULT_SEPARATOR = ', '
41
+ DEFAULT_EMPTY = '<<EMPTY>>'
51
42
 
52
- def input_formats
53
- Formats::Base.formats[:in].sort
43
+ def run(config, spec, format, input, output)
44
+ Formats[:out, format, output].run(
45
+ Formats[:in, spec, build_config(config)],
46
+ input
47
+ )
54
48
  end
55
49
 
56
- def valid_input_format?(format)
57
- Formats::Base.valid_format?(:in, format)
50
+ def input_formats
51
+ Formats.formats[:in].sort
58
52
  end
59
53
 
60
54
  def output_formats
61
- Formats::Base.formats[:out].sort
55
+ Formats.formats[:out].sort
62
56
  end
63
57
 
64
- def valid_output_format?(format)
65
- Formats::Base.valid_format?(:out, format)
58
+ def valid_format?(direction, format)
59
+ Formats.valid_format?(direction, format)
66
60
  end
67
61
 
68
- def deferred_output?(format)
69
- Formats[:out, format].deferred?
62
+ def valid_input_format?(format)
63
+ valid_format?(:in, format)
70
64
  end
71
65
 
72
- def raw_output?(format)
73
- Formats[:out, format].raw?
66
+ def valid_output_format?(format)
67
+ valid_format?(:out, format)
74
68
  end
75
69
 
76
- def with_format(format, *args, &block)
77
- Formats[:out, format].wrap(*args, &block)
70
+ private
71
+
72
+ def build_config(config)
73
+ hash = {}
74
+
75
+ config.each { |field, value|
76
+ if field.to_s =~ /\A__/
77
+ hash[field] = value
78
+ else
79
+ case value
80
+ when String, Array
81
+ elements, value = [*value], {}
82
+ when Hash
83
+ elements = value[:elements] || value[:element].to_a
84
+
85
+ raise ArgumentError, "no elements specified for field #{field}" unless elements.is_a?(Array)
86
+ else
87
+ raise ArgumentError, "illegal value for field #{field}"
88
+ end
89
+
90
+ separator = value[:separator] || DEFAULT_SEPARATOR
91
+
92
+ elements.each { |element|
93
+ (hash[element] ||= {})[field] = {
94
+ :string => value[:string] || ['%s'] * elements.size * separator,
95
+ :empty => value[:empty] || DEFAULT_EMPTY,
96
+ :elements => elements
97
+ }
98
+ }
99
+ end
100
+ }
101
+
102
+ hash
78
103
  end
79
104
 
80
105
  end
106
+
107
+ require 'nuggets/util/pluggable'
108
+ Util::Pluggable.load_plugins_for(Athena)
@@ -63,42 +63,18 @@ module Athena
63
63
  }.reverse.find { |t| config[target = t.to_sym] }
64
64
  end or abort "Config not found for target: #{target}."
65
65
 
66
- parser = Athena.parser(target_config, spec)
67
-
68
66
  input = options[:input]
69
67
  input = arguments.shift unless input != defaults[:input] || arguments.empty?
70
- options[:input] = File.directory?(input) ? Dir.open(input) : open_file_or_std(input)
68
+ input = File.directory?(input) ? Dir.open(input) : open_file_or_std(input)
71
69
 
72
70
  quit unless arguments.empty?
73
71
 
74
- options[:output] = open_file_or_std(options[:output], true)
75
-
76
- if Athena.deferred_output?(format)
77
- res = parser.parse(options[:input])
78
-
79
- res.map { |record|
80
- record.to(format)
81
- }.flatten.sort.uniq.each { |line|
82
- options[:output].puts line
83
- }
84
- elsif Athena.raw_output?(format)
85
- res = Athena.with_format(format, options[:output]) { |_format|
86
- parser.parse(options[:input]) { |record|
87
- record.to(_format)
88
- }
89
- }
90
- else
91
- res = Athena.with_format(format) { |_format|
92
- parser.parse(options[:input]) { |record|
93
- options[:output].puts record.to(_format)
94
- }
95
- }
96
- end
72
+ Athena.run(target_config, spec, format, input, open_file_or_std(options[:output], true))
97
73
  end
98
74
 
99
75
  private
100
76
 
101
- def merge_config(args = [default])
77
+ def merge_config(args = [defaults])
102
78
  super
103
79
  end
104
80
 
@@ -124,13 +100,7 @@ module Athena
124
100
  }
125
101
 
126
102
  opts.on('-L', '--list-specs', "List available input formats (specs) and exit") {
127
- puts 'Available input formats (specs):'
128
-
129
- Athena.input_formats.each { |format, name|
130
- puts " - #{format}#{" (= #{name})" if format != name.to_s}"
131
- }
132
-
133
- exit
103
+ print_formats(:in)
134
104
  }
135
105
 
136
106
  opts.separator ''
@@ -145,13 +115,7 @@ module Athena
145
115
  }
146
116
 
147
117
  opts.on('-l', '--list-formats', "List available output formats and exit") {
148
- puts 'Available output formats:'
149
-
150
- Athena.output_formats.each { |format, name|
151
- puts " - #{format}#{" (= #{name})" if format != name.to_s}"
152
- }
153
-
154
- exit
118
+ print_formats(:out)
155
119
  }
156
120
 
157
121
  opts.separator ''
@@ -161,6 +125,19 @@ module Athena
161
125
  }
162
126
  end
163
127
 
128
+ def print_formats(direction)
129
+ puts "Available #{direction}put formats:"
130
+
131
+ Athena.send("#{direction}put_formats").each { |name, klass|
132
+ line, format = " - #{name}", klass.format
133
+ line << " (= #{format})" if name != format && Athena.valid_format?(direction, format)
134
+
135
+ puts line
136
+ }
137
+
138
+ exit
139
+ end
140
+
164
141
  end
165
142
 
166
143
  end
@@ -30,108 +30,416 @@ require 'athena'
30
30
 
31
31
  module Athena
32
32
 
33
+ # In order to support additional input and/or output formats,
34
+ # Athena::Formats::Base needs to be sub-classed and an instance
35
+ # method _parse_ or an instance method _convert_ supplied,
36
+ # respectively. This way, a specific format can even function
37
+ # as both input and output format.
38
+ #
39
+ # == Defining custom formats
40
+ #
41
+ # Define one or more classes that inherit from Athena::Formats::Base
42
+ # and either call Athena::Formats.register with your format class as
43
+ # parameter or call Athena::Formats.register_all with your surrounding
44
+ # namespace (which will then recursively add any format definitions below
45
+ # it). Alternatively, you can call Athena::Formats::Base.register_format
46
+ # <b>at the end</b> of your class definition to register just this class.
47
+ #
48
+ # The directions supported by your custom format are determined
49
+ # automatically; see below for further details.
50
+ #
51
+ # === Defining an _input_ format
52
+ #
53
+ # An input format must provide a *public* instance method _parse_ that
54
+ # accepts an input object (usually an IO object) and a block it passes
55
+ # each record (Athena::Record) to. See Athena::Formats::Base#parse.
56
+ #
57
+ # === Defining an _output_ format
58
+ #
59
+ # An output format must provide a *public* instance method _convert_ that
60
+ # accepts a record (Athena::Record) and either returns a suitable value for
61
+ # output or writes the output itself. See Athena::Formats::Base#convert.
62
+ #
63
+ # == Aliases
64
+ #
65
+ # In order to provide an alias name for a format, simply assign
66
+ # the format class to a new constant. Then you need to call
67
+ # <tt>Athena::Formats.register(your_new_const)</tt> to register
68
+ # your alias.
69
+
33
70
  module Formats
34
71
 
35
- CRLF = %Q{\015\012}
72
+ # CR+LF line ending.
73
+ CRLF = %Q{\015\012}
74
+
75
+ # Regular expression to match (multiple) CR+LF line endings.
36
76
  CRLF_RE = %r{(?:\r?\n)+}
37
77
 
38
- def self.[](direction, format)
39
- if direction == :out
40
- if format.class < Base
41
- if format.class.direction != direction
42
- raise DirectionMismatchError,
43
- "expected #{direction}, got #{format.class.direction}"
78
+ # Mapping of format direction to method required for implementation.
79
+ METHODS = { :in => 'parse', :out => 'convert' }
80
+
81
+ @formats = { :in => {}, :out => {} }
82
+
83
+ class << self
84
+
85
+ # Container for registered formats per direction. Use ::find or ::[]
86
+ # to access them.
87
+ attr_reader :formats
88
+
89
+ # call-seq:
90
+ # Athena::Formats[direction, format, *args] -> aFormat
91
+ #
92
+ # Retrieves the format for +direction+ by its name +format+ (see
93
+ # ::find) and initializes it with +args+ (see Base#init). Returns
94
+ # +format+ unaltered if it already is a format instance, while
95
+ # making sure that it supports +direction+.
96
+ def [](direction, format, *args)
97
+ res = find(direction, format, true)
98
+ res.is_a?(Base) ? res : res.init(direction, *args)
99
+ end
100
+
101
+ # call-seq:
102
+ # Athena::Formats.find(direction, format) -> aFormatClass
103
+ #
104
+ # Retrieves the format for +direction+ by its name +format+. Returns
105
+ # +format+'s class if it already is a format instance, while making
106
+ # sure that it supports +direction+.
107
+ def find(direction, format, instance = false)
108
+ directions = formats.keys
109
+
110
+ unless directions.include?(direction)
111
+ raise ArgumentError, "invalid direction: #{direction.inspect}" <<
112
+ " (must be one of: #{directions.map { |d| d.inspect }.join(', ')})"
113
+ end
114
+
115
+ case format
116
+ when Symbol
117
+ find(direction, format.to_s)
118
+ when String
119
+ formats[direction][format] or
120
+ raise FormatNotFoundError.new(direction, format)
44
121
  else
45
- format
46
- end
122
+ klass = format.class
123
+
124
+ if klass < Base && !(directions = klass.directions).empty?
125
+ if klass.has_direction?(direction)
126
+ return instance ? format : klass
127
+ else
128
+ raise DirectionMismatchError.new(direction, directions)
129
+ end
130
+ else
131
+ raise ArgumentError, "invalid format of type #{klass}" <<
132
+ " (expected one of: Symbol, String, or sub-class of #{Base})"
133
+ end
134
+ end
135
+ end
136
+
137
+ # call-seq:
138
+ # Athena::Formats.valid_format?(direction, format) -> true | false
139
+ #
140
+ # Indicates whether the +direction+/+format+ combination is supported,
141
+ # i.e. a format by name +format+ has been registered and supports
142
+ # +direction+.
143
+ def valid_format?(direction, format)
144
+ if format.class < Base
145
+ format.class.has_direction?(direction)
47
146
  else
48
- Base.formats[direction][format].new
147
+ formats[direction].has_key?(format.to_s)
148
+ end
149
+ end
150
+
151
+ # call-seq:
152
+ # Athena::Formats.register(klass, name = nil, relax = false) -> anArray | nil
153
+ #
154
+ # Registers +klass+ as format under +name+ (defaults to Base.format).
155
+ # Only warns instead of raising any errors when +relax+ is +true+.
156
+ # Returns an array of the actual name +klass+ has been registered
157
+ # under and the directions supported; returns +nil+ if nothing has
158
+ # been registered.
159
+ def register(klass, name = nil, relax = false)
160
+ unless klass < Base
161
+ return if relax
162
+ raise ArgumentError, "must be a sub-class of #{Base}"
49
163
  end
50
- else
51
- Base.formats[direction][format]
164
+
165
+ name = name ? name.to_s : klass.format
166
+ methods = klass.public_instance_methods(false).map { |m| m.to_s }
167
+ directions = klass.directions
168
+
169
+ METHODS.each { |direction, method|
170
+ next unless methods.include?(method)
171
+
172
+ if formats[direction].has_key?(name)
173
+ err = DuplicateFormatDefinitionError.new(direction, name)
174
+ raise err unless relax
175
+
176
+ warn err
177
+ next
178
+ else
179
+ directions << direction unless klass.has_direction?(direction)
180
+ formats[direction][name] = klass
181
+ end
182
+ }
183
+
184
+ [name, directions] unless directions.empty?
185
+ end
186
+
187
+ # call-seq:
188
+ # Athena::Formats.register_all(klass = self) -> anArray
189
+ #
190
+ # Recursively registers all formats *below* +klass+ (see ::register).
191
+ # Returns an array of all registered format names with their supported
192
+ # directions.
193
+ def register_all(klass = self, registered = [])
194
+ names = klass.constants
195
+ names -= klass.superclass.constants if klass.is_a?(Class)
196
+
197
+ names.each { |name|
198
+ const = klass.const_get(name)
199
+ next unless const.is_a?(Module)
200
+
201
+ registered << register(const, format_name("#{klass}::#{name}"), true)
202
+ register_all(const, registered)
203
+ }
204
+
205
+ registered.compact
206
+ end
207
+
208
+ protected
209
+
210
+ # call-seq:
211
+ # Athena::Formats.format_name(name) -> aString
212
+ #
213
+ # Formats +name+ as suitable format name.
214
+ def format_name(fn)
215
+ fn.sub(/\A#{self}::/, '').
216
+ gsub(/([a-z\d])(?=[A-Z])/, '\1_').
217
+ gsub(/::/, '/').downcase
52
218
  end
219
+
53
220
  end
54
221
 
55
- class Base
222
+ # Base class for all format classes. See Athena::Formats
223
+ # for more information.
56
224
 
57
- @formats = { :in => {}, :out => {} }
225
+ class Base
58
226
 
59
227
  class << self
60
228
 
61
- def formats
62
- Base.instance_variable_get(:@formats)
229
+ # call-seq:
230
+ # Athena::Formats::Base.format -> aString
231
+ #
232
+ # Returns this class's format name.
233
+ def format
234
+ @format ||= Formats.format_name(name)
63
235
  end
64
236
 
65
- def valid_format?(direction, format)
66
- if format.class < Base
67
- direction == format.class.direction
68
- else
69
- formats[direction].has_key?(format)
70
- end
237
+ # call-seq:
238
+ # Athena::Formats::Base.directions -> anArray
239
+ #
240
+ # Returns an array of the directions supported by this class.
241
+ def directions
242
+ @directions ||= []
243
+ end
244
+
245
+ # call-seq:
246
+ # Athena::Formats::Base.has_direction?(direction) -> true | false
247
+ #
248
+ # Indicates whether this class supports +direction+.
249
+ def has_direction?(direction)
250
+ directions.include?(direction)
71
251
  end
72
252
 
73
- private
253
+ # call-seq:
254
+ # Athena::Formats::Base.init(direction, *args) -> aFormat
255
+ #
256
+ # Returns a new instance of this class for +direction+ initialized
257
+ # with +args+ (see #init).
258
+ def init(direction, *args)
259
+ new.init(direction, *args)
260
+ end
74
261
 
75
- def register_format(direction, *aliases, &block)
76
- format = name.split('::').last.
77
- gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
78
- gsub(/([a-z\d])([A-Z])/, '\1_\2').
79
- downcase
262
+ protected
80
263
 
81
- register_format!(direction, format, *aliases, &block)
264
+ # call-seq:
265
+ # Athena::Formats::Base.register_format(name = nil, relax = false) -> anArray | nil
266
+ #
267
+ # Shortcut for <tt>Athena::Formats.register(self, name, relax)</tt>.
268
+ # Must be called at the end of or after the class definition (in order
269
+ # to determine the supported direction(s), the relevant instance
270
+ # methods must be available).
271
+ def register_format(name = nil, relax = false)
272
+ Formats.register(self, name, relax)
82
273
  end
83
274
 
84
- def register_format!(direction, format, *aliases, &block)
85
- raise "must be a sub-class of #{Base}" unless self < Base
275
+ end
276
+
277
+ # The _input_ format's configuration hash.
278
+ attr_reader :config
86
279
 
87
- klass = Class.new(self, &block)
280
+ # The _input_ format's "record element" (interpreted
281
+ # differently by each format).
282
+ attr_reader :record_element
88
283
 
89
- klass.instance_eval %Q{
90
- def direction; #{direction.inspect}; end
91
- def name; '#{format}::#{direction}'; end
92
- def to_s; '#{format}'; end
93
- }
284
+ # The _output_ format's output target.
285
+ attr_reader :output
94
286
 
95
- [format, *aliases].each { |name|
96
- if existing = formats[direction][name]
97
- raise DuplicateFormatDefinitionError,
98
- "format already defined (#{direction}): #{name}"
99
- else
100
- formats[direction][name] = klass
101
- end
102
- }
287
+ # call-seq:
288
+ # format.init(direction, *args) -> format
289
+ #
290
+ # Initializes _format_ for +direction+ with +args+ (see #init_in and
291
+ # #init_out), while making sure that +direction+ is actually supported
292
+ # by _format_. Returns _format_.
293
+ def init(direction, *args)
294
+ if self.class.has_direction?(direction)
295
+ send("init_#{direction}", *args)
296
+ else
297
+ raise DirectionMismatchError.new(direction, self.class.directions)
103
298
  end
104
299
 
300
+ self
105
301
  end
106
302
 
107
- def parse(*args)
303
+ # call-seq:
304
+ # format.parse(input) { |record| ... } -> anInteger
305
+ #
306
+ # Parses +input+ according to the format represented by this class
307
+ # and passes each record to the block. _Should_ return the number of
308
+ # records parsed.
309
+ #
310
+ # NOTE: Must be implemented by the sub-class in order to function as
311
+ # _input_ format.
312
+ def parse(input)
108
313
  raise NotImplementedError, 'must be defined by sub-class'
109
314
  end
110
315
 
316
+ # call-seq:
317
+ # format.convert(record) -> aString | anArray | void
318
+ #
319
+ # Converts +record+ (Athena::Record) according to the format represented
320
+ # by this class. The return value may be different for each class; it is
321
+ # irrelevant when #raw? has been defined as +true+.
322
+ #
323
+ # NOTE: Must be implemented by the sub-class in order to function as
324
+ # _output_ format.
111
325
  def convert(record)
112
326
  raise NotImplementedError, 'must be defined by sub-class'
113
327
  end
114
328
 
115
- def wrap
116
- yield self
329
+ # call-seq:
330
+ # format.raw? -> true | false
331
+ #
332
+ # Indicates whether output is written directly in #convert.
333
+ def raw?
334
+ false
117
335
  end
118
336
 
337
+ # call-seq:
338
+ # format.deferred? -> true | false
339
+ #
340
+ # Indicates whether output is to be deferred and only be written after
341
+ # all records have been converted (see #run).
119
342
  def deferred?
120
343
  false
121
344
  end
122
345
 
123
- def raw?
124
- false
346
+ # call-seq:
347
+ # format.run(spec, input) -> anInteger
348
+ #
349
+ # Runs the _output_ generation for _input_ format +spec+ (Athena::Formats::Base)
350
+ # on +input+. Outputs a sorted and unique list of records when #deferred?
351
+ # is +true+. Returns the return value of #parse.
352
+ def run(spec, input)
353
+ parsed, block = nil, if raw?
354
+ lambda { |record| record.to(self) }
355
+ elsif deferred?
356
+ deferred = []
357
+ lambda { |record| deferred << record.to(self) }
358
+ else
359
+ lambda { |record| output.puts(record.to(self)) }
360
+ end
361
+
362
+ wrap { parsed = spec.parse(input, &block) }
363
+
364
+ if deferred?
365
+ deferred.flatten!; deferred.sort!; deferred.uniq!
366
+ output.puts(deferred)
367
+ end
368
+
369
+ parsed
370
+ end
371
+
372
+ private
373
+
374
+ # call-seq:
375
+ # format.init_in(config)
376
+ #
377
+ # Initialize _input_ format (with +config+).
378
+ def init_in(config)
379
+ @config = config
380
+
381
+ case @record_element = @config.delete(:__record_element)
382
+ when *@__record_element_ok__ || String
383
+ # fine!
384
+ when nil
385
+ raise NoRecordElementError, 'no record element specified'
386
+ else
387
+ raise IllegalRecordElementError, "illegal record element #{@record_element.inspect}"
388
+ end
389
+ end
390
+
391
+ # call-seq:
392
+ # format.init_out(output = nil)
393
+ #
394
+ # Initialize _output_ format (with optional +output+).
395
+ def init_out(output = nil)
396
+ @output = output
397
+ end
398
+
399
+ # call-seq:
400
+ # format.wrap { ... }
401
+ #
402
+ # Hook for wrapping the output generation in #run.
403
+ def wrap
404
+ yield
125
405
  end
126
406
 
127
407
  end
128
408
 
129
409
  class FormatError < StandardError; end
130
410
 
131
- class DuplicateFormatDefinitionError < FormatError; end
132
- class DirectionMismatchError < FormatError; end
411
+ class DuplicateFormatDefinitionError < FormatError
412
+ def initialize(direction, format)
413
+ @direction, @format = direction, format
414
+ end
415
+
416
+ def to_s
417
+ "format already defined (#{@direction.inspect}): #{@format.inspect}"
418
+ end
419
+ end
420
+
421
+ class FormatNotFoundError < FormatError
422
+ def initialize(direction, format)
423
+ @direction, @format = direction, format
424
+ end
425
+
426
+ def to_s
427
+ "format not found (#{@direction.inspect}): #{@format.inspect}"
428
+ end
429
+ end
133
430
 
134
- ConfigError = Parser::ConfigError
431
+ class DirectionMismatchError < FormatError
432
+ def initialize(direction, directions)
433
+ @direction, @directions = direction, directions
434
+ end
435
+
436
+ def to_s
437
+ "got #{@direction.inspect}, expected one of " <<
438
+ @directions.map { |d| d.inspect }.join(', ')
439
+ end
440
+ end
441
+
442
+ class ConfigError < StandardError; end
135
443
 
136
444
  class NoRecordElementError < ConfigError; end
137
445
  class IllegalRecordElementError < ConfigError; end
@@ -143,3 +451,5 @@ end
143
451
  Dir[__FILE__.sub(/\.rb\z/, '/**/*.rb')].sort.each { |rb|
144
452
  require "athena/formats/#{File.basename(rb, '.rb')}"
145
453
  }
454
+
455
+ Athena::Formats.register_all