module Athena::Formats

In order to support additional input and/or output formats, Athena::Formats::Base needs to be sub-classed and an instance method parse or an instance method convert supplied, respectively. This way, a specific format can even function as both input and output format.

Defining custom formats

Define one or more classes that inherit from Athena::Formats::Base and either call ::register with your format class as parameter or call ::register_all with your surrounding namespace (which will then recursively add any format definitions below it). Alternatively, you can call Athena::Formats::Base.register_format at the end of your class definition to register just this class.

The directions supported by your custom format are determined automatically; see below for further details.

Defining an input format

An input format must provide a public instance method parse that accepts an input object (usually an IO object) and a block it passes each record (Athena::Record) to. See Athena::Formats::Base#parse.

Defining an output format

An output format must provide a public instance method convert that accepts a record (Athena::Record) and either returns a suitable value for output or writes the output itself. See Athena::Formats::Base#convert.


In order to provide an alias name for a format, simply assign the format class to a new constant. Then you need to call Athena::Formats.register(your_new_const) to register your alias.



CR+LF line ending.


Regular expression to match (multiple) CR+LF line endings.


Mapping of format direction to method required for implementation.




Container for registered formats per direction. Use ::find or ::[] to access them.

Public Class Methods

Athena::Formats[direction, format, *args] → aFormat click to toggle source

Retrieves the format for direction by its name format (see ::find) and initializes it with args (see Athena::Formats::Base#init). Returns format unaltered if it already is a format instance, while making sure that it supports direction.

# File lib/athena/formats.rb, line 98
def [](direction, format, *args)
  res = find(direction, format, true)
  res.is_a?(Base) ? res : res.init(direction, *args)
Athena::Formats.find(direction, format) → aFormatClass click to toggle source

Retrieves the format for direction by its name format. Returns format's class if it already is a format instance, while making sure that it supports direction.

# File lib/athena/formats.rb, line 109
def find(direction, format, instance = false)
  directions = formats.keys

  unless directions.include?(direction)
    raise ArgumentError, "invalid direction: #{direction.inspect}" <<
      " (must be one of: #{ { |d| d.inspect }.join(', ')})"

  case format
    when Symbol
      find(direction, format.to_s)
    when String
      formats[direction][format] or
        raise, format)
      klass = format.class

      if klass < Base && !(directions = klass.directions).empty?
        if klass.has_direction?(direction)
          return instance ? format : klass
          raise, directions)
        raise ArgumentError, "invalid format of type #{klass}" <<
          " (expected one of: Symbol, String, or sub-class of #{Base})"
Athena::Formats.format_name(name) → aString click to toggle source

Formats name as suitable format name.

# File lib/athena/formats.rb, line 214
def format_name(fn)
  fn.sub(/\A#{self}::/, '').
    gsub(/([a-z\d])(?=[A-Z])/, '\1_').
    gsub(/::/, '/').downcase
Athena::Formats.register(klass, name = nil, relax = false) → anArray | nil click to toggle source

Registers klass as format under name (defaults to Athena::Formats::Base.format). Only warns instead of raising any errors when relax is true. Returns an array of the actual name klass has been registered under and the directions supported; returns nil if nothing has been registered.

# File lib/athena/formats.rb, line 161
def register(klass, name = nil, relax = false)
  unless klass < Base
    return if relax
    raise ArgumentError, "must be a sub-class of #{Base}"

  name = name ? name.to_s : klass.format
  methods = klass.public_instance_methods(false).map { |m| m.to_s }
  directions = klass.directions

  METHODS.each { |direction, method|
    next unless methods.include?(method)

    if formats[direction].has_key?(name)
      err =, name)
      raise err unless relax

      warn err
      directions << direction unless klass.has_direction?(direction)
      formats[direction][name] = klass

  [name, directions] unless directions.empty?
Athena::Formats.register_all(klass = self) → anArray click to toggle source

Recursively registers all formats below klass (see ::register). Returns an array of all registered format names with their supported directions.

# File lib/athena/formats.rb, line 195
def register_all(klass = self, registered = [])
  names  = klass.constants
  names -= klass.superclass.constants if klass.is_a?(Class)

  names.each { |name|
    const = klass.const_get(name)
    next unless const.is_a?(Module)

    registered << register(const, format_name("#{klass}::#{name}"), true)
    register_all(const, registered)

Athena::Formats.valid_format?(direction, format) → true | false click to toggle source

Indicates whether the direction/format combination is supported, i.e. a format by name format has been registered and supports direction.

# File lib/athena/formats.rb, line 145
def valid_format?(direction, format)
  if format.class < Base