flat_kit 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +1 -2
  3. data/HISTORY.md +15 -0
  4. data/Manifest.txt +21 -26
  5. data/{bin → exe}/fk +2 -1
  6. data/flat_kit.gemspec +33 -0
  7. data/lib/flat_kit/cli.rb +48 -23
  8. data/lib/flat_kit/command/cat.rb +34 -32
  9. data/lib/flat_kit/command/merge.rb +37 -36
  10. data/lib/flat_kit/command/sort.rb +37 -37
  11. data/lib/flat_kit/command/stats.rb +96 -0
  12. data/lib/flat_kit/command.rb +10 -10
  13. data/lib/flat_kit/descendant_tracker.rb +17 -5
  14. data/lib/flat_kit/error.rb +4 -0
  15. data/lib/flat_kit/event_emitter.rb +7 -4
  16. data/lib/flat_kit/field_stats.rb +246 -0
  17. data/lib/flat_kit/field_type/boolean_type.rb +52 -0
  18. data/lib/flat_kit/field_type/date_type.rb +181 -0
  19. data/lib/flat_kit/field_type/float_type.rb +43 -0
  20. data/lib/flat_kit/field_type/guess_type.rb +23 -0
  21. data/lib/flat_kit/field_type/integer_type.rb +36 -0
  22. data/lib/flat_kit/field_type/null_type.rb +39 -0
  23. data/lib/flat_kit/field_type/string_type.rb +24 -0
  24. data/lib/flat_kit/field_type/timestamp_type.rb +48 -0
  25. data/lib/flat_kit/field_type/unknown_type.rb +30 -0
  26. data/lib/flat_kit/field_type.rb +83 -0
  27. data/lib/flat_kit/format.rb +11 -5
  28. data/lib/flat_kit/input/file.rb +11 -9
  29. data/lib/flat_kit/input/io.rb +18 -21
  30. data/lib/flat_kit/input.rb +8 -7
  31. data/lib/flat_kit/internal_node.rb +22 -19
  32. data/lib/flat_kit/jsonl/format.rb +6 -2
  33. data/lib/flat_kit/jsonl/reader.rb +7 -4
  34. data/lib/flat_kit/jsonl/record.rb +16 -19
  35. data/lib/flat_kit/jsonl/writer.rb +25 -18
  36. data/lib/flat_kit/jsonl.rb +8 -4
  37. data/lib/flat_kit/leaf_node.rb +6 -5
  38. data/lib/flat_kit/log_formatter.rb +20 -0
  39. data/lib/flat_kit/logger.rb +12 -19
  40. data/lib/flat_kit/merge.rb +21 -16
  41. data/lib/flat_kit/merge_tree.rb +5 -6
  42. data/lib/flat_kit/output/file.rb +13 -9
  43. data/lib/flat_kit/output/io.rb +40 -35
  44. data/lib/flat_kit/output.rb +12 -7
  45. data/lib/flat_kit/position.rb +18 -0
  46. data/lib/flat_kit/reader.rb +8 -8
  47. data/lib/flat_kit/record.rb +12 -12
  48. data/lib/flat_kit/sentinel_internal_node.rb +6 -5
  49. data/lib/flat_kit/sentinel_leaf_node.rb +4 -1
  50. data/lib/flat_kit/sort.rb +8 -9
  51. data/lib/flat_kit/stat_type/nominal_stats.rb +64 -0
  52. data/lib/flat_kit/stat_type/numerical_stats.rb +120 -0
  53. data/lib/flat_kit/stat_type/ordinal_stats.rb +37 -0
  54. data/lib/flat_kit/stat_type.rb +70 -0
  55. data/lib/flat_kit/stats.rb +64 -0
  56. data/lib/flat_kit/writer.rb +17 -3
  57. data/lib/flat_kit/xsv/format.rb +6 -2
  58. data/lib/flat_kit/xsv/reader.rb +8 -6
  59. data/lib/flat_kit/xsv/record.rb +21 -15
  60. data/lib/flat_kit/xsv/writer.rb +36 -18
  61. data/lib/flat_kit/xsv.rb +7 -4
  62. data/lib/flat_kit.rb +33 -21
  63. metadata +38 -113
  64. data/Rakefile +0 -20
  65. data/tasks/default.rake +0 -242
  66. data/tasks/extension.rake +0 -38
  67. data/tasks/man.rake +0 -7
  68. data/tasks/this.rb +0 -208
  69. data/test/device_dataset.rb +0 -117
  70. data/test/input/test_file.rb +0 -73
  71. data/test/input/test_io.rb +0 -93
  72. data/test/jsonl/test_format.rb +0 -22
  73. data/test/jsonl/test_reader.rb +0 -49
  74. data/test/jsonl/test_record.rb +0 -61
  75. data/test/jsonl/test_writer.rb +0 -68
  76. data/test/output/test_file.rb +0 -60
  77. data/test/output/test_io.rb +0 -104
  78. data/test/test_conversions.rb +0 -45
  79. data/test/test_event_emitter.rb +0 -72
  80. data/test/test_format.rb +0 -24
  81. data/test/test_helper.rb +0 -26
  82. data/test/test_merge.rb +0 -40
  83. data/test/test_merge_tree.rb +0 -64
  84. data/test/test_version.rb +0 -11
  85. data/test/xsv/test_format.rb +0 -22
  86. data/test/xsv/test_reader.rb +0 -61
  87. data/test/xsv/test_record.rb +0 -69
  88. data/test/xsv/test_writer.rb +0 -68
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: Represeting floating point data and conversion to it
6
+ #
7
+ class FloatType < FieldType
8
+ def self.type_name
9
+ "float"
10
+ end
11
+
12
+ def self.matches?(data)
13
+ case data
14
+ when Float
15
+ true
16
+ when Integer
17
+ false
18
+ when String
19
+ return false if IntegerType.matches?(data)
20
+
21
+ maybe_float?(data)
22
+ else
23
+ false
24
+ end
25
+ end
26
+
27
+ def self.coerce(data)
28
+ Float(data)
29
+ rescue TypeError => _e
30
+ CoerceFailure
31
+ rescue ArgumentError => _e
32
+ CoerceFailure
33
+ end
34
+
35
+ def self.maybe_float?(data)
36
+ Float(data)
37
+ true
38
+ rescue ArgumentError => _e
39
+ false
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: GuessType is a field type where we don't know what type the
6
+ # field is, and it needs to be guessed. This is a sentinel type that doesn't
7
+ # match any data.
8
+ #
9
+ class GuessType < FieldType
10
+ def self.type_name
11
+ name
12
+ end
13
+
14
+ def self.matches?(*)
15
+ false
16
+ end
17
+
18
+ def self.coerce(*)
19
+ CoerceFailure
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: Class reprepseting the Integer type and coercian to it.
6
+ #
7
+ class IntegerType < FieldType
8
+ REGEX = /\A[-+]?\d+\Z/
9
+
10
+ def self.type_name
11
+ "integer"
12
+ end
13
+
14
+ def self.matches?(data)
15
+ case data
16
+ when Integer
17
+ true
18
+ when Float
19
+ false
20
+ when String
21
+ REGEX.match?(data)
22
+ else
23
+ false
24
+ end
25
+ end
26
+
27
+ def self.coerce(data)
28
+ Integer(data)
29
+ rescue TypeError => _e
30
+ CoerceFailure
31
+ rescue ArgumentError => _e
32
+ CoerceFailure
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: Class reprepseting the null type and coercian to it.
6
+ #
7
+ class NullType < FieldType
8
+ REGEX = Regexp.union(/\A(null|nil)\Z/i, /\A\\N\Z/)
9
+
10
+ def self.type_name
11
+ "null"
12
+ end
13
+
14
+ def self.matches?(data)
15
+ case data
16
+ when nil
17
+ true
18
+ when String
19
+ REGEX.match?(data)
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def self.coerce(data)
26
+ case data
27
+ when nil
28
+ data
29
+ when String
30
+ return nil if REGEX.match?(data)
31
+
32
+ CoerceFailure
33
+ else
34
+ CoerceFailure
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: StringType is essentially a fallback - hence its lower weight
6
+ # than other types that might have string representations.
7
+ #
8
+ class StringType < FieldType
9
+ def self.type_name
10
+ "string"
11
+ end
12
+
13
+ def self.matches?(data)
14
+ data.is_a?(String)
15
+ end
16
+
17
+ def self.coerce(data)
18
+ data.to_s
19
+ rescue StandardError => _e
20
+ CoerceFailure
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: Type for all tiemstamps types more granular than Date.
6
+ #
7
+ class TimestampType < FieldType
8
+ def self.parse_formats
9
+ @parse_formats ||= [
10
+ "%Y-%m-%d %H:%M:%S.%NZ",
11
+ "%Y-%m-%d %H:%M:%S.%N",
12
+ "%Y-%m-%dT%H:%M:%S.%N%z", # w3cdtf
13
+ "%Y-%m-%d %H:%M:%S",
14
+ "%Y-%m-%dT%H:%M:%S%z",
15
+ "%Y-%m-%dT%H:%M:%SZ",
16
+ "%Y%m%dT%H%M%S",
17
+ "%a, %d %b %Y %H:%M:%S %z", # rfc2822, httpdate
18
+ ].freeze
19
+ end
20
+
21
+ def self.type_name
22
+ "timestamp"
23
+ end
24
+
25
+ def self.matches?(data)
26
+ coerced = coerce(data)
27
+ coerced.is_a?(Time)
28
+ end
29
+
30
+ def self.coerce(data)
31
+ case data
32
+ when Time
33
+ data
34
+ when String
35
+ parse_formats.each do |format|
36
+ coerced_data = Time.strptime(data, format).utc
37
+ return coerced_data
38
+ rescue StandardError => _e
39
+ # do nothing
40
+ end
41
+ CoerceFailure
42
+ else
43
+ CoerceFailure
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ class FieldType
5
+ # Internal: Unknown type, this is what we use for unknown values in the data
6
+ #
7
+ class UnknownType < FieldType
8
+ REGEX = %r{\A(na|n/a|unk|unknown)\Z}i
9
+
10
+ def self.type_name
11
+ "unknown"
12
+ end
13
+
14
+ def self.matches?(data)
15
+ return false unless data.is_a?(String)
16
+ return true if data.empty?
17
+
18
+ REGEX.match?(data)
19
+ end
20
+
21
+ def self.coerce(data)
22
+ return data if REGEX.match?(data)
23
+
24
+ CoerceFailure
25
+ rescue StandardError
26
+ CoerceFailure
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlatKit
4
+ # Internal: The base class for all field types
5
+ #
6
+ class FieldType
7
+ extend FlatKit::DescendantTracker
8
+
9
+ CoerceFailure = Class.new(::Object).freeze
10
+
11
+ def self.weights
12
+ @weights ||= {
13
+ # Boolean has crossover with Integer so going to let it overrule Integer
14
+ BooleanType => 5,
15
+
16
+ # Integer could potentially overlap with Float, but it is more restrictive
17
+ # so let it override Flaot
18
+ IntegerType => 4,
19
+ FloatType => 3,
20
+
21
+ # Date and Timestamps string representation shouldn't intersect with anything so
22
+ # leaving it at the same level as Null and Unkonwn
23
+ DateType => 2,
24
+ TimestampType => 2,
25
+
26
+ # Null and Unknown shoulnd't conflict since their string representations
27
+ # do not intersect
28
+ NullType => 2,
29
+ UnknownType => 2,
30
+
31
+ # Stringtype is the fallback for anything that has a string
32
+ # representation, so it should lose out on integers, floats, nulls,
33
+ # unknowns as strings
34
+ StringType => 1,
35
+
36
+ # at the bottom - since it should never match anywhere
37
+ GuessType => 0,
38
+ }
39
+ end
40
+
41
+ def self.candidate_types(data)
42
+ find_children(:matches?, data)
43
+ end
44
+
45
+ # rubocop:disable Style/RedundantSort
46
+ # We need the stable sort, max_by(&:weight) returns the wrong one
47
+ def self.best_guess(data)
48
+ candidate_types(data).sort_by(&:weight).last
49
+ end
50
+ # rubocop:enable Style/RedundantSort
51
+
52
+ def self.type_name
53
+ raise NotImplementedError, "must impleent #{type_name}"
54
+ end
55
+
56
+ def self.matches?(data)
57
+ raise NotImplementedError, "must implement #{name}.matches?(data)"
58
+ end
59
+
60
+ def self.coerce(data)
61
+ raise NotImplementedError, "must implement #{name}.coerce(data)"
62
+ end
63
+
64
+ # Each type has a weight so if a value matches multiple types, then the list
65
+ # can be compared to see where the tie breakers are
66
+ #
67
+ # All the weights are here so that we can see the order of precedence
68
+ #
69
+ def self.weight
70
+ weights.fetch(self) { raise NotImplementedError, "No weight assigned to type #{self} - fix immediately" }
71
+ end
72
+ end
73
+ end
74
+
75
+ require "flat_kit/field_type/guess_type"
76
+ require "flat_kit/field_type/boolean_type"
77
+ require "flat_kit/field_type/date_type"
78
+ require "flat_kit/field_type/timestamp_type"
79
+ require "flat_kit/field_type/integer_type"
80
+ require "flat_kit/field_type/float_type"
81
+ require "flat_kit/field_type/null_type"
82
+ require "flat_kit/field_type/string_type"
83
+ require "flat_kit/field_type/unknown_type"
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FlatKit
4
+ # Internal: The base class of all data file format classes
5
+ #
2
6
  class Format
