iostreams 1.0.0.beta → 1.0.0.beta2
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.
- checksums.yaml +4 -4
- data/lib/io_streams/encode/reader.rb +5 -2
- data/lib/io_streams/encode/writer.rb +2 -1
- data/lib/io_streams/io_streams.rb +7 -6
- data/lib/io_streams/line/reader.rb +2 -2
- data/lib/io_streams/line/writer.rb +3 -3
- data/lib/io_streams/path.rb +12 -11
- data/lib/io_streams/paths/http.rb +8 -3
- data/lib/io_streams/paths/s3.rb +16 -15
- data/lib/io_streams/paths/sftp.rb +11 -8
- data/lib/io_streams/record/reader.rb +2 -2
- data/lib/io_streams/record/writer.rb +3 -3
- data/lib/io_streams/row/reader.rb +2 -2
- data/lib/io_streams/row/writer.rb +1 -1
- data/lib/io_streams/stream.rb +20 -125
- data/lib/io_streams/version.rb +1 -1
- data/test/stream_test.rb +300 -6
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4469de3d596bf7fd0485de78878ad8188c0a60c8a3b52cfbd2dcd207ec4e8e92
         | 
| 4 | 
            +
              data.tar.gz: 25e2e16fa02f1d8b964cd8bdb521576a106148fd6446ebddf832735e5006d403
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 37456322483004e740a678a46a32bae7dc1fd02d9ad1363a0ce02c95502cb0fb68d07295280193277b5b252b3ae39162a019ba3f85f1483c3e5503011ae68419
         | 
| 7 | 
            +
              data.tar.gz: ea9cd84e99f50b7771d7429f78da254b15ffd1b08cb7cadfa4cb7c9033bb3cc6f59064addd3824e4bcf5f1072669268916c16cd2116d7e5b6e822bd34f6f4415
         | 
| @@ -7,7 +7,9 @@ module IOStreams | |
| 7 7 | 
             
                  # Builtin strip options to apply after encoding the read data.
         | 
| 8 8 | 
             
                  CLEANSE_RULES = {
         | 
| 9 9 | 
             
                    # Strips all non printable characters
         | 
| 10 | 
            -
                    printable: ->(data) { data.gsub!(NOT_PRINTABLE, '') || data }
         | 
| 10 | 
            +
                    printable: ->(data, _) { data.gsub!(NOT_PRINTABLE, '') || data },
         | 
| 11 | 
            +
                    # Replaces non printable characters with the value specified in the `replace` option.
         | 
| 12 | 
            +
                    replace_non_printable: ->(data, replace) { data.gsub!(NOT_PRINTABLE, replace || '') || data }
         | 
| 11 13 | 
             
                  }.freeze
         | 
| 12 14 |  | 
| 13 15 | 
             
                  # Read a line at a time from a file or stream
         | 
| @@ -46,6 +48,7 @@ module IOStreams | |
| 46 48 | 
             
                    @cleaner          = self.class.extract_cleaner(cleaner)
         | 
| 47 49 | 
             
                    @encoding         = encoding.nil? || encoding.is_a?(Encoding) ? encoding : Encoding.find(encoding)
         | 
| 48 50 | 
             
                    @encoding_options = replace.nil? ? {} : {invalid: :replace, undef: :replace, replace: replace}
         | 
| 51 | 
            +
                    @replace          = replace
         | 
| 49 52 |  | 
| 50 53 | 
             
                    # More efficient read buffering only supported when the input stream `#read` method supports it.
         | 
| 51 54 | 
             
                    if replace.nil? && !@input_stream.method(:read).arity.between?(0, 1)
         | 
| @@ -75,7 +78,7 @@ module IOStreams | |
| 75 78 | 
             
                    return unless block
         | 
| 76 79 |  | 
| 77 80 | 
             
                    block = block.encode(@encoding, @encoding_options) unless block.encoding == @encoding
         | 
| 78 | 
            -
                    block = @cleaner.call(block) if @cleaner
         | 
| 81 | 
            +
                    block = @cleaner.call(block, @replace) if @cleaner
         | 
| 79 82 | 
             
                    block
         | 
| 80 83 | 
             
                  end
         | 
| 81 84 |  | 
| @@ -40,6 +40,7 @@ module IOStreams | |
| 40 40 | 
             
                    @cleaner          = ::IOStreams::Encode::Reader.send(:extract_cleaner, cleaner)
         | 
| 41 41 | 
             
                    @encoding         = encoding.nil? || encoding.is_a?(Encoding) ? encoding : Encoding.find(encoding)
         | 
| 42 42 | 
             
                    @encoding_options = replace.nil? ? {} : {invalid: :replace, undef: :replace, replace: replace}
         | 
| 43 | 
            +
                    @replace          = replace
         | 
| 43 44 | 
             
                  end
         | 
| 44 45 |  | 
| 45 46 | 
             
                  # Write a line to the output stream
         | 
| @@ -66,7 +67,7 @@ module IOStreams | |
| 66 67 |  | 
| 67 68 | 
             
                    data  = data.to_s
         | 
| 68 69 | 
             
                    block = data.encoding == @encoding ? data : data.encode(@encoding, @encoding_options)
         | 
| 69 | 
            -
                    block = @cleaner.call(block) if @cleaner
         | 
| 70 | 
            +
                    block = @cleaner.call(block, @replace) if @cleaner
         | 
| 70 71 | 
             
                    @output_stream.write(block)
         | 
| 71 72 | 
             
                  end
         | 
| 72 73 | 
             
                end
         | 
| @@ -47,13 +47,14 @@ module IOStreams | |
| 47 47 | 
             
              # IOStreams.path('blah.zip').stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
         | 
| 48 48 | 
             
              # IOStreams.path('blah.zip').stream(:zip).stream(:encode, encoding: 'BINARY').reader(&:read)
         | 
| 49 49 | 
             
              #
         | 
| 50 | 
            -
              def self.path(*elements)
         | 
| 51 | 
            -
                return elements.first if (elements.size == 1) && elements.first.is_a?(IOStreams::Path)
         | 
| 50 | 
            +
              def self.path(*elements, **args)
         | 
| 51 | 
            +
                return elements.first if (elements.size == 1) && args.empty? && elements.first.is_a?(IOStreams::Path)
         | 
| 52 52 |  | 
| 53 | 
            -
                elements | 
| 54 | 
            -
                path | 
| 55 | 
            -
                 | 
