class IOStreams::Builder

Build the streams that need to be applied to a path druing reading or writing.

Attributes

file_name[RW]
format_options[RW]
options[R]
streams[R]

Public Class Methods

new(file_name = nil) click to toggle source
# File lib/io_streams/builder.rb, line 7
def initialize(file_name = nil)
  @file_name     = file_name
  @streams       = nil
  @options       = nil
  @format        = nil
  @format_option = nil
end

Public Instance Methods

format() click to toggle source

Returns the tabular format if set, otherwise tries to autodetect the format if the file_name has been set Returns [nil] if no format is set, or if it cannot be determined from the file_name

# File lib/io_streams/builder.rb, line 96
def format
  @format ||= file_name ? Tabular.format_from_file_name(file_name) : nil
end
format=(format) click to toggle source
# File lib/io_streams/builder.rb, line 100
def format=(format)
  unless format.nil? || IOStreams::Tabular.registered_formats.include?(format)
    raise(ArgumentError, "Invalid format: #{format.inspect}")
  end

  @format = format
end
option(stream, **options) click to toggle source

Supply an option that is only applied once the file name extensions have been parsed. Note:

  • Cannot set both `stream` and `option`

# File lib/io_streams/builder.rb, line 18
def option(stream, **options)
  stream = stream.to_sym unless stream.is_a?(Symbol)
  raise(ArgumentError, "Invalid stream: #{stream.inspect}") unless IOStreams.extensions.include?(stream)
  raise(ArgumentError, "Cannot call both #option and #stream on the same streams instance}") if @streams
  raise(ArgumentError, "Cannot call #option unless the `file_name` was already set}") unless file_name

  @options ||= {}
  if (opts = @options[stream])
    opts.merge!(options)
  else
    @options[stream] = options.dup
  end
  self
end
option_or_stream(stream, **options) click to toggle source
# File lib/io_streams/builder.rb, line 53
def option_or_stream(stream, **options)
  if streams
    stream(stream, **options)
  elsif file_name
    option(stream, **options)
  else
    stream(stream, **options)
  end
end
pipeline() click to toggle source

Returns [Hash<Symbol:Hash>] the pipeline of streams with their options that will be applied when the reader or writer is invoked.

# File lib/io_streams/builder.rb, line 80
def pipeline
  return streams.dup.freeze if streams

  build_pipeline.freeze
end
reader(io_stream, &block) click to toggle source
# File lib/io_streams/builder.rb, line 70
def reader(io_stream, &block)
  execute(:reader, pipeline, io_stream, &block)
end
remove_from_pipeline(stream_name) click to toggle source

Removes the named stream from the current pipeline. If the stream pipeline has not yet been built it will be built from the file_name if present. Note: Any options must be set before calling this method.

# File lib/io_streams/builder.rb, line 89
def remove_from_pipeline(stream_name)
  @streams ||= build_pipeline
  @streams.delete(stream_name.to_sym)
end
setting(stream) click to toggle source

Return the options set for either a stream or option.

# File lib/io_streams/builder.rb, line 64
def setting(stream)
  return streams[stream] if streams

  options[stream] if options
end
stream(stream, **options) click to toggle source
# File lib/io_streams/builder.rb, line 33
def stream(stream, **options)
  stream = stream.to_sym unless stream.is_a?(Symbol)
  raise(ArgumentError, "Cannot call both #option and #stream on the same streams instance}") if @options

  # To prevent any streams from being applied supply a stream named `:none`
  if stream == :none
    @streams = {}
    return self
  end
  raise(ArgumentError, "Invalid stream: #{stream.inspect}") unless IOStreams.extensions.include?(stream)

  @streams ||= {}
  if (opts = @streams[stream])
    opts.merge!(options)
  else
    @streams[stream] = options.dup
  end
  self
end
writer(io_stream, &block) click to toggle source
# File lib/io_streams/builder.rb, line 74
def writer(io_stream, &block)
  execute(:writer, pipeline, io_stream, &block)
end

Private Instance Methods

build_pipeline() click to toggle source
# File lib/io_streams/builder.rb, line 110
def build_pipeline
  return {} unless file_name

  built_streams          = {}
  # Encode stream is always first
  built_streams[:encode] = options[:encode] if options&.key?(:encode)

  opts = options || {}
  parse_extensions.each { |stream| built_streams[stream] = opts[stream] || {} }
  built_streams
end
class_for_stream(type, stream) click to toggle source
# File lib/io_streams/builder.rb, line 122
def class_for_stream(type, stream)
  ext = IOStreams.extensions[stream.nil? ? nil : stream.to_sym] ||
        raise(ArgumentError, "Unknown Stream type: #{stream.inspect}")
  ext.send("#{type}_class") || raise(ArgumentError, "No #{type} registered for Stream type: #{stream.inspect}")
end
execute(type, pipeline, io_stream, &block) click to toggle source

Executes the streams that need to be executed.

# File lib/io_streams/builder.rb, line 142
def execute(type, pipeline, io_stream, &block)
  raise(ArgumentError, "IOStreams call is missing mandatory block") if block.nil?

  if pipeline.empty?
    block.call(io_stream)
  elsif pipeline.size == 1
    stream, opts = pipeline.first
    class_for_stream(type, stream).open(io_stream, **opts, &block)
  else
    # Daisy chain multiple streams together
    last = pipeline.keys.inject(block) do |inner, stream_sym|
      ->(io) { class_for_stream(type, stream_sym).open(io, **pipeline[stream_sym], &inner) }
    end
    last.call(io_stream)
  end
end
parse_extensions() click to toggle source

Returns the streams for the supplied file_name

# File lib/io_streams/builder.rb, line 129
def parse_extensions
  parts      = ::File.basename(file_name).split(".")
  extensions = []
  while (extension = parts.pop)
    sym = extension.downcase.to_sym
    break unless IOStreams.extensions[sym]

    extensions.unshift(sym)
  end
  extensions
end