3
7
  extend DescendantTracker
4
8
 
5
9
  def self.format_name
6
- raise NotImplementedError, "#{self.class} must implemente #{self.class}.format_name"
10
+ raise NotImplementedError, "#{self.class} must implement #{self.class}.format_name"
7
11
  end
8
12
 
9
13
  def format_name
@@ -20,15 +24,17 @@ module FlatKit
20
24
  return format unless format.nil?
21
25
 
22
26
  # now try the fallback
23
- format = ::FlatKit::Format.for(fallback)
24
- return format
27
+ ::FlatKit::Format.for(fallback)
25
28
  end
26
29
 
27
30
  def self.for_with_fallback!(path:, fallback: "auto")
28
31
  format = for_with_fallback(path: path, fallback: fallback)
29
- raise ::FlatKit::Error::UnknownFormat, "Unable to figure out format for '#{path}' with fallback '#{fallback}'" if format.nil?
32
+ if format.nil?
33
+ raise ::FlatKit::Error::UnknownFormat,
34
+ "Unable to figure out format for '#{path}' with fallback '#{fallback}'"
35
+ end
30
36
 
31
- return format
37
+ format
32
38
  end
33
39
  end
34
40
  end
@@ -1,25 +1,31 @@
1
- require 'zlib'
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require "pathname"
2
5
 
