honey_format 0.10.0 → 0.11.0
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 +5 -0
- data/README.md +19 -9
- data/exe/honey_format +58 -0
- data/honey_format.gemspec +2 -1
- data/lib/honey_format/header.rb +13 -0
- data/lib/honey_format/row.rb +31 -41
- data/lib/honey_format/row_builder.rb +50 -20
- data/lib/honey_format/rows.rb +3 -2
- data/lib/honey_format/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 220883bb92b35808697b428d346d1f171d2d06d47d4a9ed79404b824a7e65781
|
4
|
+
data.tar.gz: 9260762827e1fa50c1669696edfbc625a9e94ffc04773db81f27d6c8d9e7dd88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 245594826d582fdbbe02fb303a004197849f9ae17b184ecd4d57e833f98b220e28543bd3aaa1706221b9196b548d7adbd2b73f38ce96e24685f361645d86e307
|
7
|
+
data.tar.gz: 3c907de2398718ae946aeb3984991b9273b359e667004c331e86a359e513c38e2d09836c52fba230a2a98e47cdb3e93a5e964b7583c48e6d43ce739018806bd8
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# v0.11.0
|
2
|
+
|
3
|
+
* Add CLI: `honey_format` executable
|
4
|
+
* Swap `RowBuilder` <> `Row` class names [[#PR12](https://github.com/buren/honey_format/pull/12)]
|
5
|
+
|
1
6
|
# v0.10.0
|
2
7
|
|
3
8
|
* Add support for filtering what rows are included in `#to_csv` [[#PR11](https://github.com/buren/honey_format/pull/11)]
|
data/README.md
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
-
# HoneyFormat [](https://travis-ci.org/buren/honey_format) [](https://codeclimate.com/github/buren/honey_format) ](https://travis-ci.org/buren/honey_format) [](https://codeclimate.com/github/buren/honey_format) [](http://inch-ci.org/github/buren/honey_format)
|
2
2
|
|
3
3
|
Convert CSV to an array of objects with with ease.
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Proper objects for CSV header and rows
|
8
|
+
- Convert column values with custom row builder
|
9
|
+
- Convert header column names
|
10
|
+
- Customize what columns and rows are included in CSV output
|
11
|
+
- Has no dependencies other than Ruby stdlib
|
12
|
+
- Supports Ruby >= 2.3
|
13
|
+
|
14
|
+
## Examples
|
15
|
+
|
16
|
+
See [examples/](https://github.com/buren/honey_format/tree/master/examples) for more examples.
|
6
17
|
|
7
18
|
```ruby
|
8
19
|
csv_string = "Id,Username\n1,buren"
|
@@ -13,8 +24,6 @@ user.id # => "1"
|
|
13
24
|
user.username # => "buren"
|
14
25
|
```
|
15
26
|
|
16
|
-
:information_source: Supports Ruby >= 2.3, has no dependencies other than Ruby stdlib.
|
17
|
-
|
18
27
|
## Installation
|
19
28
|
|
20
29
|
Add this line to your application's Gemfile:
|
@@ -164,10 +173,10 @@ Errors
|
|
164
173
|
begin
|
165
174
|
HoneyFormat::CSV.new(csv_string)
|
166
175
|
rescue HoneyFormat::HeaderError => e
|
167
|
-
puts 'there
|
176
|
+
puts 'there was a problem with the header'
|
168
177
|
raise(e)
|
169
178
|
rescue HoneyFormat::RowError => e
|
170
|
-
puts 'there
|
179
|
+
puts 'there was a problem with a row'
|
171
180
|
raise(e)
|
172
181
|
end
|
173
182
|
```
|
@@ -193,13 +202,14 @@ $ bin/benchmark file.csv
|
|
193
202
|
HoneyFormat::CSV: 49.6 i/s - 1.05x slower
|
194
203
|
```
|
195
204
|
|
196
|
-
|
205
|
+
2MB (10k lines)
|
197
206
|
|
198
207
|
```
|
199
|
-
stdlib CSV:
|
200
|
-
HoneyFormat::CSV:
|
208
|
+
stdlib CSV: 4.6 i/s
|
209
|
+
HoneyFormat::CSV: 4.2 i/s - 1.08x slower
|
201
210
|
```
|
202
211
|
|
212
|
+
|
203
213
|
## Development
|
204
214
|
|
205
215
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/exe/honey_format
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# for dev purposes
|
4
|
+
require 'bundler/setup' if ENV['HONEY_FORMAT_GEM_DEV']
|
5
|
+
require 'honey_format'
|
6
|
+
|
7
|
+
input_path = ARGV.first
|
8
|
+
|
9
|
+
columns = nil
|
10
|
+
output_path = nil
|
11
|
+
delimiter = ','
|
12
|
+
|
13
|
+
require 'optparse'
|
14
|
+
|
15
|
+
OptionParser.new do |parser|
|
16
|
+
parser.banner = "Usage: honey_format [file.csv] [options]"
|
17
|
+
parser.default_argv = ARGV
|
18
|
+
|
19
|
+
parser.on("--csv=input.csv", String, "CSV file") do |value|
|
20
|
+
input_path = value
|
21
|
+
end
|
22
|
+
|
23
|
+
parser.on("--columns=id,name", Array, "Select columns.") do |value|
|
24
|
+
columns = value
|
25
|
+
end
|
26
|
+
|
27
|
+
parser.on("--output=output.csv", String, "CSV output (STDOUT otherwise)") do |value|
|
28
|
+
output_path = value
|
29
|
+
end
|
30
|
+
|
31
|
+
parser.on("--delimiter=,", String, "CSV delimiter (default: ,)") do |value|
|
32
|
+
delimiter = value
|
33
|
+
end
|
34
|
+
|
35
|
+
parser.on("-h", "--help", "How to use") do
|
36
|
+
puts parser
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
parser.on_tail('--version', 'Show version') do
|
41
|
+
puts "HoneyFormat version #{HoneyFormat::VERSION}"
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
# No argument, shows at tail. This will print an options summary.
|
46
|
+
parser.on_tail("-h", "--help", "Show this message") do
|
47
|
+
puts parser
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
end.parse!
|
51
|
+
|
52
|
+
csv = HoneyFormat::CSV.new(File.read(input_path), delimiter: delimiter)
|
53
|
+
csv_string = csv.to_csv(columns: columns.map(&:to_sym))
|
54
|
+
if output_path
|
55
|
+
File.write(output_path, csv_string)
|
56
|
+
else
|
57
|
+
puts csv_string
|
58
|
+
end
|
data/honey_format.gemspec
CHANGED
@@ -14,7 +14,8 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = 'https://github.com/buren/honey_format'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
17
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples)/}) }
|
18
|
+
spec.bindir = 'exe'
|
18
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
20
|
spec.require_paths = ['lib']
|
20
21
|
|
data/lib/honey_format/header.rb
CHANGED
@@ -87,17 +87,27 @@ module HoneyFormat
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
+
# Convert the column value
|
91
|
+
# @param [Object] column the CSV header column value
|
92
|
+
# @param [Integer] index the CSV header column index
|
93
|
+
# @return [Object] the converted object
|
90
94
|
def convert_column(column, index)
|
91
95
|
return @converter.call(column) if converter_arity == 1
|
92
96
|
@converter.call(column, index)
|
93
97
|
end
|
94
98
|
|
99
|
+
# Returns the converter#call method arity
|
100
|
+
# @return [Integer] the converter#call method arity
|
95
101
|
def converter_arity
|
96
102
|
# procs and lambdas respond to #arity
|
97
103
|
return @converter.arity if @converter.respond_to?(:arity)
|
98
104
|
@converter.method(:call).arity
|
99
105
|
end
|
100
106
|
|
107
|
+
# Raises an error if header column is unknown
|
108
|
+
# @param [Object] column the CSV header column
|
109
|
+
# @param [Array<Symbol, String>] valid CSV columns
|
110
|
+
# @raise [Errors::UnknownHeaderColumnError]
|
101
111
|
def maybe_raise_unknown_column!(column, valid)
|
102
112
|
return if valid.empty?
|
103
113
|
return if valid.include?(column)
|
@@ -106,6 +116,9 @@ module HoneyFormat
|
|
106
116
|
raise(Errors::UnknownHeaderColumnError, err_msg)
|
107
117
|
end
|
108
118
|
|
119
|
+
# Raises an error if header column is missing/empty
|
120
|
+
# @param [Object] column the CSV header column
|
121
|
+
# @raise [Errors::MissingHeaderColumnError]
|
109
122
|
def maybe_raise_missing_column!(column)
|
110
123
|
return if column && !column.empty?
|
111
124
|
|
data/lib/honey_format/row.rb
CHANGED
@@ -1,49 +1,39 @@
|
|
1
|
-
require 'honey_format/row_builder'
|
2
|
-
|
3
1
|
module HoneyFormat
|
4
|
-
#
|
5
|
-
class Row
|
6
|
-
#
|
7
|
-
# @return [
|
8
|
-
# @
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
2
|
+
# Default row builder
|
3
|
+
class Row < Struct
|
4
|
+
# Create a row
|
5
|
+
# @return [Struct] returns an instantiated Struct representing a row
|
6
|
+
# @example
|
7
|
+
# row_klass = Row.new(:id, :username)
|
8
|
+
# row = row_klass.call('1', 'buren')
|
9
|
+
# # => #<struct id="1", username="buren">
|
10
|
+
def self.call(row)
|
11
|
+
new(*row)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Represent row as CSV
|
15
|
+
# @param columns [Array<Symbol>, Set<Symbol>, NilClass] the columns to output, nil means all columns (default: nil)
|
16
|
+
# @return [String] CSV-string representation.
|
17
|
+
def to_csv(columns: nil)
|
18
|
+
attributes = members
|
19
|
+
attributes = columns & attributes if columns
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
row = attributes.map { |column| to_csv_value(column) }
|
22
|
+
|
23
|
+
::CSV.generate_line(row)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
# @
|
30
|
-
# @
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
return built_row unless @builder
|
36
|
-
@builder.call(built_row)
|
37
|
-
rescue ArgumentError => e
|
38
|
-
raise unless e.message == 'struct size differs'
|
26
|
+
private
|
27
|
+
|
28
|
+
# Returns the column in CSV format
|
29
|
+
# @param [Symbol] column name
|
30
|
+
# @return [String] column value as CSV string
|
31
|
+
def to_csv_value(column)
|
32
|
+
value = public_send(column)
|
33
|
+
return if value.nil?
|
34
|
+
return value.to_csv if value.respond_to?(:to_csv)
|
39
35
|
|
40
|
-
|
41
|
-
"Row length #{row.length}",
|
42
|
-
"column length #{@columns.length}",
|
43
|
-
"row: #{row.inspect}",
|
44
|
-
"orignal message: '#{e.message}'"
|
45
|
-
].join(', ')
|
46
|
-
raise(Errors::InvalidRowLengthError, err_msg)
|
36
|
+
value.to_s
|
47
37
|
end
|
48
38
|
end
|
49
39
|
end
|
@@ -1,28 +1,58 @@
|
|
1
|
+
require 'honey_format/row'
|
2
|
+
|
1
3
|
module HoneyFormat
|
2
|
-
#
|
3
|
-
class RowBuilder
|
4
|
-
#
|
5
|
-
# @return [
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
# Holds data for a single row.
|
5
|
+
class RowBuilder
|
6
|
+
# Returns a new instance of RowBuilder.
|
7
|
+
# @return [RowBuilder] a new instance of RowBuilder.
|
8
|
+
# @param [Array<Symbol>] columns an array of symbols.
|
9
|
+
# @param builder [#call, #to_csv] optional row builder
|
10
|
+
# @raise [RowError] super class of errors raised when there is a row error.
|
11
|
+
# @raise [EmptyRowColumnsError] raised when there are no columns.
|
12
|
+
# @raise [InvalidRowLengthError] raised when row has more columns than header columns.
|
13
|
+
# @example Create new row
|
14
|
+
# RowBuilder.new!([:id])
|
15
|
+
def initialize(columns, builder: nil)
|
16
|
+
if columns.empty?
|
17
|
+
err_msg = 'Expected array with at least one element, but was empty.'
|
18
|
+
raise(Errors::EmptyRowColumnsError, err_msg)
|
19
|
+
end
|
9
20
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
attributes = members
|
15
|
-
attributes = columns & attributes if columns
|
21
|
+
@row_klass = Row.new(*columns)
|
22
|
+
@builder = builder
|
23
|
+
@columns = columns
|
24
|
+
end
|
16
25
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
# Returns a Struct.
|
27
|
+
# @return [Row, Object] a new instance of built row.
|
28
|
+
# @param row [Array] the row array.
|
29
|
+
# @raise [InvalidRowLengthError] raised when there are more row elements longer than columns
|
30
|
+
# @example Build new row
|
31
|
+
# r = RowBuilder.new([:id])
|
32
|
+
# r.build(['1']).id #=> '1'
|
33
|
+
def build(row)
|
34
|
+
row = @row_klass.call(row)
|
35
|
+
return row unless @builder
|
36
|
+
@builder.call(row)
|
37
|
+
rescue ArgumentError => e
|
38
|
+
raise unless e.message == 'struct size differs'
|
39
|
+
raise_invalid_row_length!(e, row)
|
40
|
+
end
|
21
41
|
|
22
|
-
|
23
|
-
end
|
42
|
+
private
|
24
43
|
|
25
|
-
|
44
|
+
# Raises invalid row length error
|
45
|
+
# @param [StandardError] e the raised error
|
46
|
+
# @param [Object] row
|
47
|
+
# @raise [Errors::InvalidRowLengthError]
|
48
|
+
def raise_invalid_row_length!(e, row)
|
49
|
+
err_msg = [
|
50
|
+
"Row length #{row.length}",
|
51
|
+
"column length #{@columns.length}",
|
52
|
+
"row: #{row.inspect}",
|
53
|
+
"orignal message: '#{e.message}'"
|
54
|
+
].join(', ')
|
55
|
+
raise(Errors::InvalidRowLengthError, err_msg)
|
26
56
|
end
|
27
57
|
end
|
28
58
|
end
|
data/lib/honey_format/rows.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'set'
|
2
|
-
require 'honey_format/
|
2
|
+
require 'honey_format/row_builder'
|
3
3
|
|
4
4
|
module HoneyFormat
|
5
5
|
# Represents rows.
|
@@ -14,7 +14,8 @@ module HoneyFormat
|
|
14
14
|
# @raise [EmptyRowColumnsError] raised when there are no columns.
|
15
15
|
# @raise [InvalidRowLengthError] raised when row has more columns than header columns.
|
16
16
|
def initialize(rows, columns, builder: nil)
|
17
|
-
|
17
|
+
builder = RowBuilder.new(columns, builder: builder)
|
18
|
+
@rows = prepare_rows(builder, rows)
|
18
19
|
end
|
19
20
|
|
20
21
|
# @yield [row] The given block will be passed for every row.
|
data/lib/honey_format/version.rb
CHANGED
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: honey_format
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jacob Burenstam
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2018-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
@@ -99,7 +99,8 @@ description: Convert CSV to an array of objects with with ease. Create objects f
|
|
99
99
|
stdlib.
|
100
100
|
email:
|
101
101
|
- burenstam@gmail.com
|
102
|
-
executables:
|
102
|
+
executables:
|
103
|
+
- honey_format
|
103
104
|
extensions: []
|
104
105
|
extra_rdoc_files: []
|
105
106
|
files:
|
@@ -115,6 +116,7 @@ files:
|
|
115
116
|
- bin/benchmark
|
116
117
|
- bin/console
|
117
118
|
- bin/setup
|
119
|
+
- exe/honey_format
|
118
120
|
- honey_format.gemspec
|
119
121
|
- lib/honey_format.rb
|
120
122
|
- lib/honey_format/convert_header_value.rb
|