fight_csv 0.1.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.
- 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
|
+
|