3
6
  module FlatKit
4
7
  class Input
8
+ # Internal: Handler for file based input
9
+ #
5
10
  class File < Input
6
- attr_reader :path
7
- attr_reader :count
11
+ attr_reader :path, :count, :io
8
12
 
9
13
  def self.handles?(obj)
10
14
  return true if obj.instance_of?(Pathname)
11
15
  return false unless obj.instance_of?(String)
12
16
 
13
17
  # incase these get loaded in different orders
14
- return false if ::FlatKit::Input::IO.is_stdin?(obj)
18
+ return false if ::FlatKit::Input::IO.stdin?(obj)
15
19
 
16
- return true
20
+ true
17
21
  end
18
22
 
19
23
  def initialize(obj)
24
+ super()
20
25
  @count = 0
21
26
  @path = Pathname.new(obj)
22
27
  raise FlatKit::Error, "Input #{obj} is not readable" unless @path.readable?
28
+
23
29
  @io = open_input(path)
24
30
  end
25
31
 
@@ -31,10 +37,6 @@ module FlatKit
31
37
  @io.close
32
38
  end
33
39
 
34
- def io
35
- @io
36
- end
37
-
38
40
  private
39
41
 
40
42
  # open the opropriate input type depending on the source file name
@@ -1,35 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FlatKit
2
4
  class Input
