athena 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +12 -2
- data/Rakefile +1 -1
- data/bin/athena +1 -1
- data/example/athena_plugin.rb +13 -0
- data/lib/athena.rb +54 -26
- data/lib/athena/cli.rb +18 -41
- data/lib/athena/formats.rb +363 -53
- data/lib/athena/formats/dbm.rb +2 -2
- data/lib/athena/formats/ferret.rb +4 -21
- data/lib/athena/formats/lingo.rb +58 -54
- data/lib/athena/formats/sisis.rb +2 -21
- data/lib/athena/formats/sql.rb +24 -45
- data/lib/athena/formats/xml.rb +36 -53
- data/lib/athena/record.rb +1 -3
- data/lib/athena/version.rb +1 -1
- metadata +24 -24
- data/lib/athena/parser.rb +0 -90
data/README
CHANGED
@@ -2,12 +2,22 @@
|
|
2
2
|
|
3
3
|
== VERSION
|
4
4
|
|
5
|
-
This documentation refers to athena version 0.2.
|
5
|
+
This documentation refers to athena version 0.2.2
|
6
6
|
|
7
7
|
|
8
8
|
== DESCRIPTION
|
9
9
|
|
10
|
-
|
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.
|
17
|
+
:dependencies => %w[builder xmlstreamin] << ['ruby-nuggets', '>= 0.8.1']
|
18
18
|
}
|
19
19
|
}}
|
20
20
|
rescue LoadError => err
|
data/bin/athena
CHANGED
data/lib/athena.rb
CHANGED
@@ -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
|
-
|
49
|
-
|
50
|
-
end
|
40
|
+
DEFAULT_SEPARATOR = ', '
|
41
|
+
DEFAULT_EMPTY = '<<EMPTY>>'
|
51
42
|
|
52
|
-
def
|
53
|
-
Formats
|
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
|
57
|
-
Formats
|
50
|
+
def input_formats
|
51
|
+
Formats.formats[:in].sort
|
58
52
|
end
|
59
53
|
|
60
54
|
def output_formats
|
61
|
-
Formats
|
55
|
+
Formats.formats[:out].sort
|
62
56
|
end
|
63
57
|
|
64
|
-
def
|
65
|
-
Formats
|
58
|
+
def valid_format?(direction, format)
|
59
|
+
Formats.valid_format?(direction, format)
|
66
60
|
end
|
67
61
|
|
68
|
-
def
|
69
|
-
|
62
|
+
def valid_input_format?(format)
|
63
|
+
valid_format?(:in, format)
|
70
64
|
end
|
71
65
|
|
72
|
-
def
|
73
|
-
|
66
|
+
def valid_output_format?(format)
|
67
|
+
valid_format?(:out, format)
|
74
68
|
end
|
75
69
|
|
76
|
-
|
77
|
-
|
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)
|
data/lib/athena/cli.rb
CHANGED
@@ -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
|
-
|
68
|
+
input = File.directory?(input) ? Dir.open(input) : open_file_or_std(input)
|
71
69
|
|
72
70
|
quit unless arguments.empty?
|
73
71
|
|
74
|
-
|
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 = [
|
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
|
-
|
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
|
-
|
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
|
data/lib/athena/formats.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
222
|
+
# Base class for all format classes. See Athena::Formats
|
223
|
+
# for more information.
|
56
224
|
|
57
|
-
|
225
|
+
class Base
|
58
226
|
|
59
227
|
class << self
|
60
228
|
|
61
|
-
|
62
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
275
|
+
end
|
276
|
+
|
277
|
+
# The _input_ format's configuration hash.
|
278
|
+
attr_reader :config
|
86
279
|
|
87
|
-
|
280
|
+
# The _input_ format's "record element" (interpreted
|
281
|
+
# differently by each format).
|
282
|
+
attr_reader :record_element
|
88
283
|
|
89
|
-
|
90
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
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
|
-
|
116
|
-
|
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
|
-
|
124
|
-
|
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
|
132
|
-
|
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
|
-
|
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
|