linefeed 0.3.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acb4d8a8b79c2913d1a9b3118f11c5fc61b1bd956cecfb3c921a8a34282f369c
4
- data.tar.gz: 7d3174f86ebe7c3af2960b425b8c656427eb181158c87c347db16c896fda4cef
3
+ metadata.gz: a7ee8b08d068e093abefa6cefe66b22278a0fee127b393d17944d5c96893966a
4
+ data.tar.gz: 9a5d006b788929240e54c48c5134779fed09d16fdcb88cf2d89b9442a3b7173c
5
5
  SHA512:
6
- metadata.gz: d702bc5d2e3b49141abefd70dd72b4d84d5fbac82892cf88f97ad1da11e4fbf65af0db7dc996a198f9b8674bfdba2be456cdcfaba76058a3db45e31e8bf4cc2d
7
- data.tar.gz: e94154e6ae96d1b8abe5dfff8f39653ded9b7dbd10bbe1e771f4baeb1805789b773e1a9b6506a2081bf0195704b68971604e86723d2d2654c2e259723c642c48
6
+ metadata.gz: 72c10dbecaf129be413c7db8abf471cf4f5ae9cf5f2936a89e67385f3631adbd0d6e4482eb8a54b0abc8c43d7b05f07bf0f1cfa78df1110b1a1f2c2e1fecd27b
7
+ data.tar.gz: 6aa585d5375f2aac5f2a3565822ff82d091ed4cc07c1605525b795739f02e1bb09f427d33346c147d3af6b8dd2c7206eee7459807517603d0af0c1947154fc87
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## 1.0.2 - 2026-01-04
4
+ - fix example 6 when run standalone
5
+
6
+ ## 1.0.1 - 2026-01-04
7
+ - style nits
8
+
9
+ ## 1.0.0 - 2026-01-04
10
+ - faster again
11
+ - better errors
12
+ - ci & doco
13
+
14
+ ## 0.3.0 - 2026-01-03
15
+ - speedup
16
+ - safety rails
17
+
18
+ ## 0.2.0 - 2026-01-03
19
+ - gemify
20
+
21
+ ## 0.1.0 - 2025-12-31
22
+ - idea
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 inopinatus
3
+ Copyright (c) 2026 inopinatus
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Linefeed
2
2
 
3
- Linefeed turns a push-style byte stream, of any chunk size, into individually yielded lines.
3
+ Linefeed turns a chunked byte stream into individually yielded lines.
4
4
 
5
5
  * https://github.com/inopinatus/linefeed
6
+ * https://inopinatus.github.io/linefeed/
6
7
 
7
8
  ## Why?
8
9
 
9
- When you're downstream of the read on a binary-mode chunked stream and can't easily turn that into a nice efficient `IO#readlines`.
10
-
11
- Also, it has nice properties if you chain them together.
10
+ When you're downstream of the read on a binary-mode chunked stream and want a push-style
11
+ take on `#each_line` that doesn't burn too much memory.
12
12
 
13
13
  ## Install
14
14
 
@@ -24,12 +24,12 @@ gem "linefeed"
24
24
 
25
25
  ## Protocol
26
26
 
27
- Including `linefeed` supplies two methods, `#<<` and `#close`. The idea is for external
27
+ Including `Linefeed` supplies two methods, `#<<` and `#close`. The idea is for external
28
28
  producers to drive processing by calls to these methods.
29
29
 
30
30
  - `#<<` accepts an arbitrary-size chunk of incoming data and yields each LF-terminated line
31
- to a handler set by `linefeed { |line| ... }` as an 8-bit ASCII string. Lines yielded
32
- will include the trailing LF.
31
+ to a handler set by `linefeed { |line| ... }`. Lines yielded will be 8-bit ASCII strings
32
+ and include the trailing LF.
33
33
 
34
34
  - `#close` marks end-of-incoming-data; if any data persists in the buffer, this yields a
