fight_csv 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +20 -0
- data/README.md +160 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/examples/timetracking.rb +46 -0
- data/fight_csv.gemspec +84 -0
- data/lib/fight_csv/data_source.rb +23 -0
- data/lib/fight_csv/field.rb +64 -0
- data/lib/fight_csv/record.rb +91 -0
- data/lib/fight_csv/schema.rb +17 -0
- data/lib/fight_csv.rb +10 -0
- data/tags +16131 -0
- data/test/fixtures/cocktails.csv +2 -0
- data/test/fixtures/prog_lang_schema.rb +13 -0
- data/test/fixtures/programming_languages.csv +4 -0
- data/test/fixtures/recipes.csv +3 -0
- data/test/helper.rb +41 -0
- data/test/test_field.rb +119 -0
- data/test/test_fight_csv.rb +23 -0
- data/test/test_record.rb +132 -0
- data/test/test_schema.rb +34 -0
- metadata +172 -0
data/test/helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
begin
|
3
|
+
Bundler.setup(:default, :development)
|
4
|
+
rescue Bundler::BundlerError => e
|
5
|
+
$stderr.puts e.message
|
6
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
7
|
+
exit e.status_code
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'simplecov'
|
11
|
+
SimpleCov.start do
|
12
|
+
add_filter 'test'
|
13
|
+
add_filter 'help'
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'minitest/autorun'
|
17
|
+
|
18
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
19
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
20
|
+
require 'fight_csv'
|
21
|
+
|
22
|
+
class MiniTest::Unit::TestCase
|
23
|
+
include FightCSV
|
24
|
+
def fixture(filename)
|
25
|
+
File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures', filename)
|
26
|
+
end
|
27
|
+
|
28
|
+
def refute_raises
|
29
|
+
test = -> do
|
30
|
+
begin
|
31
|
+
yield
|
32
|
+
rescue => e
|
33
|
+
return false, e
|
34
|
+
else
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
boolean, exception = test.call
|
39
|
+
assert boolean, "Expected no exception, but got #{exception}"
|
40
|
+
end
|
41
|
+
end
|
data/test/test_field.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
describe 'Field' do
|
5
|
+
describe 'initialize' do
|
6
|
+
it 'sets the matcher for matching csv fields' do
|
7
|
+
field = FightCSV::Field.new(/Field/, identifier: :field)
|
8
|
+
assert_equal /Field/, field.matcher
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'identifier' do
|
13
|
+
it 'defaults to the matcher downcase' do
|
14
|
+
field = FightCSV::Field.new("Field")
|
15
|
+
assert_equal field.identifier, :field
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises an error if nor a identifier is present neither the matcher is a string' do
|
19
|
+
field = FightCSV::Field.new(5)
|
20
|
+
assert_raises ArgumentError do
|
21
|
+
field.identifier
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'ivar_symbol' do
|
27
|
+
it 'returns the identifier as an ivar symbol' do
|
28
|
+
field = FightCSV::Field.new(/Field/, identifier: :field)
|
29
|
+
assert_equal :@field, field.ivar_symbol
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'validate' do
|
34
|
+
before do
|
35
|
+
@field = FightCSV::Field.new(/(Git-Hash)|(Commit-Hash)/,required: true, validator: /[a-z0-9]{5}/, identifier: :git_hash)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'checks for every property, returns {valid: true} if valid' do
|
39
|
+
assert_equal({valid: true}, @field.validate(['12abc'], ['Git-Hash']))
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns detailed error information if invalid(validate)' do
|
43
|
+
assert_equal({valid: false, error: ":git_hash must match (?-mix:[a-z0-9]{5}), but was \"foo\""},
|
44
|
+
@field.validate(['foo'], ['Git-Hash']))
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'also accepts a proc as a validator(not valid)' do
|
48
|
+
@field.validator = ->(git_hash) { git_hash === /[a-z0-9]{5}/ }
|
49
|
+
assert_equal({valid: false, error: ":git_hash must pass #{@field.validator}, but was \"foo\""},
|
50
|
+
@field.validate(['foo'],['Git-Hash']))
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'also accepts a proc as a validator(not valid)' do
|
54
|
+
@field.validator = ->(git_hash) { /[a-z0-9]{5}/ === git_hash }
|
55
|
+
assert_equal({valid: true},
|
56
|
+
@field.validate(['5oa9c'],['Git-Hash']))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'match' do
|
61
|
+
before do
|
62
|
+
@row = ['6','7']
|
63
|
+
end
|
64
|
+
describe 'not an integer' do
|
65
|
+
before do
|
66
|
+
@field = FightCSV::Field.new('Foo', identifier: :foo)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns an element, whose header matches the matcher' do
|
70
|
+
header = ['Foo', 'Bar']
|
71
|
+
assert_equal '6', @field.match(@row, header)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'raises an ArgumentError, if no header is provided' do
|
75
|
+
assert_raises ArgumentError do
|
76
|
+
@field.match(@row)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'an integer' do
|
82
|
+
it 'returns the element, with the index == matcher - 1' do
|
83
|
+
field = FightCSV::Field.new(1, identifier: :foo)
|
84
|
+
assert_equal '6', field.match(@row)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'process' do
|
90
|
+
before do
|
91
|
+
@field = FightCSV::Field.new('Foo', identifier: :foo)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'raises an ArgumentError if no header is present, and the matcher isn\'t an Integer' do
|
95
|
+
assert_raises ArgumentError do
|
96
|
+
@field.process(['bla'])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'returns a match if one is present' do
|
101
|
+
row = ['6','7']
|
102
|
+
header = ['Bar','Foo']
|
103
|
+
assert_equal '7', @field.process(row, header)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns "" if it doesnt find a match' do
|
107
|
+
row = ['6']
|
108
|
+
header = ['Bar']
|
109
|
+
assert_equal nil, @field.process(row, header)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'converts a value if necessary' do
|
113
|
+
@field.converter = proc { |value| value.to_i ** 2 }
|
114
|
+
row = ['6']
|
115
|
+
header = ['Foo']
|
116
|
+
assert_equal 36, @field.process(row, header)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'Integration' do
|
4
|
+
before do
|
5
|
+
|
6
|
+
schema_file = fixture('prog_lang_schema.rb')
|
7
|
+
ProgrammingLanguage ||= Class.new do
|
8
|
+
include FightCSV::Record
|
9
|
+
schema schema_file
|
10
|
+
end
|
11
|
+
|
12
|
+
@programming_languages = ProgrammingLanguage.records File.open(fixture('programming_languages.csv'))
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can validate a csv document' do
|
16
|
+
assert_equal true, @programming_languages.all? { |programming_language| programming_language.valid? }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'converts fields of a csv document and provides dynamic attribut readers' do
|
20
|
+
ruby = @programming_languages.find { |prog_lang| prog_lang.name == 'Ruby' }
|
21
|
+
assert_equal ['object oriented', 'imperative', 'reflective', 'functional'], ruby.paradigms
|
22
|
+
end
|
23
|
+
end
|
data/test/test_record.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'helper'
|
2
|
+
describe 'Record' do
|
3
|
+
before do
|
4
|
+
@klass = Class.new do
|
5
|
+
include FightCSV::Record
|
6
|
+
end
|
7
|
+
@schema = FightCSV::Schema.new do
|
8
|
+
field('Foo', identifier: :foo)
|
9
|
+
field('Foz', identifier: :foz)
|
10
|
+
end
|
11
|
+
@klass.schema = @schema
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'schema=' do
|
15
|
+
it 'automatically refreshes the row' do
|
16
|
+
instance = @klass.new(%w{bar baz}, header: %w{Bar Foz})
|
17
|
+
instance.schema = FightCSV::Schema.new do
|
18
|
+
field('Bar', identifier: :bar)
|
19
|
+
end
|
20
|
+
assert_equal({ bar: 'bar' }, instance.row)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'records' do
|
25
|
+
it 'maps each row to a record object' do
|
26
|
+
records = @klass.records("Foo,Foz\nbar,baz\nfoo,foz")
|
27
|
+
assert records.all? { |r| Record === r }
|
28
|
+
assert_equal 'bar', records.first.foo
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'import' do
|
33
|
+
it 'provides an enumerator for iterating over the csv reusing the same record object' do
|
34
|
+
enum = @klass.import("Foo,Foz\nbar,baz\nfoo,foz")
|
35
|
+
assert enum.map(&:object_id).each_cons(2).all? { |a,b| a == b }, 'Expected all records to be the same object'
|
36
|
+
assert_equal({foo: 'bar', foz: 'baz'}, enum.first.row)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'fields' do
|
41
|
+
it 'aggregates fields using the dynamic attribute readers or hard coded readers' do
|
42
|
+
@klass.class_eval { def foo; 1;end }
|
43
|
+
record = @klass.new(['Bar','Baz'], schema: @schema, header: ['Foo', 'Foz'])
|
44
|
+
assert_equal({foo: 1, foz: 'Baz'}, record.fields)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'dynamic attributes' do
|
49
|
+
describe 'readers' do
|
50
|
+
it 'works' do
|
51
|
+
record = @klass.new(['Bar','Baz'], schema: @schema, header: ['Foo', 'Foz'])
|
52
|
+
assert_equal 'Bar', record.foo
|
53
|
+
assert_equal 'Baz', record.foz
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns "" if the attribute is not defined' do
|
57
|
+
record = @klass.new(['Bar'], schema: @schema, header: ['Foo'])
|
58
|
+
assert_equal 'Bar', record.foo
|
59
|
+
assert_equal nil, record.foz
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'converts values if necessary' do
|
63
|
+
@schema.fields.find { |f| f.matcher == 'Foo' }.converter = proc { |value| value.downcase.to_sym }
|
64
|
+
record = @klass.new(['Bar'], schema: @schema,header: ['Foo'])
|
65
|
+
assert_equal :bar, record.foo
|
66
|
+
end
|
67
|
+
end
|
68
|
+
describe 'writers' do
|
69
|
+
it 'allow write access to attributes in the row' do
|
70
|
+
record = @klass.new(['Bar'], schema: @schema, header: ['Foo'])
|
71
|
+
record.foo = 4
|
72
|
+
assert_equal 4, record.foo
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'allows to write to fields defined in the schema but not provided through the csv document' do
|
76
|
+
record = @klass.new([], schema: @schema,header: [])
|
77
|
+
record.foo = 4
|
78
|
+
assert_equal 4, record.foo
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'shared test data' do
|
84
|
+
before do
|
85
|
+
@class = Class.new { include FightCSV::Record }
|
86
|
+
@class.schema do
|
87
|
+
converter = ->(value) { value.to_i }
|
88
|
+
field 'a', identifier: :a, converter: converter
|
89
|
+
field 'b', identifier: :b, converter: converter
|
90
|
+
field 'c', identifier: :c, converter: converter
|
91
|
+
end
|
92
|
+
@record = @class.new %w{1 2 3}, header: ['a','b','c']
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'row' do
|
96
|
+
it 'returns a hash with identifiers as keys and values as values' do
|
97
|
+
assert_equal Hash[[[:a, 1],[:b,2],[:c,3]]], @record.row
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'schema validation' do
|
103
|
+
before do
|
104
|
+
prog_lang_schema = fixture('prog_lang_schema.rb')
|
105
|
+
@prog_lang = Class.new do
|
106
|
+
include FightCSV::Record
|
107
|
+
schema prog_lang_schema
|
108
|
+
end
|
109
|
+
@prog_langs = @prog_lang.records(File.open(fixture('programming_languages.csv')))
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'valid?' do
|
113
|
+
it 'returns true if a record is valid' do
|
114
|
+
assert_equal true, @prog_langs.all?(&:valid?)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'errors' do
|
119
|
+
it 'returns a hash includind valid: true if the record is valid' do
|
120
|
+
assert_equal({valid: true, errors: []}, @prog_langs.first.validate)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'returns a hash inlcuding valid: false and detailed error report' do
|
124
|
+
@prog_lang.schema.fields.find { |f| f.identifier == :creator }.validator = /.+/
|
125
|
+
errors = [ ':creator must match (?-mix:.+), but was ""']
|
126
|
+
instance = @prog_lang.new(['LOLCODE','lolfulness',nil], header: ['Name','Paradigms'])
|
127
|
+
assert_equal false, instance.valid?
|
128
|
+
assert_equal errors, instance.errors
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/test/test_schema.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'helper'
|
2
|
+
describe 'Schema' do
|
3
|
+
describe 'DSL' do
|
4
|
+
it 'allows to easily create a field' do
|
5
|
+
schema = FightCSV::Schema.new
|
6
|
+
converter = ->(value) { value.upcase }
|
7
|
+
|
8
|
+
schema.field "Name", {
|
9
|
+
converter: converter,
|
10
|
+
validator: /\w*/,
|
11
|
+
identifier: :name
|
12
|
+
}
|
13
|
+
|
14
|
+
field = schema.fields.first
|
15
|
+
assert_equal converter, field.converter
|
16
|
+
assert_equal /\w*/, field.validator
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'accepts a file name' do
|
20
|
+
schema = Schema.new fixture('prog_lang_schema.rb')
|
21
|
+
assert_equal FightCSV::Schema, schema.class
|
22
|
+
assert_equal 'Name', schema.fields.first.matcher
|
23
|
+
assert_equal 'Creator', schema.fields.last.matcher
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'also responds to a block' do
|
27
|
+
schema = Schema.new do
|
28
|
+
field 'Foo', identifier: :foo
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_equal 'Foo', schema.fields.first.matcher
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fight_csv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Manuel Korfmann
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-08-18 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: constructable
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
prerelease: false
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: jeweler
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 1
|
55
|
+
- 6
|
56
|
+
- 0
|
57
|
+
version: 1.6.0
|
58
|
+
type: :development
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: awesome_print
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: *id004
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: simplecov
|
76
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
type: :development
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: *id005
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: rake
|
89
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: *id006
|
100
|
+
description: "\n\
|
101
|
+
Provides a nice DSL to describe your CSV document.\n\
|
102
|
+
CSV documents can be validated against this description.\n\
|
103
|
+
You can easily define types like Integer or Array for CSV through converters.\n "
|
104
|
+
email: manu@korfmann.info
|
105
|
+
executables: []
|
106
|
+
|
107
|
+
extensions: []
|
108
|
+
|
109
|
+
extra_rdoc_files:
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
files:
|
113
|
+
- .document
|
114
|
+
- .rvmrc
|
115
|
+
- Gemfile
|
116
|
+
- Gemfile.lock
|
117
|
+
- LICENSE.txt
|
118
|
+
- README.md
|
119
|
+
- Rakefile
|
120
|
+
- VERSION
|
121
|
+
- examples/timetracking.rb
|
122
|
+
- fight_csv.gemspec
|
123
|
+
- lib/fight_csv.rb
|
124
|
+
- lib/fight_csv/data_source.rb
|
125
|
+
- lib/fight_csv/field.rb
|
126
|
+
- lib/fight_csv/record.rb
|
127
|
+
- lib/fight_csv/schema.rb
|
128
|
+
- tags
|
129
|
+
- test/fixtures/cocktails.csv
|
130
|
+
- test/fixtures/prog_lang_schema.rb
|
131
|
+
- test/fixtures/programming_languages.csv
|
132
|
+
- test/fixtures/recipes.csv
|
133
|
+
- test/helper.rb
|
134
|
+
- test/test_field.rb
|
135
|
+
- test/test_fight_csv.rb
|
136
|
+
- test/test_record.rb
|
137
|
+
- test/test_schema.rb
|
138
|
+
has_rdoc: true
|
139
|
+
homepage: http://github.com/mkorfmann/fight_csv
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
none: false
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
hash: 729338381106442456
|
153
|
+
segments:
|
154
|
+
- 0
|
155
|
+
version: "0"
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
version: "0"
|
164
|
+
requirements: []
|
165
|
+
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 1.3.7
|
168
|
+
signing_key:
|
169
|
+
specification_version: 3
|
170
|
+
summary: JSON-Schema + ActiveModel for CSV
|
171
|
+
test_files: []
|
172
|
+
|