5
+ # Internal: Handler for non-filebased input. Generally this is just stdin
6
+ #
3
7
  class IO < Input
4
- STDINS = %w[ stdin STDIN - <stdin> ]
8
+ STDINS = %w[stdin STDIN - <stdin>].freeze
5
9
 
6
10
  def self.handles?(obj)
7
- return true if is_stdin?(obj)
8
- return true if [ ::File, ::StringIO, ::IO ].any? { |klass| obj.kind_of?(klass) }
9
- return false
11
+ return true if stdin?(obj)
12
+ return true if [::File, ::StringIO, ::IO].any? { |klass| obj.is_a?(klass) }
13
+
14
+ false
10
15
  end
11
16
 
12
- def self.is_stdin?(obj)
17
+ def self.stdin?(obj)
13
18
  case obj
14
19
  when String
15
20
  return true if STDINS.include?(obj)
16
21
  when ::IO
17
- return true if obj == ::STDIN
22
+ return true if obj == $stdin
18
23
  end
19
- return false
24
+ false
20
25
  end
21
26
 
22
27
  def initialize(obj)
23
- if self.class.is_stdin?(obj) then
28
+ super()
29
+ if self.class.stdin?(obj)
24
30
  @name = "<STDIN>"
25
31
  @io = $stdin
