honey_format 0.17.0 → 0.21.1

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: 997883e33f720a7dc6c0d3f2a552b4dcd68cfa1bac8be23d5a36332c83bf0e32
4
- data.tar.gz: dd522a2171b7befc1e41e80022dade22d18d3d9228c8b40eb6b7d3f8d032005b
3
+ metadata.gz: dd0d06f453986109809c196cef032b5f60233e165a8d5e029162b26b6cc801b2
4
+ data.tar.gz: 8f5a15f42e9f2587bc80b7e88d5c929d74a04ef69bd6be272513ba8fc544a22a
5
5
  SHA512:
6
- metadata.gz: c6e15af7156b78f97f02e1e42f9c961116ad7e09428ccaa6eb7b0651d93f162f2417381d034b82eaefa843d0b5aceab1cfb824347db1701ed5186d575c6d88eb
7
- data.tar.gz: dfa13256a8446d9b7f6bedf4505e9c275268999098befd62a81174ef5d682185ea4e379e07b870e3554ecdb983714bac31b1175422dd750dc07d6997b52233b9
6
+ metadata.gz: 563e6ba6a8ede49e13d15957c12c247b6e26c981afe25b150d6ae8c6c3dab58929c100341dcc0181790a03eabffca5ddd3ac127cf97fcdb4ac76ef04b01c9839
7
+ data.tar.gz: 93a08cb9b2683961e80f8f2bb79f763f05fb1401e112458a6188bf86530233e93f9d397b0ee415e4ce9c8b95ff644485e50dc6b52a2c8381aa4379b0bce0e65a
@@ -1,7 +1,28 @@
1
1
  language: ruby
2
- rvm:
3
- - 2.3.0
4
- - 2.4.0
5
- - 2.5.0
6
- - 2.6.0-preview1
7
- before_install: gem install bundler -v 1.16.1
2
+ script:
3
+ - bundle exec rspec
4
+ matrix:
5
+ fast_finish: true
6
+ allow_failures:
7
+ - name: TruffleRuby
8
+ rvm: system
9
+ include:
10
+ - rvm: 2.3.0
11
+ install:
12
+ - gem install bundler --no-document
13
+ - bundle install
14
+ - rvm: 2.5.1
15
+ install:
16
+ - gem install bundler --no-document
17
+ - bundle install
18
+ - rvm: 2.6.0-preview3
19
+ install:
20
+ - bundle install
21
+ - name: TruffleRuby
22
+ rvm: system
23
+ install:
24
+ - export TRUFFLERUBY_VERSION=1.0.0-rc10
25
+ - curl -L https://github.com/oracle/truffleruby/releases/download/vm-$TRUFFLERUBY_VERSION/truffleruby-$TRUFFLERUBY_VERSION-linux-amd64.tar.gz | tar xz
26
+ - export PATH="$PWD/truffleruby-$TRUFFLERUBY_VERSION-linux-amd64/bin:$PATH"
27
+ - gem install bundler -v 1.16.6 --no-ri --no-rdoc
28
+ - bundle install
@@ -1,5 +1,42 @@
1
1
  # HEAD
2
2
 