| 56 | 
            -
                scheme( | 
| 53 | 
            +
                elements         = elements.collect(&:to_s)
         | 
| 54 | 
            +
                path             = ::File.join(*elements)
         | 
| 55 | 
            +
                extracted_scheme = path.include?("://") ? URI.parse(path).scheme : nil
         | 
| 56 | 
            +
                klass            = scheme(extracted_scheme)
         | 
| 57 | 
            +
                args.empty? ? klass.new(path) : klass.new(path, **args)
         | 
| 57 58 | 
             
              end
         | 
| 58 59 |  | 
| 59 60 | 
             
              # For an existing IO Stream
         | 
| @@ -9,9 +9,9 @@ module IOStreams | |
| 9 9 | 
             
                  LINEFEED_REGEXP = Regexp.compile(/\r\n|\n|\r/).freeze
         | 
| 10 10 |  | 
| 11 11 | 
             
                  # Read a line at a time from a stream
         | 
| 12 | 
            -
                  def self.stream(input_stream, original_file_name: nil, **args | 
| 12 | 
            +
                  def self.stream(input_stream, original_file_name: nil, **args)
         | 
| 13 13 | 
             
                    # Pass-through if already a line reader
         | 
| 14 | 
            -
                    return  | 
| 14 | 
            +
                    return yield(input_stream) if input_stream.is_a?(self.class)
         | 
| 15 15 |  | 
| 16 16 | 
             
                    yield new(input_stream, **args)
         | 
| 17 17 | 
             
                  end
         | 
| @@ -4,9 +4,9 @@ module IOStreams | |
| 4 4 | 
             
                  attr_reader :delimiter
         | 
| 5 5 |  | 
| 6 6 | 
             
                  # Write a line at a time to a stream.
         | 
| 7 | 
            -
                  def self.stream(output_stream,  | 
| 7 | 
            +
                  def self.stream(output_stream, **args)
         | 
| 8 8 | 
             
                    # Pass-through if already a line writer
         | 
| 9 | 
            -
                    return  | 
| 9 | 
            +
                    return yield(output_stream) if output_stream.is_a?(self.class)
         | 
| 10 10 |  | 
| 11 11 | 
             
                    yield new(output_stream, **args)
         | 
| 12 12 | 
             
                  end
         | 
| @@ -24,7 +24,7 @@ module IOStreams | |
| 24 24 | 
             
                  #     Add the specified delimiter after every record when writing it
         | 
| 25 25 | 
             
                  #     to the output stream
         | 
| 26 26 | 
             
                  #     Default: OS Specific. Linux: "\n"
         | 
| 27 | 
            -
                  def initialize(output_stream, delimiter:  | 
| 27 | 
            +
                  def initialize(output_stream, delimiter: $/, original_file_name: nil)
         | 
| 28 28 | 
             
                    super(output_stream)
         | 
| 29 29 | 
             
                    @delimiter = delimiter
         | 
| 30 30 | 
             
                  end
         | 
    
        data/lib/io_streams/path.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            module IOStreams
         | 
| 2 2 | 
             
              class Path < IOStreams::Stream
         | 
| 3 | 
            -
                 | 
| 3 | 
            +
                attr_accessor :path
         | 
| 4 4 |  | 
| 5 5 | 
             
                def initialize(path)
         | 
| 6 6 | 
             
                  raise(ArgumentError, 'Path cannot be nil') if path.nil?
         | 
| @@ -18,11 +18,11 @@ module IOStreams | |
| 18 18 |  | 
| 19 19 | 
             
                  elements = elements.collect(&:to_s)
         | 
| 20 20 | 
             
                  relative = ::File.join(*elements)
         | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
                   | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 21 | 
            +
             | 
| 22 | 
            +
                  new_path         = dup
         | 
| 23 | 
            +
                  new_path.streams = nil
         | 
| 24 | 
            +
                  new_path.path    = relative.start_with?(path) ? relative : ::File.join(path, relative)
         | 
| 25 | 
            +
                  new_path
         | 
| 26 26 | 
             
                end
         | 
| 27 27 |  | 
| 28 28 | 
             
                def relative?
         | 
| @@ -103,7 +103,6 @@ module IOStreams | |
| 103 103 | 
             
                end
         | 
| 104 104 |  | 
| 105 105 | 
             
                # Returns [IOStreams::Path] the directory for this file.
         | 
| 106 | 
            -
                # Returns `nil` if no `file_name` was set.
         | 
| 107 106 | 
             
                #
         | 
| 108 107 | 
             
                # If `path` does not include a directory name then "." is returned.
         | 
| 109 108 | 
             
                #
         | 
| @@ -114,8 +113,10 @@ module IOStreams | |
| 114 113 | 
             
                #   IOStreams.path("test").directory            #=> "."
         | 
| 115 114 | 
             
                #   IOStreams.path(".profile").directory        #=> "."
         | 
| 116 115 | 
             
                def directory
         | 
| 117 | 
            -
                   | 
| 118 | 
            -
                   | 
| 116 | 
            +
                  new_path         = dup
         | 
| 117 | 
            +
                  new_path.streams = nil
         | 
| 118 | 
            +
                  new_path.path    = ::File.dirname(path)
         | 
| 119 | 
            +
                  new_path
         | 
| 119 120 | 
             
                end
         | 
| 120 121 |  | 
| 121 122 | 
             
                # When path is a file, deletes this file.
         | 
| @@ -166,12 +167,12 @@ module IOStreams | |
| 166 167 |  | 
| 167 168 | 
             
                # Paths are sortable by name
         | 
| 168 169 | 
             
                def <=>(other)
         | 
| 169 | 
            -
                  path <=> other. | 
| 170 | 
            +
                  path <=> other.path
         | 
| 170 171 | 
             
                end
         | 
| 171 172 |  | 
| 172 173 | 
             
                # Compare by path name, ignore streams
         | 
| 173 174 | 
             
                def ==(other)
         | 
| 174 | 
            -
                  path == other. | 
| 175 | 
            +
                  path == other.path
         | 
| 175 176 | 
             
                end
         | 
| 176 177 |  | 
| 177 178 | 
             
                def inspect
         | 
| @@ -3,7 +3,7 @@ require 'uri' | |
| 3 3 | 
             
            module IOStreams
         | 
| 4 4 | 
             
              module Paths
         | 
| 5 5 | 
             
                class HTTP < IOStreams::Path
         | 
| 6 | 
            -
                  attr_reader :username, :password, :http_redirect_count
         | 
| 6 | 
            +
                  attr_reader :username, :password, :http_redirect_count, :url
         | 
| 7 7 |  | 
| 8 8 | 
             
                  # Stream to/from a remote file over http(s).
         | 
| 9 9 | 
             
                  #
         | 
| @@ -34,7 +34,12 @@ module IOStreams | |
| 34 34 | 
             
                    @username            = username || uri.user
         | 
| 35 35 | 
             
                    @password            = password || uri.password
         | 
| 36 36 | 
             
                    @http_redirect_count = http_redirect_count
         | 
| 37 | 
            -
                     | 
| 37 | 
            +
                    @url                 = url
         | 
| 38 | 
            +
                    super(uri.path)
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def to_s
         | 
| 42 | 
            +
                    url
         | 
| 38 43 | 
             
                  end
         | 
| 39 44 |  | 
| 40 45 | 
             
                  # Read a file using an http get.
         | 
| @@ -48,7 +53,7 @@ module IOStreams | |
| 48 53 | 
             
                  # Notes:
         | 
| 49 54 | 
             
                  # * Since Net::HTTP download only supports a push stream, the data is streamed into a tempfile first.
         | 
| 50 55 | 
             
                  def reader(&block)
         | 
| 51 | 
            -
                    handle_redirects( | 
| 56 | 
            +
                    handle_redirects(url, http_redirect_count, &block)
         | 
| 52 57 | 
             
                  end
         | 
| 53 58 |  | 
| 54 59 | 
             
                  def handle_redirects(uri, http_redirect_count, &block)
         | 
    
        data/lib/io_streams/paths/s3.rb
    CHANGED
    
    | @@ -3,14 +3,14 @@ require "uri" | |
| 3 3 | 
             
            module IOStreams
         | 
| 4 4 | 
             
              module Paths
         | 
| 5 5 | 
             
                class S3 < IOStreams::Path
         | 
| 6 | 
            -
                  attr_reader :bucket_name, : | 
| 6 | 
            +
                  attr_reader :bucket_name, :client
         | 
| 7 7 |  | 
| 8 8 | 
             
                  # Arguments:
         | 
| 9 9 | 
             
                  #
         | 
| 10 10 | 
             
                  # url: [String]
         | 
| 11 11 | 
             
                  #   Prefix must be: `s3://`
         | 
| 12 12 | 
             
                  #   followed by bucket name,
         | 
| 13 | 
            -
                  #   followed by  | 
| 13 | 
            +
                  #   followed by key.
         | 
| 14 14 | 
             
                  #   Examples:
         | 
| 15 15 | 
             
                  #     s3://my-bucket-name/file_name.txt
         | 
| 16 16 | 
             
                  #     s3://my-bucket-name/some_path/file_name.csv
         | 
| @@ -62,9 +62,6 @@ module IOStreams | |
| 62 62 | 
             
                  # @option params [String] :grant_write_acp
         | 
| 63 63 | 
             
                  #   Allows grantee to write the ACL for the applicable object.
         | 
| 64 64 | 
             
                  #
         | 
| 65 | 
            -
                  # @option params [required, String] :key
         | 
| 66 | 
            -
                  #   Object key for which the PUT operation was initiated.
         | 
| 67 | 
            -
                  #
         | 
| 68 65 | 
             
                  # @option params [Hash<String,String>] :metadata
         | 
| 69 66 | 
             
                  #   A map of metadata to store with the object in S3.
         | 
| 70 67 | 
             
                  #
         | 
| @@ -134,21 +131,25 @@ module IOStreams | |
| 134 131 | 
             
                    raise "Invalid URI. Required Format: 's3://<bucket_name>/<key>'" unless uri.scheme == 's3'
         | 
| 135 132 |  | 
| 136 133 | 
             
                    @bucket_name = uri.host
         | 
| 137 | 
            -
                     | 
| 134 | 
            +
                    key          = uri.path.sub(%r{\A/}, '')
         | 
| 138 135 | 
             
                    @client      = client || ::Aws::S3::Client.new
         | 
| 139 136 | 
             
                    @options     = args
         | 
| 140 | 
            -
                    super( | 
| 137 | 
            +
                    super(key)
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def to_s
         | 
| 141 | 
            +
                    ::File.join("s3://", bucket_name, path)
         | 
| 141 142 | 
             
                  end
         | 
| 142 143 |  | 
| 143 144 | 
             
                  def delete
         | 
| 144 | 
            -
                    client.delete_object(bucket: bucket_name, key:  | 
| 145 | 
            +
                    client.delete_object(bucket: bucket_name, key: path)
         | 
| 145 146 | 
             
                    self
         | 
| 146 147 | 
             
                  rescue Aws::S3::Errors::NotFound
         | 
| 147 148 | 
             
                    self
         | 
| 148 149 | 
             
                  end
         | 
| 149 150 |  | 
| 150 151 | 
             
                  def exist?
         | 
| 151 | 
            -
                    client.head_object(bucket: bucket_name, key:  | 
| 152 | 
            +
                    client.head_object(bucket: bucket_name, key: path)
         | 
| 152 153 | 
             
                    true
         | 
| 153 154 | 
             
                  rescue Aws::S3::Errors::NotFound
         | 
| 154 155 | 
             
                    false
         | 
| @@ -162,9 +163,9 @@ module IOStreams | |
| 162 163 | 
             
                    target = IOStreams.new(target_path)
         | 
| 163 164 | 
             
                    return super(target) unless target.is_a?(self.class)
         | 
| 164 165 |  | 
| 165 | 
            -
                    source_name = ::File.join(bucket_name,  | 
| 166 | 
            +
                    source_name = ::File.join(bucket_name, path)
         | 
| 166 167 | 
             
                    # TODO: Does/should it also copy metadata?
         | 
| 167 | 
            -
                    client.copy_object(bucket: target.bucket_name, key: target. | 
| 168 | 
            +
                    client.copy_object(bucket: target.bucket_name, key: target.path, copy_source: source_name)
         | 
| 168 169 | 
             
                    delete
         | 
| 169 170 | 
             
                    target
         | 
| 170 171 | 
             
                  end
         | 
| @@ -179,7 +180,7 @@ module IOStreams | |
| 179 180 | 
             
                  end
         | 
| 180 181 |  | 
| 181 182 | 
             
                  def size
         | 
| 182 | 
            -
                    client.head_object(bucket: bucket_name, key:  | 
| 183 | 
            +
                    client.head_object(bucket: bucket_name, key: path).content_length
         | 
| 183 184 | 
             
                  rescue Aws::S3::Errors::NotFound
         | 
| 184 185 | 
             
                    nil
         | 
| 185 186 | 
             
                  end
         | 
| @@ -199,7 +200,7 @@ module IOStreams | |
| 199 200 | 
             
                  # Shortcut method if caller has a filename already with no other streams applied:
         | 
| 200 201 | 
             
                  def read_file(file_name)
         | 
| 201 202 | 
             
                    ::File.open(file_name, 'wb') do |file|
         | 
| 202 | 
            -
                      client.get_object(@options.merge(response_target: file, bucket: bucket_name, key:  | 
| 203 | 
            +
                      client.get_object(@options.merge(response_target: file, bucket: bucket_name, key: path))
         | 
| 203 204 | 
             
                    end
         | 
| 204 205 | 
             
                  end
         | 
| 205 206 |  | 
| @@ -226,11 +227,11 @@ module IOStreams | |
| 226 227 | 
             
                    if ::File.size(file_name) > 5 * 1024 * 1024
         | 
| 227 228 | 
             
                      # Use multipart file upload
         | 
| 228 229 | 
             
                      s3  = Aws::S3::Resource.new(client: client)
         | 
| 229 | 
            -
                      obj = s3.bucket(bucket_name).object( | 
| 230 | 
            +
                      obj = s3.bucket(bucket_name).object(path)
         | 
| 230 231 | 
             
                      obj.upload_file(file_name)
         | 
| 231 232 | 
             
                    else
         | 
| 232 233 | 
             
                      ::File.open(file_name, 'rb') do |file|
         | 
| 233 | 
            -
                        client.put_object(@options.merge(bucket: bucket_name, key:  | 
| 234 | 
            +
                        client.put_object(@options.merge(bucket: bucket_name, key: path, body: file))
         | 
| 234 235 | 
             
                      end
         | 
| 235 236 | 
             
                    end
         | 
| 236 237 | 
             
                  end
         | 
| @@ -3,12 +3,12 @@ module IOStreams | |
| 3 3 | 
             
                class SFTP < IOStreams::Path
         | 
| 4 4 | 
             
                  include SemanticLogger::Loggable if defined?(SemanticLogger)
         | 
| 5 5 |  | 
| 6 | 
            -
                  attr_reader :hostname, :username, : | 
| 6 | 
            +
                  attr_reader :hostname, :username, :create_path, :options, :url
         | 
| 7 7 |  | 
| 8 8 | 
             
                  # Stream to a remote file over sftp.
         | 
| 9 9 | 
             
                  #
         | 
| 10 | 
            -
                  #  | 
| 11 | 
            -
                  #    | 
| 10 | 
            +
                  # url: [String]
         | 
| 11 | 
            +
                  #   "sftp://<host_name>/<file_name>"
         | 
| 12 12 | 
             
                  #
         | 
| 13 13 | 
             
                  # username: [String]
         | 
| 14 14 | 
             
                  #   Name of user to login with.
         | 
| @@ -39,7 +39,6 @@ module IOStreams | |
| 39 39 | 
             
                    raise(ArgumentError, "Invalid URL. Required Format: 'sftp://<host_name>/<file_name>'") unless uri.scheme == 'sftp'
         | 
| 40 40 |  | 
| 41 41 | 
             
                    @hostname              = uri.hostname
         | 
| 42 | 
            -
                    @file_name             = uri.path
         | 
| 43 42 | 
             
                    @mkdir                 = false
         | 
| 44 43 | 
             
                    @username              = username || uri.user
         | 
| 45 44 | 
             
                    @create_path           = create_path
         | 
| @@ -51,7 +50,11 @@ module IOStreams | |
| 51 50 | 
             
                    options[:max_pkt_size] = max_pkt_size
         | 
| 52 51 | 
             
                    options[:password]     = password || uri.password
         | 
| 53 52 | 
             
                    @options               = options
         | 
| 54 | 
            -
                    super( | 
| 53 | 
            +
                    super(uri.path)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def to_s
         | 
| 57 | 
            +
                    url
         | 
| 55 58 | 
             
                  end
         | 
| 56 59 |  | 
| 57 60 | 
             
                  def mkdir
         | 
| @@ -73,7 +76,7 @@ module IOStreams | |
| 73 76 | 
             
                  def reader(&block)
         | 
| 74 77 | 
             
                    result = nil
         | 
| 75 78 | 
             
                    Net::SFTP.start(hostname, username, options) do |sftp|
         | 
| 76 | 
            -
                      result = sftp.file.open( | 
| 79 | 
            +
                      result = sftp.file.open(path, 'rb', &block)
         | 
| 77 80 | 
             
                    end
         | 
| 78 81 | 
             
                    result
         | 
| 79 82 | 
             
                  end
         | 
| @@ -89,8 +92,8 @@ module IOStreams | |
| 89 92 | 
             
                  def writer(&block)
         | 
| 90 93 | 
             
                    result = nil
         | 
| 91 94 | 
             
                    Net::SFTP.start(hostname, username, options) do |sftp|
         | 
| 92 | 
            -
                      sftp.session.exec!("mkdir -p '#{::File.dirname( | 
| 93 | 
            -
                      result = sftp.file.open( | 
| 95 | 
            +
                      sftp.session.exec!("mkdir -p '#{::File.dirname(path)}'") if create_path
         | 
| 96 | 
            +
                      result = sftp.file.open(path, 'wb', &block)
         | 
| 94 97 | 
             
                    end
         | 
| 95 98 | 
             
                    result
         | 
| 96 99 | 
             
                  end
         | 
| @@ -7,9 +7,9 @@ module IOStreams | |
| 7 7 | 
             
                  # Read a record at a time from a line stream
         | 
| 8 8 | 
             
                  # Note:
         | 
| 9 9 | 
             
                  # - The supplied stream _must_ already be a line stream, or a stream that responds to :each
         | 
| 10 | 
            -
                  def self.stream(line_reader, original_file_name: nil, **args | 
| 10 | 
            +
                  def self.stream(line_reader, original_file_name: nil, **args)
         | 
| 11 11 | 
             
                    # Pass-through if already a record reader
         | 
| 12 | 
            -
                    return  | 
| 12 | 
            +
                    return yield(line_reader) if line_reader.is_a?(self.class)
         | 
| 13 13 |  | 
| 14 14 | 
             
                    yield new(line_reader, **args)
         | 
| 15 15 | 
             
                  end
         | 
| @@ -9,9 +9,9 @@ module IOStreams | |
| 9 9 | 
             
                  # Write a record as a Hash at a time to a stream.
         | 
| 10 10 | 
             
                  # Note:
         | 
| 11 11 | 
             
                  # - The supplied stream _must_ already be a line stream, or a stream that responds to :<<
         | 
| 12 | 
            -
                  def self.stream(line_writer, original_file_name: nil, **args | 
| 13 | 
            -
                    # Pass-through if already a  | 
| 14 | 
            -
                    return  | 
| 12 | 
            +
                  def self.stream(line_writer, original_file_name: nil, **args)
         | 
| 13 | 
            +
                    # Pass-through if already a record writer
         | 
| 14 | 
            +
                    return yield(line_writer) if line_writer.is_a?(self.class)
         | 
| 15 15 |  | 
| 16 16 | 
             
                    yield new(line_writer, **args)
         | 
| 17 17 | 
             
                  end
         | 
| @@ -5,9 +5,9 @@ module IOStreams | |
| 5 5 | 
             
                  # Read a line as an Array at a time from a stream.
         | 
| 6 6 | 
             
                  # Note:
         | 
| 7 7 | 
             
                  # - The supplied stream _must_ already be a line stream, or a stream that responds to :each
         | 
| 8 | 
            -
                  def self.stream(line_reader, original_file_name: nil, **args | 
| 8 | 
            +
                  def self.stream(line_reader, original_file_name: nil, **args)
         | 
| 9 9 | 
             
                    # Pass-through if already a row reader
         | 
| 10 | 
            -
                    return  | 
| 10 | 
            +
                    return yield(line_reader) if line_reader.is_a?(self.class)
         | 
| 11 11 |  | 
| 12 12 | 
             
                    yield new(line_reader, **args)
         | 
| 13 13 | 
             
                  end
         | 
| @@ -14,7 +14,7 @@ module IOStreams | |
| 14 14 | 
             
                  # - The supplied stream _must_ already be a line stream, or a stream that responds to :<<
         | 
| 15 15 | 
             
                  def self.stream(line_writer, original_file_name: nil, **args)
         | 
| 16 16 | 
             
                    # Pass-through if already a row writer
         | 
| 17 | 
            -
                    return  | 
| 17 | 
            +
                    return yield(line_writer) if line_writer.is_a?(self.class)
         | 
| 18 18 |  | 
| 19 19 | 
             
                    yield new(line_writer, **args)
         | 
| 20 20 | 
             
                  end
         | 
    
        data/lib/io_streams/stream.rb
    CHANGED
    
    | @@ -58,50 +58,6 @@ module IOStreams | |
| 58 58 | 
             
                end
         | 
| 59 59 |  | 
| 60 60 | 
             
                # Returns a Reader for reading a file / stream
         | 
| 61 | 
            -
                #
         | 
| 62 | 
            -
                # Parameters
         | 
| 63 | 
            -
                #   file_name_or_io [String|IO]
         | 
| 64 | 
            -
                #     The file_name of the file to write to, or an IO Stream that implements
         | 
| 65 | 
            -
                #     #read.
         | 
| 66 | 
            -
                #
         | 
| 67 | 
            -
                #   streams [Symbol|Array]
         | 
| 68 | 
            -
                #     The formats/streams that be used to convert the data whilst it is
         | 
| 69 | 
            -
                #     being read.
         | 
| 70 | 
            -
                #     When nil, the file_name will be inspected to try and determine what
         | 
| 71 | 
            -
                #     streams should be applied.
         | 
| 72 | 
            -
                #     Default: nil
         | 
| 73 | 
            -
                #
         | 
| 74 | 
            -
                #   file_name [String]
         | 
| 75 | 
            -
                #     When `streams` is not supplied, `file_name` can be used for determining the streams
         | 
| 76 | 
            -
                #     to apply to read the file/stream.
         | 
| 77 | 
            -
                #     This is particularly useful when `file_name_or_io` is a stream, or a temporary file name.
         | 
| 78 | 
            -
                #     Default: nil
         | 
| 79 | 
            -
                #
         | 
| 80 | 
            -
                # Example: Zip
         | 
| 81 | 
            -
                #   IOStreams.reader('myfile.zip') do |stream|
         | 
| 82 | 
            -
                #     puts stream.read
         | 
| 83 | 
            -
                #   end
         | 
| 84 | 
            -
                #
         | 
| 85 | 
            -
                # Example: Encrypted Zip
         | 
| 86 | 
            -
                #   IOStreams.reader('myfile.zip.enc') do |stream|
         | 
| 87 | 
            -
                #     puts stream.read
         | 
| 88 | 
            -
                #   end
         | 
| 89 | 
            -
                #
         | 
| 90 | 
            -
                # Example: Explicitly set the streams
         | 
| 91 | 
            -
                #   IOStreams.reader('myfile.zip.enc', [:zip, :enc]) do |stream|
         | 
| 92 | 
            -
                #     puts stream.read
         | 
| 93 | 
            -
                #   end
         | 
| 94 | 
            -
                #
         | 
| 95 | 
            -
                # Example: Supply custom options
         | 
| 96 | 
            -
                #   # Encrypt the file and get Symmetric Encryption to also compress it
         | 
| 97 | 
            -
                #   IOStreams.reader('myfile.csv.enc', streams: enc: {compress: true}) do |stream|
         | 
| 98 | 
            -
                #     puts stream.read
         | 
| 99 | 
            -
                #   end
         | 
| 100 | 
            -
                #
         | 
| 101 | 
            -
                # Note:
         | 
| 102 | 
            -
                # * Passes the file_name_or_io as-is into the block if it is already a reader stream AND
         | 
| 103 | 
            -
                #   no streams are passed in.
         | 
| 104 | 
            -
                #
         | 
| 105 61 | 
             
                def reader(&block)
         | 
| 106 62 | 
             
                  streams.reader(io_stream, &block)
         | 
| 107 63 | 
             
                end
         | 
| @@ -141,12 +97,12 @@ module IOStreams | |
| 141 97 | 
             
                def copy_from(source, convert: true)
         | 
| 142 98 | 
             
                  if convert
         | 
| 143 99 | 
             
                    stream = IOStreams.new(source)
         | 
| 144 | 
            -
                     | 
| 100 | 
            +
                    writer do |target|
         | 
| 145 101 | 
             
                      stream.reader { |src| IO.copy_stream(src, target) }
         | 
| 146 102 | 
             
                    end
         | 
| 147 103 | 
             
                  else
         | 
| 148 104 | 
             
                    stream = source.is_a?(Stream) ? source.dup : IOStreams.new(source)
         | 
| 149 | 
            -
                     | 
| 105 | 
            +
                    dup.stream(:none).writer do |target|
         | 
| 150 106 | 
             
                      stream.stream(:none).reader { |src| IO.copy_stream(src, target) }
         | 
| 151 107 | 
             
                    end
         | 
| 152 108 | 
             
                  end
         | 
| @@ -182,26 +138,6 @@ module IOStreams | |
| 182 138 | 
             
                end
         | 
| 183 139 |  | 
| 184 140 | 
             
                # Returns [Hash] of every record in a file or stream with support for headers.
         | 
| 185 | 
            -
                #
         | 
| 186 | 
            -
                # Reading a delimited stream and converting to tabular form.
         | 
| 187 | 
            -
                #
         | 
| 188 | 
            -
                # Each record / line is returned one at a time so that very large files
         | 
| 189 | 
            -
                # can be read without having to load the entire file into memory.
         | 
| 190 | 
            -
                #
         | 
| 191 | 
            -
                # Embedded lines (within double quotes) will be skipped if
         | 
| 192 | 
            -
                #   1. The file name contains .csv
         | 
| 193 | 
            -
                #   2. Or the embedded_within argument is set
         | 
| 194 | 
            -
                #
         | 
| 195 | 
            -
                # Example: Supply custom options
         | 
| 196 | 
            -
                #   IOStreams.each_record(file_name, embedded_within: '"') do |line|
         | 
| 197 | 
            -
                #     puts line
         | 
| 198 | 
            -
                #   end
         | 
| 199 | 
            -
                #
         | 
| 200 | 
            -
                # Example:
         | 
| 201 | 
            -
                #   file_name = 'customer_data.csv.pgp'
         | 
| 202 | 
            -
                #   IOStreams.each_record(file_name) do |hash|
         | 
| 203 | 
            -
                #     p hash
         | 
| 204 | 
            -
                #   end
         | 
| 205 141 | 
             
                def each_record(**args, &block)
         | 
| 206 142 | 
             
                  record_reader(**args) { |record_stream| record_stream.each(&block) }
         | 
| 207 143 | 
             
                end
         | 
| @@ -229,57 +165,6 @@ module IOStreams | |
| 229 165 | 
             
                end
         | 
| 230 166 |  | 
| 231 167 | 
             
                # Returns a Writer for writing to a file / stream
         | 
| 232 | 
            -
                #
         | 
| 233 | 
            -
                # Parameters
         | 
| 234 | 
            -
                #   file_name_or_io [String|IO]
         | 
| 235 | 
            -
                #     The file_name of the file to write to, or an IO Stream that implements
         | 
| 236 | 
            -
                #     #write.
         | 
| 237 | 
            -
                #
         | 
| 238 | 
            -
                #   streams [Symbol|Array]
         | 
| 239 | 
            -
                #     The formats/streams that be used to convert the data whilst it is
         | 
| 240 | 
            -
                #     being written.
         | 
| 241 | 
            -
                #     When nil, the file_name will be inspected to try and determine what
         | 
| 242 | 
            -
                #     streams should be applied.
         | 
| 243 | 
            -
                #     Default: nil
         | 
| 244 | 
            -
                #
         | 
| 245 | 
            -
                # Stream types / extensions supported:
         | 
| 246 | 
            -
                #   .zip       Zip File                                   [ :zip ]
         | 
| 247 | 
            -
                #   .gz, .gzip GZip File                                  [ :gzip ]
         | 
| 248 | 
            -
                #   .enc       File Encrypted using symmetric encryption  [ :enc ]
         | 
| 249 | 
            -
                #   other      All other extensions will be returned as:  [ :file ]
         | 
| 250 | 
            -
                #
         | 
| 251 | 
            -
                # When a file is encrypted, it may also be compressed:
         | 
| 252 | 
            -
                #   .zip.enc  [ :zip, :enc ]
         | 
| 253 | 
            -
                #   .gz.enc   [ :gz,  :enc ]
         | 
| 254 | 
            -
                #
         | 
| 255 | 
            -
                # Example: Zip
         | 
| 256 | 
            -
                #   IOStreams.writer('myfile.zip') do |stream|
         | 
| 257 | 
            -
                #     stream.write(data)
         | 
| 258 | 
            -
                #   end
         | 
| 259 | 
            -
                #
         | 
| 260 | 
            -
                # Example: Encrypted Zip
         | 
| 261 | 
            -
                #   IOStreams.writer('myfile.zip.enc') do |stream|
         | 
| 262 | 
            -
                #     stream.write(data)
         | 
| 263 | 
            -
                #   end
         | 
| 264 | 
            -
                #
         | 
| 265 | 
            -
                # Example: Explicitly set the streams
         | 
| 266 | 
            -
                #   IOStreams.writer('myfile.zip.enc', [:zip, :enc]) do |stream|
         | 
| 267 | 
            -
                #     stream.write(data)
         | 
| 268 | 
            -
                #   end
         | 
| 269 | 
            -
                #
         | 
| 270 | 
            -
                # Example: Supply custom options
         | 
| 271 | 
            -
                #   IOStreams.writer('myfile.csv.enc', [enc: { compress: true }]) do |stream|
         | 
| 272 | 
            -
                #     stream.write(data)
         | 
| 273 | 
            -
                #   end
         | 
| 274 | 
            -
                #
         | 
| 275 | 
            -
                # Example: Set internal filename when creating a zip file
         | 
| 276 | 
            -
                #   IOStreams.writer('myfile.csv.zip', zip: { zip_file_name: 'myfile.csv' }) do |stream|
         | 
| 277 | 
            -
                #     stream.write(data)
         | 
| 278 | 
            -
                #   end
         | 
| 279 | 
            -
                #
         | 
| 280 | 
            -
                # Note:
         | 
| 281 | 
            -
                # * Passes the file_name_or_io as-is into the block if it is already a writer stream AND
         | 
| 282 | 
            -
                #   no streams are passed in.
         | 
| 283 168 | 
             
                def writer(&block)
         | 
| 284 169 | 
             
                  streams.writer(io_stream, &block)
         | 
| 285 170 | 
             
                end
         | 
| @@ -295,22 +180,32 @@ module IOStreams | |
| 295 180 | 
             
                  writer { |stream| stream.write(data) }
         | 
| 296 181 | 
             
                end
         | 
| 297 182 |  | 
| 298 | 
            -
                def line_writer(**args)
         | 
| 299 | 
            -
                   | 
| 183 | 
            +
                def line_writer(**args, &block)
         | 
| 184 | 
            +
                  return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Line::Writer)
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  writer { |io| IOStreams::Line::Writer.stream(io, **args, &block) }
         | 
| 300 187 | 
             
                end
         | 
| 301 188 |  | 
| 302 | 
            -
                def row_writer(delimiter: $/, **args)
         | 
| 303 | 
            -
                   | 
| 189 | 
            +
                def row_writer(delimiter: $/, **args, &block)
         | 
| 190 | 
            +
                  return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Row::Writer)
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  line_writer(delimiter: delimiter) { |io| IOStreams::Row::Writer.stream(io, **args, &block) }
         | 
| 304 193 | 
             
                end
         | 
| 305 194 |  | 
| 306 | 
            -
                def record_writer(delimiter: $/, **args)
         | 
| 307 | 
            -
                   | 
| 195 | 
            +
                def record_writer(delimiter: $/, **args, &block)
         | 
| 196 | 
            +
                  return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Record::Writer)
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  line_writer(delimiter: delimiter) { |io| IOStreams::Record::Writer.stream(io, **args, &block) }
         | 
| 308 199 | 
             
                end
         | 
| 309 200 |  | 
| 310 201 | 
             
                # Set/get the original file_name
         | 
| 311 202 | 
             
                def file_name(file_name = :none)
         | 
| 312 | 
            -
                  file_name == :none | 
| 313 | 
            -
             | 
| 203 | 
            +
                  if file_name == :none
         | 
| 204 | 
            +
                    streams.file_name
         | 
| 205 | 
            +
                  else
         | 
| 206 | 
            +
                    streams.file_name = file_name
         | 
| 207 | 
            +
                    self
         | 
| 208 | 
            +
                  end
         | 
| 314 209 | 
             
                end
         | 
| 315 210 |  | 
| 316 211 | 
             
                # Set/get the original file_name
         | 
    
        data/lib/io_streams/version.rb
    CHANGED
    
    
    
        data/test/stream_test.rb
    CHANGED
    
    | @@ -97,19 +97,313 @@ class StreamTest < Minitest::Test | |
| 97 97 | 
             
                describe '.each_record' do
         | 
| 98 98 | 
             
                end
         | 
| 99 99 |  | 
| 100 | 
            -
                describe ' | 
| 101 | 
            -
             | 
| 100 | 
            +
                describe '#writer' do
         | 
| 101 | 
            +
                  describe "#write" do
         | 
| 102 | 
            +
                    it 'one block' do
         | 
| 103 | 
            +
                      io = StringIO.new
         | 
| 104 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 105 | 
            +
                        stream.write("Hello World")
         | 
| 106 | 
            +
                      end
         | 
| 107 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    it 'multiple blocks' do
         | 
| 111 | 
            +
                      io = StringIO.new
         | 
| 112 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 113 | 
            +
                        stream.write("He")
         | 
| 114 | 
            +
                        stream.write("l")
         | 
| 115 | 
            +
                        stream.write("lo ")
         | 
| 116 | 
            +
                        stream.write("World")
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    it 'empty blocks' do
         | 
| 122 | 
            +
                      io = StringIO.new
         | 
| 123 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 124 | 
            +
                        stream.write("")
         | 
| 125 | 
            +
                        stream.write("He")
         | 
| 126 | 
            +
                        stream.write("")
         | 
| 127 | 
            +
                        stream.write("l")
         | 
| 128 | 
            +
                        stream.write("")
         | 
| 129 | 
            +
                        stream.write("lo ")
         | 
| 130 | 
            +
                        stream.write("World")
         | 
| 131 | 
            +
                        stream.write("")
         | 
| 132 | 
            +
                      end
         | 
| 133 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    it 'nil blocks' do
         | 
| 137 | 
            +
                      io = StringIO.new
         | 
| 138 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 139 | 
            +
                        stream.write(nil)
         | 
| 140 | 
            +
                        stream.write("He")
         | 
| 141 | 
            +
                        stream.write(nil)
         | 
| 142 | 
            +
                        stream.write("l")
         | 
| 143 | 
            +
                        stream.write(nil)
         | 
| 144 | 
            +
                        stream.write("lo ")
         | 
| 145 | 
            +
                        stream.write("World")
         | 
| 146 | 
            +
                        stream.write(nil)
         | 
| 147 | 
            +
                      end
         | 
| 148 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  describe "#<<" do
         | 
| 153 | 
            +
                    it 'one block' do
         | 
| 154 | 
            +
                      io = StringIO.new
         | 
| 155 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 156 | 
            +
                        stream << "Hello World"
         | 
| 157 | 
            +
                      end
         | 
| 158 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 159 | 
            +
                    end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                    it 'multiple blocks' do
         | 
| 162 | 
            +
                      io = StringIO.new
         | 
| 163 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 164 | 
            +
                        stream << "He"
         | 
| 165 | 
            +
                        stream << "l" << "lo " << "World"
         | 
| 166 | 
            +
                      end
         | 
| 167 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 168 | 
            +
                    end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                    it 'empty blocks' do
         | 
| 171 | 
            +
                      io = StringIO.new
         | 
| 172 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 173 | 
            +
                        stream << ""
         | 
| 174 | 
            +
                        stream << "He" << "" << "l" << ""
         | 
| 175 | 
            +
                        stream << "lo " << "World"
         | 
| 176 | 
            +
                        stream << ""
         | 
| 177 | 
            +
                      end
         | 
| 178 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 179 | 
            +
                    end
         | 
| 102 180 |  | 
| 103 | 
            -
             | 
| 181 | 
            +
                    it 'nil blocks' do
         | 
| 182 | 
            +
                      io = StringIO.new
         | 
| 183 | 
            +
                      IOStreams::Stream.new(io).writer do |stream|
         | 
| 184 | 
            +
                        stream << nil
         | 
| 185 | 
            +
                        stream << "He" << nil << "l" << nil
         | 
| 186 | 
            +
                        stream << "lo " << "World"
         | 
| 187 | 
            +
                        stream << nil
         | 
| 188 | 
            +
                      end
         | 
| 189 | 
            +
                      assert_equal "Hello World", io.string
         | 
| 190 | 
            +
                    end
         | 
| 191 | 
            +
                  end
         | 
| 104 192 | 
             
                end
         | 
| 105 193 |  | 
| 106 | 
            -
                describe ' | 
| 194 | 
            +
                describe '#line_writer' do
         | 
| 195 | 
            +
                  describe "#write" do
         | 
| 196 | 
            +
                    it 'one block' do
         | 
| 197 | 
            +
                      io = StringIO.new
         | 
| 198 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 199 | 
            +
                        stream.write("Hello World")
         | 
| 200 | 
            +
                      end
         | 
| 201 | 
            +
                      assert_equal "Hello World\n", io.string
         | 
| 202 | 
            +
                    end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                    it 'multiple blocks' do
         | 
| 205 | 
            +
                      io = StringIO.new
         | 
| 206 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 207 | 
            +
                        stream.write("He")
         | 
| 208 | 
            +
                        stream.write("l")
         | 
| 209 | 
            +
                        stream.write("lo ")
         | 
| 210 | 
            +
                        stream.write("World")
         | 
| 211 | 
            +
                      end
         | 
| 212 | 
            +
                      assert_equal "He\nl\nlo \nWorld\n", io.string
         | 
| 213 | 
            +
                    end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                    it 'empty blocks' do
         | 
| 216 | 
            +
                      io = StringIO.new
         | 
| 217 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 218 | 
            +
                        stream.write("")
         | 
| 219 | 
            +
                        stream.write("He")
         | 
| 220 | 
            +
                        stream.write("")
         | 
| 221 | 
            +
                        stream.write("l")
         | 
| 222 | 
            +
                        stream.write("")
         | 
| 223 | 
            +
                        stream.write("lo ")
         | 
| 224 | 
            +
                        stream.write("World")
         | 
| 225 | 
            +
                        stream.write("")
         | 
| 226 | 
            +
                      end
         | 
| 227 | 
            +
                      assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string, io.string.inspect
         | 
| 228 | 
            +
                    end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                    it 'nil blocks' do
         | 
| 231 | 
            +
                      io = StringIO.new
         | 
| 232 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 233 | 
            +
                        stream.write(nil)
         | 
| 234 | 
            +
                        stream.write("He")
         | 
| 235 | 
            +
                        stream.write(nil)
         | 
| 236 | 
            +
                        stream.write("l")
         | 
| 237 | 
            +
                        stream.write(nil)
         | 
| 238 | 
            +
                        stream.write("lo ")
         | 
| 239 | 
            +
                        stream.write("World")
         | 
| 240 | 
            +
                        stream.write(nil)
         | 
| 241 | 
            +
                      end
         | 
| 242 | 
            +
                      assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string, io.string.inspect
         | 
| 243 | 
            +
                    end
         | 
| 244 | 
            +
                  end
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                  describe "#<<" do
         | 
| 247 | 
            +
                    it 'one block' do
         | 
| 248 | 
            +
                      io = StringIO.new
         | 
| 249 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 250 | 
            +
                        stream << "Hello World"
         | 
| 251 | 
            +
                      end
         | 
| 252 | 
            +
                      assert_equal "Hello World\n", io.string
         | 
| 253 | 
            +
                    end
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                    it 'multiple blocks' do
         | 
| 256 | 
            +
                      io = StringIO.new
         | 
| 257 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 258 | 
            +
                        stream << "He"
         | 
| 259 | 
            +
                        stream << "l" << "lo " << "World"
         | 
| 260 | 
            +
                      end
         | 
| 261 | 
            +
                      assert_equal "He\nl\nlo \nWorld\n", io.string
         | 
| 262 | 
            +
                    end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                    it 'empty blocks' do
         | 
| 265 | 
            +
                      io = StringIO.new
         | 
| 266 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 267 | 
            +
                        stream << ""
         | 
| 268 | 
            +
                        stream << "He" << "" << "l" << ""
         | 
| 269 | 
            +
                        stream << "lo " << "World"
         | 
| 270 | 
            +
                        stream << ""
         | 
| 271 | 
            +
                      end
         | 
| 272 | 
            +
                      assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string
         | 
| 273 | 
            +
                    end
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                    it 'nil blocks' do
         | 
| 276 | 
            +
                      io = StringIO.new
         | 
| 277 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 278 | 
            +
                        stream << nil
         | 
| 279 | 
            +
                        stream << "He" << nil << "l" << nil
         | 
| 280 | 
            +
                        stream << "lo " << "World"
         | 
| 281 | 
            +
                        stream << nil
         | 
| 282 | 
            +
                      end
         | 
| 283 | 
            +
                      assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string
         | 
| 284 | 
            +
                    end
         | 
| 285 | 
            +
                  end
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                  describe "line writers within line writers" do
         | 
| 288 | 
            +
                    it 'uses existing line writer' do
         | 
| 289 | 
            +
                      io = StringIO.new
         | 
| 290 | 
            +
                      IOStreams::Stream.new(io).line_writer do |stream|
         | 
| 291 | 
            +
                        stream.write("Before")
         | 
| 292 | 
            +
                        IOStreams::Stream.new(stream).line_writer do |inner|
         | 
| 293 | 
            +
                          stream.write("Inner")
         | 
| 294 | 
            +
                          assert_equal inner.object_id, stream.object_id
         | 
| 295 | 
            +
                        end
         | 
| 296 | 
            +
                        stream.write("After")
         | 
| 297 | 
            +
                      end
         | 
| 298 | 
            +
                      assert_equal "Before\nInner\nAfter\n", io.string, io.string.inspect
         | 
| 299 | 
            +
                    end
         | 
| 300 | 
            +
                  end
         | 
| 107 301 | 
             
                end
         | 
| 108 302 |  | 
| 109 | 
            -
                describe ' | 
| 303 | 
            +
                describe '#row_writer' do
         | 
| 304 | 
            +
                  describe "#write" do
         | 
| 305 | 
            +
                    it 'one block' do
         | 
| 306 | 
            +
                      io = StringIO.new
         | 
| 307 | 
            +
                      IOStreams::Stream.new(io).row_writer do |stream|
         | 
| 308 | 
            +
                        stream << %w[Hello World]
         | 
| 309 | 
            +
                      end
         | 
| 310 | 
            +
                      assert_equal "Hello,World\n", io.string
         | 
| 311 | 
            +
                    end
         | 
| 312 | 
            +
             | 
| 313 | 
            +
                    it 'multiple blocks' do
         | 
| 314 | 
            +
                      io = StringIO.new
         | 
| 315 | 
            +
                      IOStreams::Stream.new(io).row_writer do |stream|
         | 
| 316 | 
            +
                        stream << %w[He]
         | 
| 317 | 
            +
                        stream << %w[l lo\  World]
         | 
| 318 | 
            +
                        stream << ["He", "", "l", ""]
         | 
| 319 | 
            +
                        stream << ["lo ", "World"]
         | 
| 320 | 
            +
                      end
         | 
| 321 | 
            +
                      assert_equal "He\nl,lo ,World\nHe,\"\",l,\"\"\nlo ,World\n", io.string, io.string.inspect
         | 
| 322 | 
            +
                    end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                    it 'empty blocks' do
         | 
| 325 | 
            +
                      # skip "TODO"
         | 
| 326 | 
            +
                      io = StringIO.new
         | 
| 327 | 
            +
                      IOStreams::Stream.new(io).row_writer do |stream|
         | 
| 328 | 
            +
                        stream << %w[He]
         | 
| 329 | 
            +
                        stream << []
         | 
| 330 | 
            +
                        stream << %w[l lo\  World]
         | 
| 331 | 
            +
                        stream << ["He", "", "l", ""]
         | 
| 332 | 
            +
                        stream << ["lo ", "World"]
         | 
| 333 | 
            +
                        stream << []
         | 
| 334 | 
            +
                      end
         | 
| 335 | 
            +
                      assert_equal "He\n\nl,lo ,World\nHe,\"\",l,\"\"\nlo ,World\n\n", io.string, io.string.inspect
         | 
| 336 | 
            +
                    end
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                    it 'nil values' do
         | 
| 339 | 
            +
                      io = StringIO.new
         | 
| 340 | 
            +
                      IOStreams::Stream.new(io).row_writer do |stream|
         | 
| 341 | 
            +
                        stream << %w[He]
         | 
| 342 | 
            +
                        stream << %w[l lo\  World]
         | 
| 343 | 
            +
                        stream << ["He", nil, "l", nil]
         | 
| 344 | 
            +
                        stream << ["lo ", "World"]
         | 
| 345 | 
            +
                      end
         | 
| 346 | 
            +
                      assert_equal "He\nl,lo ,World\nHe,,l,\nlo ,World\n", io.string, io.string.inspect
         | 
| 347 | 
            +
                    end
         | 
| 348 | 
            +
             | 
| 349 | 
            +
                    it 'empty leading array' do
         | 
| 350 | 
            +
                      skip "TODO"
         | 
| 351 | 
            +
                      io = StringIO.new
         | 
| 352 | 
            +
                      IOStreams::Stream.new(io).row_writer do |stream|
         | 
| 353 | 
            +
                        stream << []
         | 
| 354 | 
            +
                        stream << %w[He]
         | 
| 355 | 
            +
                        stream << %w[l lo\  World]
         | 
| 356 | 
            +
                        stream << ["He", "", "l", ""]
         | 
| 357 | 
            +
                        stream << ["lo ", "World"]
         | 
| 358 | 
            +
                        stream << []
         | 
| 359 | 
            +
                      end
         | 
| 360 | 
            +
                      assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string, io.string.inspect
         | 
| 361 | 
            +
                    end
         | 
| 362 | 
            +
                  end
         | 
| 110 363 | 
             
                end
         | 
| 111 364 |  | 
| 112 | 
            -
                describe ' | 
| 365 | 
            +
                describe '#record_writer' do
         | 
| 366 | 
            +
                  describe "#write" do
         | 
| 367 | 
            +
                    it 'one block' do
         | 
| 368 | 
            +
                      io = StringIO.new
         | 
| 369 | 
            +
                      IOStreams::Stream.new(io).record_writer do |stream|
         | 
| 370 | 
            +
                        stream << {first_name: "Jack", last_name: "Johnson"}
         | 
| 371 | 
            +
                      end
         | 
| 372 | 
            +
                      assert_equal  "first_name,last_name\nJack,Johnson\n", io.string, io.string.inspect
         | 
| 373 | 
            +
                    end
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                    it 'multiple blocks' do
         | 
| 376 | 
            +
                      io = StringIO.new
         | 
| 377 | 
            +
                      IOStreams::Stream.new(io).record_writer do |stream|
         | 
| 378 | 
            +
                        stream << {first_name: "Jack", last_name: "Johnson"}
         | 
| 379 | 
            +
                        stream << {first_name: "Able", last_name: "Smith"}
         | 
| 380 | 
            +
                      end
         | 
| 381 | 
            +
                      assert_equal  "first_name,last_name\nJack,Johnson\nAble,Smith\n", io.string, io.string.inspect
         | 
| 382 | 
            +
                    end
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                    it 'empty hashes' do
         | 
| 385 | 
            +
                      io = StringIO.new
         | 
| 386 | 
            +
                      IOStreams::Stream.new(io).record_writer do |stream|
         | 
| 387 | 
            +
                        stream << {first_name: "Jack", last_name: "Johnson"}
         | 
| 388 | 
            +
                        stream << {} << {first_name: "Able", last_name: "Smith"}
         | 
| 389 | 
            +
                        stream << {}
         | 
| 390 | 
            +
                      end
         | 
| 391 | 
            +
                      assert_equal "first_name,last_name\nJack,Johnson\n\n{:first_name=>\"Able\", :last_name=>\"Smith\"}\n\n", io.string, io.string.inspect
         | 
| 392 | 
            +
                    end
         | 
| 393 | 
            +
             | 
| 394 | 
            +
                    it 'nil values' do
         | 
| 395 | 
            +
                      skip "TODO"
         | 
| 396 | 
            +
                      io = StringIO.new
         | 
| 397 | 
            +
                      IOStreams::Stream.new(io).record_writer do |stream|
         | 
| 398 | 
            +
                        stream << {first_name: "Jack", last_name: "Johnson"}
         | 
| 399 | 
            +
                        stream << {} << {first_name: "Able", last_name: "Smith"}
         | 
| 400 | 
            +
                        stream << {first_name: "Able", last_name: nil}
         | 
| 401 | 
            +
                        stream << {}
         | 
| 402 | 
            +
                      end
         | 
| 403 | 
            +
                      assert_equal "first_name,last_name\nJack,Johnson\n\n{:first_name=>\"Able\", :last_name=>\"Smith\"}\n\n", io.string, io.string.inspect
         | 
| 404 | 
            +
                    end
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                  end
         | 
| 113 407 | 
             
                end
         | 
| 114 408 |  | 
| 115 409 | 
             
              end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: iostreams
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0.0. | 
| 4 | 
            +
              version: 1.0.0.beta2
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Reid Morrison
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2019-10- | 
| 11 | 
            +
            date: 2019-10-24 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies: []
         | 
| 13 13 | 
             
            description: 
         | 
| 14 14 | 
             
            email:
         |