26
- elsif obj.kind_of?(::File) then
27
- @name = obj.path
28
- @io = obj
29
- elsif obj.kind_of?(::StringIO) then
30
- @name = obj.inspect
32
+ elsif obj.is_a?(::IO)
33
+ @name = (obj.respond_to?(:path) && obj.path) || obj.inspect
31
34
  @io = obj
32
- elsif obj.kind_of?(::IO) then
35
+ elsif obj.is_a?(::StringIO)
33
36
  @name = obj.inspect
34
37
  @io = obj
35
38
  else
@@ -37,18 +40,12 @@ module FlatKit
37
40
  end
38
41
  end
39
42
 
40
- def name
41
- @name
42
- end
43
+ attr_reader :name, :io
43
44
 
44
45
  # this goes to an io stream and we are not in charge of opening it
45
46
  def close
46
47
  @io.close
47
48
  end
48
-
49
- def io
50
- @io
51
- end
52
49
  end
53
50
  end
54
51
  end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FlatKit
4
+ # Internal: Base class of all input handlers
5
+ #
2
6
  class Input
3
7
  extend DescendantTracker
4
8
 
5
9
  def self.from(input)
6
- return input if input.kind_of?(::FlatKit::Input)
10
+ return input if input.is_a?(::FlatKit::Input)
7
11
 
8
12
  in_klass = find_child(:handles?, input)
9
- if in_klass then
10
- return in_klass.new(input)
11
- end
13
+ return in_klass.new(input) if in_klass
12
14
 
13
15
  raise FlatKit::Error, "Unable to create input from #{input.class} : #{input.inspect}"
14
16
  end
@@ -17,7 +19,6 @@ module FlatKit
17
19
  raise NotImplementedError, "#{self.class} must implement #name"
18
20
  end
19
21
 
20
- #
21
22
  def io
22
23
  raise NotImplementedError, "#{self.class} must implement #io"
23
24
  end
@@ -28,5 +29,5 @@ module FlatKit
28
29
  end
29
30
  end
30
31
 
31
- require 'flat_kit/input/io'
32
- require 'flat_kit/input/file'
32
+ require "flat_kit/input/io"
33
+ require "flat_kit/input/file"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FlatKit
2
4
  # Private: This is a class used internally by MergeTree and should not be used
3
5
  # outside of that context.
@@ -10,22 +12,24 @@ module FlatKit
10
12
  # here.
