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 +4 -4
- data/CHANGELOG.md +22 -0
- data/LICENSE +1 -1
- data/README.md +10 -16
- data/examples/01_logger.rb +4 -1
- data/examples/02_canonicalize.rb +13 -5
- data/examples/03_escaped.rb +13 -5
- data/examples/04_line_digest.rb +4 -2
- data/examples/05_chunk_digest.rb +4 -2
- data/examples/06_canonicalized_digest.rb +5 -3
- data/examples/07_null.rb +2 -0
- data/examples/demo.rb +8 -6
- data/examples/demo_helper.rb +17 -6
- data/lib/linefeed/errors.rb +20 -0
- data/lib/linefeed/version.rb +1 -1
- data/lib/linefeed.rb +108 -29
- metadata +9 -48
- data/examples/consumer.rb +0 -16
- data/test/test_linefeed.rb +0 -95
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7ee8b08d068e093abefa6cefe66b22278a0fee127b393d17944d5c96893966a
|
|
4
|
+
data.tar.gz: 9a5d006b788929240e54c48c5134779fed09d16fdcb88cf2d89b9442a3b7173c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Linefeed
|
|
2
2
|
|
|
3
|
-
Linefeed turns a
|
|
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
|
|
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 `
|
|
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| ... }
|
|
32
|
-
|
|
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
|
|
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)
|
|
83
|
+
MIT license. Copyright (c) 2026 inopinatus
|
|
90
84
|
|
|
91
85
|
## Contributing
|
|
92
86
|
|
|
93
|
-
|
|
87
|
+
Visit https://github.com/inopinatus/linefeed to open a PR.
|
data/examples/01_logger.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
|
# 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
|
-
|
|
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
|
data/examples/02_canonicalize.rb
CHANGED
|
@@ -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
|
|
7
|
-
|
|
8
|
-
|
|
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]+$/,
|
|
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
|
data/examples/03_escaped.rb
CHANGED
|
@@ -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
|
|
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 )/,
|
|
16
|
+
line.sub(/^(-|From )/, '- \\1')
|
|
9
17
|
end
|
|
10
18
|
|
|
11
19
|
def <<(chunk)
|
|
12
|
-
super
|
|
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)
|
|
27
|
+
@output << "#{escape(line)}\n"
|
|
20
28
|
end
|
|
21
29
|
end
|
|
22
30
|
end
|
|
23
|
-
end
|
|
31
|
+
end
|
data/examples/04_line_digest.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require_relative 'demo'
|
|
3
|
-
require
|
|
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(
|
|
14
|
+
@line_digest = Digest('SHA256').new
|
|
13
15
|
|
|
14
16
|
linefeed do |line|
|
|
15
17
|
@line_digest.update(line)
|
data/examples/05_chunk_digest.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require_relative 'demo'
|
|
3
|
-
require
|
|
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(
|
|
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
|
-
|
|
3
|
-
|
|
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(
|
|
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
data/examples/demo.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative
|
|
2
|
+
|
|
3
|
+
require_relative 'demo_helper'
|
|
4
4
|
|
|
5
5
|
if $0 == __FILE__
|
|
6
|
-
example_files = Dir[File.join(__dir__,
|
|
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 =
|
|
16
|
+
chunk = ''.b
|
|
16
17
|
|
|
17
|
-
while
|
|
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
|
|
26
|
+
at_exit { run unless $! } unless @at_exit_installed
|
|
27
|
+
@at_exit_installed = true
|
data/examples/demo_helper.rb
CHANGED
|
@@ -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 <<(
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
data/lib/linefeed/version.rb
CHANGED
data/lib/linefeed.rb
CHANGED
|
@@ -1,51 +1,130 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
@__linefeed_buffer
|
|
25
|
-
@__linefeed_buffer << chunk
|
|
55
|
+
linefeed_start unless @__linefeed_started
|
|
56
|
+
buf = @__linefeed_buffer
|
|
26
57
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
data/test/test_linefeed.rb
DELETED
|
@@ -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
|