3
+ ## v0.21.1
4
+
5
+ * Closes [issue #58](https://github.com/buren/honey_format/issues/58). [PR #62](https://github.com/buren/honey_format/pull/62)
6
+
7
+ ## v0.21.0
8
+
9
+ * Add `Rows#[]` method
10
+
11
+ ## v0.20.0
12
+
13
+ * Support additional header variant, pass hash with `String => Symbol` and/or `String => #call` (callable object). Unmapped keys are converted using the default header converter.
14
+ ```ruby
15
+ converter = {
16
+ 'First name' => :first_name,
17
+ 'Last name' => -> { :surname }
18
+ }
19
+ csv = HoneyFormat::CSV.new(csv_string, header_converter: converter)
20
+ ```
21
+ * Add `encoding` option to `CSV`
22
+
23
+ ## v0.19.0
24
+
25
+ * Add `method_name` as alias for `header_column` converter
26
+ * Freeze constants in `HeaderColumnConverter`
27
+ * Add support for passing a callable object in `type_map` argument [PR#49](https://github.com/buren/honey_format/pull/49)
28
+ * Remove non-printable & zero-width characters from header columns
29
+ * Add `£` to header separator character list
30
+ * Replace `@` with `_at_` in header converter
31
+ * Replace all space like chars in Header converter. Closes [Issue#39]([PR#49](https://github.com/buren/honey_format/issues/39))
32
+ * Improved CLI output when input path is missing
33
+
34
+ ## v0.18.0
35
+
36
+ * Require `set` class in `ConvertBoolean` - fixes crash when set is already required in environment
37
+ * Allow symbols to be passed to `HeaderColumnConverter`
38
+ * Replace `.` and `,` with `_` in header column converter
39
+
3
40
  ## v0.17.0
4
41
 
5
42
  :warning: This release contains some backwards compatible changes.
data/README.md CHANGED
@@ -27,8 +27,9 @@ Id,Username,Email
27
27
  2,jacob,jacob@example.com
28
28
  CSV
29
29
  csv = HoneyFormat::CSV.new(csv_string, type_map: { id: :integer })
30
- csv.columns # => [:id, :username]
31
- user = csv.rows # => [#<Row id=1, username="buren">]
30
+ csv.columns # => [:id, :username, :email]
31
+ csv.rows # => [#<Row id=1, username="buren", email="buren@example.com">, #<Row id=2, username="jacob", email="jacob@example.com">]
32
+ user = csv.rows.first
32
33
  user.id # => 1
33
34
  user.username # => "buren"
34
35
 
@@ -90,7 +91,7 @@ __Type converters__
90
91
 
91
92
  > Type converters are great if you want to convert column values, like numbers and dates.
92
93
 
93
- There are a few default type converters
94
+ There are a bunch of [default type converters](https://github.com/buren/honey_format/blob/master/lib/honey_format/converters/converters.rb)
94
95
  ```ruby
95
96
  csv_string = "Id,Username\n1,buren"
96
97
  type_map = { id: :integer }
@@ -98,7 +99,15 @@ csv = HoneyFormat::CSV.new(csv_string, type_map: type_map)
98
99
  csv.rows.first.id # => 1
99
100
  ```
100
101
 
101
- Add your own converter
102
+ Pass your own
103
+ ```ruby
104
+ csv_string = "Id,Username\n1,buren"
105
+ type_map = { username: proc { |v| v.upcase } }
106
+ csv = HoneyFormat::CSV.new(csv_string, type_map: type_map)
107
+ csv.rows.first.username # => "BUREN"
108
+ ```
109
+
110
+ Register your own converter
102
111
  ```ruby
103
112
  HoneyFormat.configure do |config|
104
113
  config.converter_registry.register :upcased, proc { |v| v.upcase }
@@ -125,7 +134,12 @@ decimal_converter = HoneyFormat.converter_registry[:decimal]
125
134
  decimal_converter.call('1.1') # => 1.1
126
135
  ```
127
136
 
128
- See [`Configuration#default_converters`](https://github.com/buren/honey_format/tree/master/lib/honey_format/configuration.rb#L38) for a complete list of the default ones.
137
+ Default converter names
138
+ ```ruby
139
+ HoneyFormat.config.default_converters.keys
140
+ ```
141
+
142
+ See [`Converters::DEFAULT`](https://github.com/buren/honey_format/blob/master/lib/honey_format/converters.rb) for a complete list of the default converter names.
129
143
 
130
144
  __Row builder__
131
145
 
@@ -234,21 +248,36 @@ csv.columns # => [:ID, :USERNAME]
234
248
 
235
249
  Pass your own header converter
236
250
  ```ruby
237
- map = { 'First^Name' => :first_name }
238
- converter = ->(column) { map.fetch(column, column.downcase) }
251
+ # unmapped keys use the default header converter,
252
+ # mix simple key => value mapping with key => proc
253
+ converter = {
254
+ 'First^Name' => :first_name,
255
+ 'Username' => -> { :handle }
256
+ }
239
257
 
240
- csv_string = "ID,First^Name\n1,Jacob"
258
+ csv_string = "ID,Username,First^Name\n1,buren,Jacob"
241
259
  user = HoneyFormat::CSV.new(csv_string, header_converter: converter).rows.first
242
260
  user.first_name # => "Jacob"
261
+ user.handle # => "buren"
243
262
  user.id # => "1"
263
+
264
+ # you can also pass a proc or any callable object
265
+ converter = Class.new do
266
+ define_singleton_method(:call) { |value, index| "#{value}#{index}" }
267
+ end
268
+ # or
269
+ converter = ->(value, index) { "#{value}#{index}" }
270
+ user = HoneyFormat::CSV.new(csv_string, header_converter: converter)
244
271
  ```
245
272
 
246
- Missing header values
273
+ Missing header values are automatically set and deduplicated
247
274
  ```ruby
248
- csv_string = "first,,third\nval0,val1,val2"
275
+ csv_string = "first,,third,third\nval0,val1,val2,val3"
249
276
  csv = HoneyFormat::CSV.new(csv_string)
250
277
  user = csv.rows.first
251
278
  user.column1 # => "val1"
279
+ user.third # => "val2"
280
+ user.third1 # => "val3"
252
281
  ```
253
282
 
254
283
  Duplicated header values
@@ -288,7 +317,7 @@ user['first^name'] # => "Jacob"
288
317
 
289
318
  __Errors__
290
319
 
291
- > When you need that some extra safety.
320
+ > When you need to be extra safe.
292
321
 
293
322
  If you want to there are some errors you can rescue
294
323
  ```ruby
@@ -403,7 +432,7 @@ Usage: bin/benchmark [file.csv] [options]
403
432
  --[no-]verbose Verbose output
404
433
  --lines-multipliers=[1,2,10] Multiply the rows in the CSV file (default: 1)
405
434
  --time=[30] Benchmark time (default: 30)
406
- --warmup=[30] Benchmark warmup (default: 30)
435
+ --warmup=[5] Benchmark warmup (default: 5)
407
436
  -h, --help How to use
408
437
  ```
409
438
 
@@ -10,7 +10,13 @@ require 'honey_format/cli/cli'
10
10
  cli = HoneyFormat::CLI.new
11
11
  options = cli.options
12
12
 
13
- input_path = options[:input_path] || raise(ArgumentError, 'input path required')
13
+ input_path = options[:input_path]
14
+ unless input_path
15
+ puts cli.usage
16
+ puts
17
+ puts '[ERROR] input path required'
18
+ exit 1
19
+ end
14
20
  csv_input = File.read(input_path)
15
21
  csv = HoneyFormat::CSV.new(
16
22
  csv_input,
@@ -23,9 +23,9 @@ Gem::Specification.new do |spec|
23
23
  spec.required_ruby_version = '>= 2.3.0'
24
24
 
25
25
  spec.add_development_dependency 'benchmark-ips'
26
- spec.add_development_dependency 'bundler', '~> 1.10'
26
+ spec.add_development_dependency 'bundler', '> 1.10', '< 3'
27
27
  spec.add_development_dependency 'byebug'
28
- spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rake', '~> 12.3'
29
29
  spec.add_development_dependency 'rspec'
30
30
  spec.add_development_dependency 'simplecov'
31
31
  end
@@ -12,9 +12,14 @@ module HoneyFormat
12
12
  # @return [CLI] the CLI
13
13
  def initialize(argv: ARGV, io: STDOUT)
14
14
  @io = io
15
+ @parser = nil
15
16
  @options = parse_options(argv: argv.dup)
16
17
  end
17
18
 
19
+ def usage
20
+ @parser.to_s
21
+ end
22
+
18
23
  private
19
24
 
20
25
  # Puts to configured IO
@@ -38,6 +43,8 @@ module HoneyFormat
38
43
  type_map = {}
39
44
 
40
45
  OptionParser.new do |parser|
46
+ @parser = parser
47
+
41
48
  parser.banner = 'Usage: honey_format [options] <file.csv>'
42
49
  parser.default_argv = argv
43
50
 
@@ -91,6 +98,7 @@ module HoneyFormat
91
98
  if input_path && argv.last
92
99
  raise(ArgumentError, "you can't provide both --csv and <path>")
93
100
  end
101
+
94
102
  input_path ||= argv.last
95
103
 
96
104
  {
@@ -97,33 +97,7 @@ module HoneyFormat
97
97
  # Default converter registry
98
98
  # @return [Hash] hash with default converters
99
99
  def default_converters
100
- @default_converters ||= {
101
- # strict variants
102
- decimal!: StrictConvertDecimal,
103
- integer!: StrictConvertInteger,
104
- date!: StrictConvertDate,
105
- datetime!: StrictConvertDatetime,
106
- symbol!: StrictConvertSymbol,
107
- downcase!: StrictConvertDowncase,
108
- upcase!: StrictConvertUpcase,
109
- boolean!: StrictConvertBoolean,
110
- # safe variants
111
- decimal: ConvertDecimal,
112
- decimal_or_zero: ConvertDecimalOrZero,
113
- integer: ConvertInteger,
114
- integer_or_zero: ConvertIntegerOrZero,
115
- date: ConvertDate,
116
- datetime: ConvertDatetime,
117
- symbol: ConvertSymbol,
118
- downcase: ConvertDowncase,
119
- upcase: ConvertUpcase,
120
- boolean: ConvertBoolean,
121
- md5: ConvertMD5,
122
- hex: ConvertHex,
123
- nil: ConvertNil,
124
- blank: ConvertBlank,
125
- header_column: ConvertHeaderColumn,
126
- }.freeze
100
+ @default_converters ||= Converters::DEFAULT
127
101
  end
128
102
  end
129
103
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module HoneyFormat
4
6
  # String values considered truthy
5
- TRUTHY = Set.new(%w[t T 1 y Y true TRUE]).freeze
7
+ TRUTHY = Set.new(%w[t T 1 y Y true TRUE] + [true]).freeze
6
8
  # String values considered falsy
7
- FALSY = Set.new(%w[f F 0 n N false FALSE]).freeze
9
+ FALSY = Set.new(%w[f F 0 n N false FALSE] + [false]).freeze
8
10
 
9
11
  # Tries to convert value boolean to, returns nil if it can't convert
10
12
  ConvertBoolean = proc do |v|
@@ -23,8 +23,8 @@ module HoneyFormat
23
23
  end
24
24
 
25
25
  # Convert to date or raise error
26
- StrictConvertDate = proc { |v| Date.parse(v) }
26
+ StrictConvertDate = proc { |v| v.is_a?(Date) ? v : Date.parse(v) }
27
27
 
28
28
  # Convert to datetime or raise error
29
- StrictConvertDatetime = proc { |v| Time.parse(v) }
29
+ StrictConvertDatetime = proc { |v| v.is_a?(Time) ? v : Time.parse(v) }
30
30
  end
@@ -9,4 +9,35 @@ require 'honey_format/converters/convert_string'
9
9
  module HoneyFormat
10
10
  # Convert to nil
11
11
  ConvertNil = proc {}
12
+
13
+ module Converters
14
+ DEFAULT = {
15
+ # strict variants
16
+ decimal!: StrictConvertDecimal,
17
+ integer!: StrictConvertInteger,
18
+ date!: StrictConvertDate,
19
+ datetime!: StrictConvertDatetime,
20
+ symbol!: StrictConvertSymbol,
21
+ downcase!: StrictConvertDowncase,
22
+ upcase!: StrictConvertUpcase,
23
+ boolean!: StrictConvertBoolean,
24
+ # safe variants
25
+ decimal: ConvertDecimal,
26
+ decimal_or_zero: ConvertDecimalOrZero,
27
+ integer: ConvertInteger,
28
+ integer_or_zero: ConvertIntegerOrZero,
29
+ date: ConvertDate,
30
+ datetime: ConvertDatetime,
31
+ symbol: ConvertSymbol,
32
+ downcase: ConvertDowncase,
33
+ upcase: ConvertUpcase,
34
+ boolean: ConvertBoolean,
35
+ md5: ConvertMD5,
36
+ hex: ConvertHex,
37
+ nil: ConvertNil,
38
+ blank: ConvertBlank,
39
+ header_column: ConvertHeaderColumn,
40
+ method_name: ConvertHeaderColumn,
41
+ }.freeze
42
+ end
12
43
  end
@@ -4,35 +4,48 @@ module HoneyFormat
4
4
  # Header column converter
5
5
  module HeaderColumnConverter
6
6
  # Bracket character matcher
7
- BRACKETS = /\(|\[|\{|\)|\]|\}/
7
+ BRACKETS = /\(|\[|\{|\)|\]|\}/.freeze
8
8
 
9
9
  # Separator characters
10
- SEPS = /'|"|\||\*|\^|\&|%|\$|€|#/
10
+ SEPS = /'|"|\||\*|\^|\&|%|\$|€|£|#/.freeze
11
+
12
+ # Space characters
13
+ SPACES = /[[:space:]]+/.freeze
14
+
15
+ # Non-printable characters
16
+ NON_PRINT = /[^[:print:]]/.freeze
17
+
18
+ # zero-width characters - see https://stackoverflow.com/q/50647999
19
+ ZERO_WIDTH = /[\u200B-\u200D\uFEFF]/.freeze
11
20
 
12
21
  # Replace map
13
22
  REPLACE_MAP = [
14
- [/\\/, '/'], # replace "\" with "/"
15
- [/ \(/, '('], # replace " (" with "("
16
- [/ \[/, '['], # replace " [" with "["
17
- [/ \{/, '{'], # replace " {" with "{"
18
- [/ \{/, '{'], # replace " {" with "{"
19
- [/\) /, ')'], # replace ") " with ")"
20
- [/\] /, ']'], # replace "] " with "]"
21
- [/\} /, '}'], # replace "} " with "}"
22
- [BRACKETS, '_'], # replace (, [, {, ), ] and } with "_"
23
- [/ +/, '_'], # replace one or more spaces with "_"
24
- [/-/, '_'], # replace "-" with "("
25
- [/::/, '_'], # replace "::" with "_"
26
- [%r{/}, '_'], # replace "/" with "_"
27
- [SEPS, '_'], # replace separator chars with "_"
28
- [/_+/, '_'], # replace one or more "_" with single "_"
29
- [/\A_+/, ''], # remove leading "_"
30
- [/_+\z/, ''], # remove trailing "_"
31
- ].map(&:freeze).freeze
23
+ [/\\/, '/'], # replace "\" with "/"
24
+ [/ \(/, '('], # replace " (" with "("
25
+ [/ \[/, '['], # replace " [" with "["
26
+ [/ \{/, '{'], # replace " {" with "{"
27
+ [/ \{/, '{'], # replace " {" with "{"
28
+ [/\) /, ')'], # replace ") " with ")"
29
+ [/\] /, ']'], # replace "] " with "]"
30
+ [/\} /, '}'], # replace "} " with "}"
31
+ [/@/, '_at_'], # replace "@' with "_at_"
32
+ [BRACKETS, '_'], # replace (, [, {, ), ] and } with "_"
33
+ [SPACES, '_'], # replace one or more space chars with "_"
34
+ [/-/, '_'], # replace "-" with "_"
35
+ [/\.|,/, '_'], # replace "." and "," with "_"
36
+ [/::/, '_'], # replace "::" with "_"
37
+ [%r{/}, '_'], # replace "/" with "_"
38
+ [SEPS, '_'], # replace separator chars with "_"
39
+ [/_+/, '_'], # replace one or more "_" with single "_"
40
+ [NON_PRINT, ''], # remove non-printable characters
41
+ [ZERO_WIDTH, ''], # remove zero-width characters
42
+ [/\A_+/, ''], # remove leading "_"
43
+ [/_+\z/, ''], # remove trailing "_"
44
+ ].map { |e| e.map(&:freeze).freeze }.freeze
32
45
 
33
46
  # Returns converted value and mutates the argument.
34
47
  # @return [Symbol] the cleaned header column.
35
- # @param [String] column the string to be cleaned.
48
+ # @param [String, Symbol] column the string to be cleaned.
36
49
  # @param [Integer] index the column index.
37
50
  # @example Convert simple header
38
51
  # HeaderColumnConverter.call(" User name ") #=> "user_name"
@@ -41,10 +54,11 @@ module HoneyFormat
41
54
  def self.call(column, index = nil)
42
55
  if column.nil? || column.empty?
43
56
  raise(ArgumentError, "column and column index can't be blank/nil") unless index
57
+
44
58
  return :"column#{index}"
45
59
  end
46
60
 
47
- column = column.dup
61
+ column = column.to_s.dup
48
62
  column.strip!
49
63
  column.downcase!
50
64
  REPLACE_MAP.each do |data|
@@ -19,6 +19,7 @@ module HoneyFormat
19
19
  # @param header_deduplicator [#call] deduplicates header columns.
20
20
  # @param row_builder [#call] will be called for each parsed row.
21
21
  # @param type_map [Hash] map of column_name => type conversion to perform.
22
+ # @param encoding [String] CSV encoding (for example "BOM|UTF-16LE:UTF-8").
22
23
  # @param skip_lines [Regexp, String]
23
24
  # Regexp for determining wheter a line is a comment. See CSV skip_lines option.
24
25
  # @raise [HeaderError] super class of errors raised when there is a CSV header error.
@@ -46,6 +47,8 @@ module HoneyFormat
46
47
  # @example Skip lines all lines starting with '#'
47
48
  # csv = HoneyFormat::CSV.new("name,id\n# some comment\njacob,1", skip_lines: '#')
48
49
  # csv.rows.length # => 1
50
+ # @example CSV encoding
51
+ # csv = HoneyFormat::CSV.new(csv_string, encoding: "BOM|UTF-16LE:UTF-8")
49
52
  # @see Matrix#new
50
53
  def initialize(
51
54
  csv,
@@ -56,6 +59,7 @@ module HoneyFormat
56
59
  header_converter: HoneyFormat.header_converter,
57
60
  header_deduplicator: HoneyFormat.config.header_deduplicator,
58
61
  row_builder: nil,
62
+ encoding: nil,
59
63
  type_map: {},
60
64
  skip_lines: HoneyFormat.config.skip_lines
61
65
  )
@@ -65,7 +69,8 @@ module HoneyFormat
65
69
  row_sep: row_delimiter,
66
70
  quote_char: quote_character,
67
71
  skip_blanks: true,
68
- skip_lines: skip_lines
72
+ skip_lines: skip_lines,
73
+ encoding: encoding
69
74
  )
70
75
  super(
71
76
  csv,
@@ -15,6 +15,7 @@ module HoneyFormat
15
15
 
16
16
  values = Array.new(value) { |i| i }.map do |index|
17
17
  next key if index.zero?
18
+
18
19
  :"#{key}#{index}"
19
20
  end
20
21
  array.concat(values)
@@ -10,10 +10,11 @@ module HoneyFormat
10
10
  # Instantiate a Header
11
11
  # @return [Header] a new instance of Header.
12
12
  # @param [Array<String>] header array of strings.
13
- # @param converter [#call, Symbol]
13
+ # @param converter [#call, Symbol, Hash]
14
14
  # header converter that implements a #call method
15
15
  # that takes one column (string) argument OR symbol for a registered
16
- # converter registry.
16
+ # converter registry OR a hash mapped to a symbol or something that responds
17
+ # to #call.
17
18
  # @param deduplicator [#call, Symbol]
18
19
  # header deduplicator that implements a #call method
19
20
  # that takes columns Array<String> argument OR symbol for a registered
@@ -93,7 +94,7 @@ module HoneyFormat
93
94
  private
94
95
 
95
96
  # Set the header converter
96
- # @param [Symbol, #call] symbol to known converter or object that responds to #call
97
+ # @param [Hash, Symbol, #call] symbol to known converter, object that responds to #call or Hash
97
98
  # @return [nil]
98
99
  def converter=(object)
99
100
  if object.is_a?(Symbol)
@@ -101,6 +102,11 @@ module HoneyFormat
101
102
  return
102
103
  end
103
104
 
105
+ if object.is_a?(Hash)
106
+ @converter = hash_converter(object)
107
+ return
108
+ end
109
+
104
110
  @converter = object
105
111
  end
106
112
 
@@ -134,20 +140,17 @@ module HoneyFormat
134
140
  # @param [Integer] index the CSV header column index
135
141
  # @return [Symbol] the converted column
136
142
  def convert_column(column, index)
137
- value = if converter_arity == 1
138
- @converter.call(column)
139
- else
140
- @converter.call(column, index)
141
- end
142
- value.to_sym
143
+ call_column_builder(@converter, column, index)&.to_sym
143
144
  end
144
145
 
145
- # Returns the converter#call method arity
146
- # @return [Integer] the converter#call method arity
147
- def converter_arity
146
+ # Returns the callable object method arity
147
+ # @param [#arity, #call] callable thing that responds to #call and maybe #arity
148
+ # @return [Integer] the method arity
149
+ def callable_arity(callable)
148
150
  # procs and lambdas respond to #arity
149
- return @converter.arity if @converter.respond_to?(:arity)
150
- @converter.method(:call).arity
151
+ return callable.arity if callable.respond_to?(:arity)
152
+
153
+ callable.method(:call).arity
151
154
  end
152
155
 
153
156
  # Raises an error if header column is missing/empty
@@ -163,5 +166,29 @@ module HoneyFormat
163
166
  ]
164
167
  raise(Errors::MissingHeaderColumnError, parts.join(' '))
165
168
  end
169
+
170
+ def hash_converter(hash)
171
+ lambda { |value, index|
172
+ # support strings and symbol keys interchangeably
173
+ column = hash.fetch(value) do
174
+ key = value.respond_to?(:to_sym) ? value.to_sym : value
175
+ # if column is unmapped use the default header converter
176
+ hash.fetch(key) { HoneyFormat.header_converter.call(value, index) }
177
+ end
178
+
179
+ # The hash can contain mixed values, Symbol and procs
180
+ if column.respond_to?(:call)
181
+ column = call_column_builder(column, value, index)
182
+ end
183
+
184
+ column&.to_sym
185
+ }
186
+ end
187
+
188
+ def call_column_builder(callable, value, index)
189
+ return callable.call if callable_arity(callable).zero?
190
+ return callable.call(value) if callable_arity(callable) == 1
191
+ callable.call(value, index)
192
+ end
166
193
  end
167
194
  end
@@ -4,7 +4,7 @@ module HoneyFormat
4
4
  # Default row builder
5
5
  class Row < Struct
6
6
  # Create a row
7
- # @return [Struct] returns an instantiated Struct representing a row
7
+ # @return [Row] returns an instantiated Row
8
8
  # @example
9
9
  # row_klass = Row.new(:id, :username)
10
10
  # row = row_klass.call('1', 'buren')
@@ -39,6 +39,7 @@ module HoneyFormat
39
39
  build_row!(row)
40
40
  rescue ArgumentError => e
41
41
  raise unless e.message == 'struct size differs'
42
+
42
43
  raise_invalid_row_length!(e, row)
43
44
  end
44
45
 
@@ -60,6 +61,7 @@ module HoneyFormat
60
61
  end
61
62
 
62
63
  return row unless @builder
64
+
63
65
  @builder.call(row)
64
66
  end
65
67
 
@@ -40,6 +40,12 @@ module HoneyFormat
40
40
  @rows
41
41
  end
42
42
 
43
+ # Return element at given position.
44
+ # @return [Row] of rows.
45
+ def [](index)
46
+ @rows[index]
47
+ end
48
+
43
49
  # Return the number of rows
44
50
  # @return [Integer] the number of rows
45
51
  def length
@@ -78,6 +84,7 @@ module HoneyFormat
78
84
  rows.each do |row|
79
85
  # ignore empty rows
80
86
  next if row.nil? || row.empty? || row == [nil]
87
+
81
88
  built_rows << builder.build(row)
82
89
  end
83
90
  built_rows
@@ -38,9 +38,11 @@ module HoneyFormat
38
38
  end
39
39
 
40
40
  # Call value type
41
- # @param [Symbol, String] type the name of the type
41
+ # @param [Symbol, String, #call] type the name of the type
42
42
  # @param [Object] value to be converted
43
43
  def call(value, type)
44
+ return type.call(value) if type.respond_to?(:call)
45
+
44
46
  self[type].call(value)
45
47
  end
46
48
 
@@ -72,6 +74,7 @@ module HoneyFormat
72
74
  # @return [true, false] true if type exists, false otherwise
73
75
  def type?(type)
74
76
  return false unless keyable?(type)
77
+
75
78
  @callers.key?(to_key(type))
76
79
  end
77
80
 
@@ -4,7 +4,7 @@ module HoneyFormat
4
4
  # Gem version
5
5
  VERSION = [
6
6
  MAJOR_VERSION = 0,
7
- MINOR_VERSION = 17,
8
- PATCH_VERSION = 0,
7
+ MINOR_VERSION = 21,
8
+ PATCH_VERSION = 1,
9
9
  ].join('.')
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honey_format
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.21.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Burenstam
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-11 00:00:00.000000000 Z
11
+ date: 2020-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.10'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3'
34
37
  type: :development
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">"
39
42
  - !ruby/object:Gem::Version
40
43
  version: '1.10'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: byebug
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +64,14 @@ dependencies:
58
64
  requirements:
59
65
  - - "~>"
60
66
  - !ruby/object:Gem::Version
61
- version: '10.0'
67
+ version: '12.3'
62
68
  type: :development
63
69
  prerelease: false
64
70
  version_requirements: !ruby/object:Gem::Requirement
65
71
  requirements:
66
72
  - - "~>"
67
73
  - !ruby/object:Gem::Version
68
- version: '10.0'
74
+ version: '12.3'
69
75
  - !ruby/object:Gem::Dependency
70
76
  name: rspec
71
77
  requirement: !ruby/object:Gem::Requirement
@@ -162,8 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
168
  - !ruby/object:Gem::Version
163
169
  version: '0'
164
170
  requirements: []
165
- rubyforge_project:
166
- rubygems_version: 2.7.6
171
+ rubygems_version: 3.0.3
167
172
  signing_key:
168
173
  specification_version: 4
169
174
  summary: Makes working with CSVs as smooth as honey.