11
13
  #
12
14
  class InternalNode
13
-
14
15
  include Comparable
15
16
 
16
- attr_accessor :left # Internal Node
17
- attr_accessor :right # Internal Node
18
- attr_accessor :winner # Internal Node
19
- attr_accessor :next_level # Who to tell
20
- attr_accessor :leaf # winning leaf node
17
+ # Internal Nodes
18
+ attr_accessor :left, :right, :winner
19
+
20
+ # Who to tell
21
+ attr_accessor :next_level
22
+
23
+ # winning leaf node
24
+ attr_accessor :leaf
21
25
 
22
26
  def initialize(left:, right:)
23
- @left = left
27
+ @left = left
24
28
  @left.next_level = self
25
29
 
26
- @right = right
30
+ @right = right
27
31
  @right.next_level = self
28
- @next_level = nil
32
+ @next_level = nil
29
33
 
30
34
  play
31
35
  end
@@ -53,32 +57,31 @@ module FlatKit
53
57
  # from the tree.
54
58
  #
55
59
  def player_finished(node)
56
- if left.object_id == node.object_id then
60
+ if left.equal?(node)
57
61
  @left = SentinelInternalNode.new
58
62
  @left.next_level = self
59
- elsif right.object_id == node.object_id then
63
+ elsif right.equal?(node)
60
64
  @right = SentinelInternalNode.new
61
65
  @right.next_level = self
62
66
  else
63
67
  raise FlatKit::Error, "Unknown player #{node}"
64
68
  end
65
69
 
66
- if @right.sentinel? && @left.sentinel? then
67
- next_level.player_finished(self) if next_level
68
- end
70
+ return unless @right.sentinel? && @left.sentinel?
71
+
72
+ next_level.player_finished(self) if next_level
69
73
  end
70
74
 
71
75
  def play
72
- @winner = left <= right ? left : right
73
- if !@winner.sentinel? then
74
- @leaf = winner.leaf
75
- end
76
+ @winner = (left <= right) ? left : right
77
+ @leaf = winner.leaf unless @winner.sentinel?
76
78
  next_level.play if next_level
77
79
  end
78
80
 
79
81
  def <=>(other)
80
82
  return -1 if other.sentinel?
81
- value.<=>(other.value)
83
+
84
+ value <=> (other.value)
82
85
  end
83
86
  end
84
87
  end
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FlatKit
2
4
  module Jsonl
5
+ # Internal: JSONL format class holding the metadata about the JSONL format
6
+ #
3
7
  class Format < ::FlatKit::Format
4
8
  def self.format_name
5
9
  "jsonl"
@@ -7,10 +11,10 @@ module FlatKit
7
11
 
8
12
  def self.handles?(filename)
9
13
  parts = filename.split(".")
10
- %w[ json jsonl ndjson ].each do |ext|
14
+ %w[json jsonl ndjson].each do |ext|
11
15
  return true if parts.include?(ext)
12
16
  end
13
- return false
17
+ false
14
18
  end
15
19
 
16
20
  def self.reader
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FlatKit
2
4
  module Jsonl
5
+ # Internal: Reader class that parses and yields records from JSONL files
6
+ #
3
7
  class Reader < ::FlatKit::Reader
4
- attr_reader :input
5
- attr_reader :count
8
+ attr_reader :input, :count
6
9
 
7
10
  def self.format_name
8
11
  ::FlatKit::Jsonl::Format.format_name
@@ -15,13 +18,13 @@ module FlatKit
15
18
  end
16
19
 
17
20
  def each
18
- while line = input.io.gets do
21
+ while (line = input.io.gets)
19
22
  record = ::FlatKit::Jsonl::Record.new(data: line, compare_fields: compare_fields)
20
23
  @count += 1
21
24
  yield record
22
25
  end
23
26
  input.close
24
- rescue => e
27
+ rescue StandardError => e
25
28
  ::FlatKit.logger.error "Error reading jsonl records from #{input.name}: #{e}"
26
29
  raise ::FlatKit::Error, e
27
30
  end