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 +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
|