35
35
  final unterminated string to the same handler.
@@ -76,18 +76,12 @@ def close
76
76
  end
77
77
  ```
78
78
 
79
- See `examples/` for more, like daisy-chaining, or updating a digest.
80
-
81
- ## Examples
82
-
83
- Run `examples/demo.rb` and review the numbered examples it includes.
84
-
85
- If testing with cooked interactive input at the console, note that `linefeed`'s examples necessarily read in binary mode, so ^D may not be instant EOF.
79
+ See Examples for more, like daisy-chaining, or updating a digest.
86
80
 
87
81
  ## License
88
82
 
89
- MIT. Copyright (c) 2025 inopinatus.
83
+ MIT license. Copyright (c) 2026 inopinatus
90
84
 
91
85
  ## Contributing
92
86
 
93
- At https://github.com/inopinatus/linefeed.
87
+ Visit https://github.com/inopinatus/linefeed to open a PR.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'demo'
4
+ require 'linefeed'
3
5
 
4
6
  # Simplest possible example
5
7
  module Demo
@@ -9,7 +11,8 @@ module Demo
9
11
  def initialize(output)
10
12
  line_no = 0
11
13
  linefeed do |line|
12
- output << "%.3d => %s" % [line_no += 1, line]
14
+ line_no += 1
15
+ output << format('%<line_no>03d => %<line>s', line_no: line_no, line: line)
13
16
  end
14
17
  end
15
18
  end
@@ -1,14 +1,22 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'demo'
4
+ require 'linefeed'
3
5
 
4
6
  # Per-line processing with headers & trailers
5
7
  module Demo
6
- class Canonicalize < Consumer
7
- def initialize(*)
8
- super
8
+ class Canonicalize
9
+ include Linefeed
10
+
11
+ def initialize(output)
12
+ @output = output
9
13
  @output << "---------- START\r\n"
10
14
  @output << "Canonicalized: yes\r\n"
11
15
  @output << "\r\n"
16
+
17
+ linefeed do |line|
18
+ output << process_line(line)
19
+ end
12
20
  end
13
21
 
14
22
  def process_line(line)
@@ -16,7 +24,7 @@ module Demo
16
24
  end
17
25
 
18
26
  def canonicalize(line)
19
- line.chomp.sub(/[ \t]+$/, "") + "\r\n"
27
+ "#{line.chomp.sub(/[ \t]+$/, '')}\r\n"
20
28
  end
21
29
 
22
30
  def close
@@ -25,4 +33,4 @@ module Demo
25
33
  @output.close
26
34
  end
27
35
  end
28
- end
36
+ end
@@ -1,23 +1,31 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'demo'
4
+ require 'linefeed'
3
5
 
4
6
  # Handling the protocol via super
5
7
  module Demo
6
- class Escaped < Consumer
8
+ class Escaped
9
+ include Linefeed
10
+
11
+ def initialize(output)
12
+ @output = output
13
+ end
14
+
7
15
  def escape(line)
8
- line.sub(/^(-|From )/, "- \\1")
16
+ line.sub(/^(-|From )/, '- \\1')
9
17
  end
10
18
 
11
19
  def <<(chunk)
12
- super(chunk) do |line|
20
+ super do |line|
13
21
  @output << escape(line)
14
22
  end
15
23
  end
16
24
 
17
25
  def close
18
26
  super do |line|
19
- @output << escape(line) + "\n"
27
+ @output << "#{escape(line)}\n"
20
28
  end
21
29
  end
22
30
  end
23
- end
31
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'demo'
3
- require "digest"
4
+ require 'linefeed'
5
+ require 'digest'
4
6
 
5
7
  # Only outputs at close
6
8
  module Demo
@@ -9,7 +11,7 @@ module Demo
9
11
 
10
12
  def initialize(output)
11
13
  @output = output
12
- @line_digest = Digest("SHA256").new
14
+ @line_digest = Digest('SHA256').new
13
15
 
14
16
  linefeed do |line|
15
17
  @line_digest.update(line)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'demo'
3
- require "digest"
4
+ require 'linefeed'
5
+ require 'digest'
4
6
 
5
7
  # Not actually using Linefeed, but speaking the same protocol,
6
8
  # consuming entire chunks.
@@ -10,7 +12,7 @@ module Demo
10
12
  class ChunkDigest
11
13
  def initialize(output)
12
14
  @output = output
13
- @digest = Digest("SHA256").new
15
+ @digest = Digest('SHA256').new
14
16
  end
15
17
 
16
18
  def <<(chunk)
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
- require_relative 'demo'
3
- require "delegate"
2
+
3
+ require_relative '02_canonicalize'
4
+ require_relative '04_line_digest'
5
+ require 'delegate'
4
6
 
5
7
  # Easy chaining
6
8
  module Demo
7
- class CanonicalizedDigest < DelegateClass(Consumer)
9
+ class CanonicalizedDigest < DelegateClass(Canonicalize)
8
10
  def initialize(output)
9
11
  super(Canonicalize.new(LineDigest.new(output)))
10
12
  end
data/examples/07_null.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'demo'
4
+ require 'linefeed'
3
5
 
4
6
  # Intentionally fails to setup the feed and suffers for it.
5
7
  # Don't do this.
data/examples/demo.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require_relative "../lib/linefeed"
3
- require_relative "demo_helper"
2
+
3
+ require_relative 'demo_helper'
4
4
 
5
5
  if $0 == __FILE__
6
- example_files = Dir[File.join(__dir__, "[0-9][0-9]_*.rb")].sort
6
+ example_files = Dir[File.join(__dir__, '[0-9][0-9]_*.rb')]
7
7
  example_files.each do |path|
8
8
  require_relative File.basename(path)
9
9
  end
@@ -11,10 +11,11 @@ end
11
11
 
12
12
  def run
13
13
  recipients = Demo.setup_examples
14
+ input = Demo.input_pipe(ARGF)
14
15
  maxlen = 8192
15
- chunk = "".b
16
+ chunk = ''.b
16
17
 
17
- while $stdin.read(maxlen, chunk)
18
+ while input.read(maxlen, chunk) && !chunk.empty?
18
19
  recipients.each do |r|
19
20
  r << chunk
20
21
  end
@@ -22,4 +23,5 @@ def run
22
23
  recipients.each(&:close)
23
24
  end
24
25
 
25
- at_exit { run } unless @at_exit_installed; @at_exit_installed = true
26
+ at_exit { run unless $! } unless @at_exit_installed
27
+ @at_exit_installed = true
@@ -1,22 +1,34 @@
1
1
  # frozen_string_literal: true
2
- require_relative 'consumer'
3
2
 
4
3
  module Demo
5
4
  # IO trap
6
5
  class Output
7
6
  def initialize(klass)=@prefix = klass.to_s
8
- def <<(o)=puts "#{@prefix}: #{o.inspect}"
7
+ def <<(obj)=puts "#{@prefix}: #{obj.inspect}"
9
8
  def close()=puts "#{@prefix} closed."
10
9
  end
11
10
 
11
+ # decouple from tty if demo run interactively
12
+ def self.input_pipe(source)
13
+ return source unless $stdin.tty?
14
+
15
+ reader, writer = IO.pipe
16
+ Thread.new do
17
+ IO.copy_stream(source, writer)
18
+ ensure
19
+ writer.close unless writer.closed?
20
+ end
21
+ reader
22
+ end
23
+
12
24
  # Example registry
13
25
  @example_classes = []
14
26
  class << self
15
27
  def const_added(const_name)
16
28
  super
17
- if const_get(const_name, false) in Class => klass
18
- register(klass)
19
- end
29
+ return unless const_get(const_name, false) in Class => klass
30
+
31
+ register(klass)
20
32
  end
21
33
 
22
34
  def register(klass)
@@ -28,4 +40,3 @@ module Demo
28
40
  end
29
41
  end
30
42
  end
31
-
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linefeed
4
+ # Base error for linefeed-specific failures.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when linefeed is started more than once.
8
+ class StartError < Error; end
9
+
10
+ # Raised when operations are attempted after close.
11
+ class ClosedError < Error
12
+ def initialize(message = 'already closed')
13
+ super
14
+ end
15
+ end
16
+
17
+ # Raised when no handler is provided for line processing. Subclass of
18
+ # {ArgumentError}[https://docs.ruby-lang.org/en/master/ArgumentError.html]
19
+ class MissingHandler < ArgumentError; end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linefeed
4
- VERSION = "0.3.0"
4
+ VERSION = '1.0.2'
5
5
  end
data/lib/linefeed.rb CHANGED
@@ -1,51 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "linefeed/version"
3
+ require 'linefeed/version'
4
+ require 'linefeed/errors'
5
+
6
+ # Include Linefeed to enable handling of chunked binary streams as yielded
7
+ # lines.
8
+ #
9
+ # See README for more.
4
10
 
5
11
  module Linefeed
6
- class Error < StandardError; end
12
+ # Set up linefeed processing and install a default handler.
13
+ #
14
+ # call-seq:
15
+ # linefeed { |line| ... } -> self
16
+ #
17
+ # The handler receives each LF-terminated line as a binary +String+ and will
18
+ # be invoked on #<< and #close unless a per-call block is provided. A final
19
+ # unterminated line may be yielded by #close.
20
+ #
21
+ # Raises MissingHandler if no block is given.<br>
22
+ # Raises StartError if linefeed was already started.<br>
23
+ # Raises ClosedError if linefeed was already closed.
24
+ def linefeed(&)
25
+ raise MissingHandler unless block_given?
7
26
 
8
- def linefeed(&default_proc)
9
- raise ArgumentError, "linefeed already called" if @__linefeed_called
10
- @__linefeed_default = default_proc
11
- @__linefeed_buffer = +"".b
12
- @__linefeed_closed = false
13
- @__linefeed_called = true
14
- self
27
+ linefeed_start(&)
15
28
  end
16
29
 
17
- # Called by push-type source to write to us.
18
- def <<(chunk, &per_line)
19
- per_line ||= @__linefeed_default
20
- raise Error, "already closed" if @__linefeed_closed
21
- raise ArgumentError, "no line handler" unless per_line
30
+ # Push a binary chunk to the receiver.
31
+ #
32
+ # call-seq:
33
+ # self << chunk -> self
34
+ # self << chunk { |line| ... } -> self
35
+ #
36
+ # The receiver yields complete LF-terminated lines to the handler.
37
+ # Any # trailing partial line is buffered until the next chunk or #close.
38
+ # Lines will be 8-bit ASCII +String+ values and always include the trailing +\n+.
39
+ #
40
+ # If you override this method, call +super+ with the chunk and pass a block
41
+ # to receive each line:
42
+ #
43
+ # def <<(chunk)
44
+ # super(chunk) { |line| puts escape(line) }
45
+ # end
46
+ #
47
+ # Raises MissingHandler if no handler is given and no default handler was installed.<br>
48
+ # Raises ClosedError if called after #close.
49
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
50
+ def <<(chunk, &handler)
51
+ handler ||= @__linefeed_handler
52
+ raise MissingHandler unless handler
53
+ raise ClosedError if @__linefeed_closed
22
54
 
23
- @__linefeed_called = true
24
- @__linefeed_buffer ||= +"".b
25
- @__linefeed_buffer << chunk
55
+ linefeed_start unless @__linefeed_started
56
+ buf = @__linefeed_buffer
26
57
 
27
- start = 0
28
- while (eol = @__linefeed_buffer.index("\n", start))
29
- per_line.call(@__linefeed_buffer.slice(start..eol)) # includes the "\n"
30
- start = eol + 1
58
+ if chunk.getbyte(-1) == 10
59
+ if buf.empty?
60
+ chunk.each_line("\n", &handler)
61
+ else
62
+ buf << chunk
63
+ buf.each_line("\n", &handler)
64
+ buf.clear
65
+ end
66
+ return self
31
67
  end
32
68
 
33
- if start > 0
34
- @__linefeed_buffer = @__linefeed_buffer.byteslice(start, @__linefeed_buffer.bytesize - start) || +"".b
69
+ buf << chunk
70
+ return self if chunk.index("\n").nil?
71
+
72
+ buf.each_line("\n") do |line|
73
+ if line.getbyte(-1) == 10
74
+ handler.call(line)
75
+ else
76
+ @__linefeed_buffer = line
77
+ end
35
78
  end
36
79
 
37
80
  self
38
81
  end
82
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
83
+
84
+ # Close the stream and flush any buffered data.
85
+ #
86
+ # call-seq:
87
+ # close -> self
88
+ # close { |line| ... } -> self
89
+ #
90
+ # If the buffer contains an unterminated line, it is yielded once to the
91
+ # handler as a binary +String+ without a trailing +\n+.
92
+
93
+ # It is valid to call #close without any prior calls to #<< or #linefeed.
94
+ #
95
+ # If you override this method, call +super+ and pass a block to receive the
96
+ # final partial line:
97
+ #
98
+ # def close
99
+ # super { |line| puts escape(line) }
100
+ # puts "-- all done."
101
+ # end
102
+ #
103
+ # Raises MissingHandler if no handler is given and no default handler was installed.<br>
104
+ # Raises ClosedError if called more than once.
105
+ def close(&handler)
106
+ handler ||= @__linefeed_handler
107
+ raise MissingHandler unless handler
108
+ raise ClosedError if @__linefeed_closed
39
109
 
40
- # Called at end-of-stream.
41
- def close(&per_line)
42
- per_line ||= @__linefeed_default
43
- raise Error, "already closed" if @__linefeed_closed
44
- raise ArgumentError, "no line handler" unless per_line
45
110
  @__linefeed_closed = true
46
111
  return self if !@__linefeed_buffer || @__linefeed_buffer.empty?
47
112
 
48
- per_line.call(@__linefeed_buffer.slice!(0, @__linefeed_buffer.bytesize)) # final unterminated line
113
+ handler.call(@__linefeed_buffer.dup)
114
+ @__linefeed_buffer.clear
115
+ self
116
+ end
117
+
118
+ private
119
+
120
+ def linefeed_start(&handler)
121
+ raise StartError, 'already started' if @__linefeed_started
122
+ raise ClosedError if @__linefeed_closed
123
+
124
+ @__linefeed_handler = handler
125
+ @__linefeed_buffer = +''.b
126
+ @__linefeed_closed = false
127
+ @__linefeed_started = true
49
128
  self
50
129
  end
51
130
  end
metadata CHANGED
@@ -1,56 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linefeed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Goodall
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: minitest
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '0'
19
- type: :development
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: rake
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
- - !ruby/object:Gem::Dependency
41
- name: irb
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0'
11
+ dependencies: []
54
12
  description: Linefeed turns a push-style byte stream, of any chunk size, into individually
55
13
  yielded lines.
56
14
  email: inopinatus@hey.com
@@ -58,6 +16,7 @@ executables: []
58
16
  extensions: []
59
17
  extra_rdoc_files: []
60
18
  files:
19
+ - CHANGELOG.md
61
20
  - LICENSE
62
21
  - README.md
63
22
  - examples/01_logger.rb
@@ -67,18 +26,20 @@ files:
67
26
  - examples/05_chunk_digest.rb
68
27
  - examples/06_canonicalized_digest.rb
69
28
  - examples/07_null.rb
70
- - examples/consumer.rb
71
29
  - examples/demo.rb
72
30
  - examples/demo_helper.rb
73
31
  - lib/linefeed.rb
32
+ - lib/linefeed/errors.rb
74
33
  - lib/linefeed/version.rb
75
- - test/test_linefeed.rb
76
- homepage: https://github.com/inopinatus/linefeed
34
+ homepage: https://inopinatus.github.io/linefeed/
77
35
  licenses:
78
36
  - MIT
79
37
  metadata:
80
- homepage_uri: https://github.com/inopinatus/linefeed
38
+ homepage_uri: https://inopinatus.github.io/linefeed/
81
39
  source_code_uri: https://github.com/inopinatus/linefeed
40
+ changelog_uri: https://github.com/inopinatus/linefeed/blob/main/CHANGELOG.md
41
+ bug_tracker_uri: https://github.com/inopinatus/linefeed/issues
42
+ rubygems_mfa_required: 'true'
82
43
  rdoc_options: []
83
44
  require_paths:
84
45
  - lib
data/examples/consumer.rb DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # simple base for demos
4
- module Demo
5
- class Consumer
6
- include Linefeed
7
-
8
- def initialize(output)
9
- @output = output
10
-
11
- linefeed do |line|
12
- output << process_line(line)
13
- end
14
- end
15
- end
16
- end
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "minitest/autorun"
4
- require_relative "../lib/linefeed"
5
-
6
- class LinefeedTest < Minitest::Test
7
- class StandardReceiver
8
- include Linefeed
9
- attr_reader :lines
10
-
11
- def initialize
12
- @lines = []
13
- linefeed { |line| @lines << line }
14
- end
15
- end
16
-
17
- class CustomReceiver
18
- include Linefeed
19
- attr_reader :lines
20
-
21
- def initialize
22
- @lines = []
23
- end
24
-
25
- def <<(chunk)
26
- super do |line|
27
- @lines << "line:#{line}"
28
- end
29
- end
30
-
31
- def close
32
- super do |line|
33
- @lines << "eof:#{line}"
34
- end
35
- end
36
- end
37
-
38
- def test_basic_yield
39
- receiver = StandardReceiver.new
40
- receiver << "a\nb\n"
41
-
42
- assert_equal ["a\n", "b\n"], receiver.lines
43
- end
44
-
45
- def test_works_across_chunks
46
- receiver = StandardReceiver.new
47
- receiver << "a"
48
- receiver << "\n"
49
- receiver << "b"
50
- receiver << "\n"
51
-
52
- assert_equal ["a\n", "b\n"], receiver.lines
53
- end
54
-
55
- def test_flush_unterminated
56
- receiver = StandardReceiver.new
57
- receiver << "tail"
58
- receiver.close
59
-
60
- assert_equal ["tail"], receiver.lines
61
- end
62
-
63
- def test_empty_close_does_nothing
64
- receiver = StandardReceiver.new
65
- receiver.close
66
-
67
- assert_equal [], receiver.lines
68
- end
69
-
70
- def test_you_forget_the_handlers
71
- obj = Object.new
72
- obj.extend(Linefeed)
73
-
74
- assert_raises(ArgumentError) { obj << "a\n" }
75
- assert_raises(ArgumentError) { obj.close }
76
- end
77
-
78
- def test_raise_after_close
79
- receiver = StandardReceiver.new
80
- receiver.close
81
-
82
- assert_raises(Linefeed::Error) { receiver << "a\n" }
83
- assert_raises(Linefeed::Error) { receiver.close }
84
- end
85
-
86
- def test_custom_handlers
87
- receiver = CustomReceiver.new
88
-
89
- receiver << "a"
90
- receiver << "\n"
91
- receiver << "b"
92
- receiver.close
93
- assert_equal ["line:a\n", "eof:b"], receiver.lines
94
- end
95
- end