csvrecord 0.1.0 → 0.1.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 +4 -4
- data/lib/csvrecord/base.rb +112 -22
- data/lib/csvrecord/builder.rb +6 -6
- data/lib/csvrecord/version.rb +1 -1
- data/test/test_beer.rb +55 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1458e4244f8a88c335b08c7e4c21ff576ba07fe
|
4
|
+
data.tar.gz: c44ca7e8f52667c0a62895c97e86ebe501b18c82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dabfc7c26f4e69548f2791c9474f4b3579a90c17e14bccfc5f9c0db4285f8e4610b6ef70a4bd6560547a828ff55c4d38235a8201a57f7fb1c615e742c277bf15
|
7
|
+
data.tar.gz: 4f5879e6827cf1a1d56c989242bd13b672fe6abf28abff1743219921ed88d55ab238eedf763ff5bf592a07acd9aa0a3e0504673e79fbaeadd931c3e1dd6db7d6
|
data/lib/csvrecord/base.rb
CHANGED
@@ -12,36 +12,94 @@ module CsvRecord
|
|
12
12
|
attr_reader :name, :type
|
13
13
|
|
14
14
|
def initialize( name, type )
|
15
|
-
##
|
15
|
+
## note: always symbol-ify (to_sym) name and type
|
16
|
+
|
17
|
+
## todo: add title or titles for header field/column title as is e.g. 'Team 1' etc.
|
18
|
+
## incl. numbers only or even an empty header title
|
16
19
|
@name = name.to_sym
|
17
20
|
|
18
21
|
if type.is_a?( Class )
|
19
22
|
@type = type ## assign class to its own property - why? why not?
|
20
23
|
else
|
21
|
-
@type = type.to_sym
|
24
|
+
@type = Type.registry[type.to_sym]
|
25
|
+
if @type.nil?
|
26
|
+
puts "!!!! warn unknown type >#{type}< - no class mapping found; add missing type to CsvRecord::Type.registry[]"
|
27
|
+
## todo: fix raise exception!!!!
|
28
|
+
end
|
22
29
|
end
|
23
30
|
end
|
24
31
|
end # class Field
|
25
32
|
|
26
33
|
|
27
34
|
|
35
|
+
class Type ## todo: use a module - it's just a namespace/module now - why? why not?
|
36
|
+
|
37
|
+
## e.g. use Type.registry[:string] = String etc.
|
38
|
+
## note use @@ - there is only one registry
|
39
|
+
def self.registry() @@registry ||={} end
|
40
|
+
|
41
|
+
## add built-in types:
|
42
|
+
registry[:string] = String
|
43
|
+
registry[:integer] = Integer ## todo/check: add :number alias for integer? why? why not?
|
44
|
+
registry[:float] = Float
|
45
|
+
## todo: add some more
|
46
|
+
end # class Type
|
47
|
+
|
48
|
+
|
49
|
+
|
28
50
|
def self.define( &block )
|
29
51
|
builder = Builder.new
|
30
|
-
|
52
|
+
if block.arity == 1
|
53
|
+
block.call( builder )
|
54
|
+
## e.g. allows "yield" dsl style e.g.
|
55
|
+
## CsvRecord.define do |rec|
|
56
|
+
## rec.string :team1
|
57
|
+
## rec.string :team2
|
58
|
+
## end
|
59
|
+
##
|
60
|
+
else
|
61
|
+
builder.instance_eval( &block )
|
62
|
+
## e.g. shorter "instance eval" dsl style e.g.
|
63
|
+
## CsvRecord.define do
|
64
|
+
## string :team1
|
65
|
+
## string :team2
|
66
|
+
## end
|
67
|
+
end
|
31
68
|
builder.to_record
|
32
69
|
end
|
33
70
|
|
34
71
|
|
72
|
+
|
35
73
|
class Base
|
36
74
|
|
37
75
|
def self.fields ## note: use class instance variable (@fields and NOT @@fields)!!!! (derived classes get its own copy!!!)
|
38
76
|
@fields ||= []
|
39
77
|
end
|
40
78
|
|
79
|
+
def self.field_names ## rename to header - why? why not?
|
80
|
+
## return header row, that is, all field names in an array
|
81
|
+
## todo: rename to field_names or just names - why? why not?
|
82
|
+
## note: names are (always) symbols!!!
|
83
|
+
fields.map {|field| field.name }
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.field_types
|
87
|
+
## note: types are (always) classes!!!
|
88
|
+
fields.map {|field| field.type }
|
89
|
+
end
|
90
|
+
|
91
|
+
|
41
92
|
|
42
93
|
def self.field( name, type=:string )
|
94
|
+
field = Field.new( name, type )
|
95
|
+
fields << field
|
96
|
+
|
97
|
+
define_field( field ) ## auto-add getter,setter,parse/typecast
|
98
|
+
end
|
43
99
|
|
44
|
-
|
100
|
+
def self.define_field( field )
|
101
|
+
name = field.name ## note: always assumes a "cleaned-up" (symbol) name
|
102
|
+
type = field.type ## note: always assumes a (class) type
|
45
103
|
|
46
104
|
define_method( name ) do
|
47
105
|
instance_variable_get( "@#{name}" )
|
@@ -55,27 +113,36 @@ def self.field( name, type=:string )
|
|
55
113
|
instance_variable_set( "@#{name}", self.class.typecast( value, type ) )
|
56
114
|
end
|
57
115
|
end
|
58
|
-
def self.add_field( name, type ) field( name, type ); end ## add alias for builder
|
59
116
|
|
117
|
+
## column/columns aliases for field/fields
|
118
|
+
## use self << with alias_method - possible? works? why? why not?
|
119
|
+
def self.column( name, type=:string ) field( name, type ); end
|
120
|
+
def self.columns() fields; end
|
121
|
+
def self.column_names() field_names; end
|
122
|
+
def self.column_types() field_types; end
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
def self.typecast( value, type ) ## cast (convert) from string value to type (e.g. float, integer, etc.)
|
60
127
|
|
61
128
|
|
62
|
-
def self.typecast( value, type )
|
63
129
|
## convert string value to (field) type
|
64
|
-
if type ==
|
130
|
+
if type == String
|
65
131
|
value ## pass through as is
|
66
|
-
elsif type ==
|
67
|
-
## note: allow/check for nil values
|
132
|
+
elsif type == Float
|
133
|
+
## note: allow/check for nil values - why? why not?
|
68
134
|
float = (value.nil? || value.empty?) ? nil : value.to_f
|
69
135
|
puts "typecast >#{value}< to float number >#{float}<"
|
70
136
|
float
|
71
|
-
elsif type ==
|
137
|
+
elsif type == Integer
|
72
138
|
number = (value.nil? || value.empty?) ? nil : value.to_i(10) ## always use base10 for now (e.g. 010 => 10 etc.)
|
73
139
|
puts "typecast >#{value}< to integer number >#{number}<"
|
74
140
|
number
|
75
141
|
else
|
76
|
-
|
77
|
-
|
78
|
-
|
142
|
+
## raise exception about unknow type
|
143
|
+
pp type
|
144
|
+
puts "!!!! unknown type >#{type}< - don't know how to convert/typecast string value >#{value}<"
|
145
|
+
value
|
79
146
|
end
|
80
147
|
end
|
81
148
|
|
@@ -85,21 +152,43 @@ def self.build_hash( values ) ## find a better name - build_attrib? or somethi
|
|
85
152
|
## puts "== build_hash:"
|
86
153
|
## pp values
|
87
154
|
|
88
|
-
|
89
|
-
values
|
90
|
-
field = fields[i]
|
91
|
-
## pp field
|
92
|
-
h[ field.name ] = value
|
93
|
-
end
|
94
|
-
h
|
155
|
+
## e.g. [[],[]] return zipped pairs in array as (attribute - name/value pair) hash
|
156
|
+
Hash[ field_names.zip(values) ]
|
95
157
|
end
|
96
158
|
|
97
159
|
|
160
|
+
|
98
161
|
def parse( values ) ## use read (from array) or read_values or read_row - why? why not?
|
99
|
-
|
162
|
+
|
163
|
+
## todo/fix:
|
164
|
+
## check if values is a string
|
165
|
+
## use Csv.parse_line to convert to array
|
166
|
+
## otherwise assume array of (string) values
|
167
|
+
|
168
|
+
h = self.class.build_hash( values )
|
100
169
|
update( h )
|
101
170
|
end
|
102
171
|
|
172
|
+
def to_a
|
173
|
+
## return array of all record values (typed e.g. float, integer, date, ..., that is,
|
174
|
+
## as-is and NOT auto-converted to string
|
175
|
+
## use to_csv or values for all string values)
|
176
|
+
self.class.fields.map { |field| send( field.name ) }
|
177
|
+
end
|
178
|
+
|
179
|
+
def to_h ## use to_hash - why? why not? - add attributes alias - why? why not?
|
180
|
+
self.class.build_hash( to_a )
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def values ## use/rename/alias to to_row too - why? why not?
|
185
|
+
## todo/fix: check for date and use own date to string format!!!!
|
186
|
+
to_a.map{ |value| value.to_s }
|
187
|
+
end
|
188
|
+
## use values as to_csv alias
|
189
|
+
## - reverse order? e.g. make to_csv an alias of value s- why? why not?
|
190
|
+
alias_method :to_csv, :values
|
191
|
+
|
103
192
|
|
104
193
|
|
105
194
|
def self.parse( txt_or_rows ) ## note: returns an (lazy) enumarator
|
@@ -120,7 +209,8 @@ def self.parse( txt_or_rows ) ## note: returns an (lazy) enumarator
|
|
120
209
|
## else
|
121
210
|
## pp row.fields
|
122
211
|
## pp row.to_hash
|
123
|
-
## fix/todo
|
212
|
+
## fix/todo!!!!!!!!!!!!!
|
213
|
+
## check for CSV::Row etc. - use row.to_hash ?
|
124
214
|
h = build_hash( row.fields )
|
125
215
|
## pp h
|
126
216
|
rec = new( h )
|
data/lib/csvrecord/builder.rb
CHANGED
@@ -11,22 +11,22 @@ class Builder # check: rename to RecordDefinition or RecordDsl or similar - why
|
|
11
11
|
|
12
12
|
def field( name, type=:string ) ## note: type defaults to string
|
13
13
|
puts " adding field >#{name}< with type >#{type}<"
|
14
|
-
@clazz.
|
14
|
+
@clazz.field( name, type ) ## auto-add getter and setter
|
15
15
|
end
|
16
16
|
|
17
17
|
def string( name )
|
18
18
|
puts " adding string field >#{name}<"
|
19
|
-
field( name,
|
19
|
+
field( name, :string )
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
puts " adding number field >#{name}<"
|
24
|
-
field( name,
|
22
|
+
def integer( name ) ## use number for alias for integer - why? why not???
|
23
|
+
puts " adding integer number field >#{name}<"
|
24
|
+
field( name, :integer )
|
25
25
|
end
|
26
26
|
|
27
27
|
def float( name )
|
28
28
|
puts " adding float number field >#{name}<"
|
29
|
-
field( name,
|
29
|
+
field( name, :float )
|
30
30
|
end
|
31
31
|
|
32
32
|
|
data/lib/csvrecord/version.rb
CHANGED
data/test/test_beer.rb
CHANGED
@@ -79,6 +79,16 @@ class TestBeer < MiniTest::Test
|
|
79
79
|
pp clazz2.class.name
|
80
80
|
pp Beer.class.name
|
81
81
|
|
82
|
+
clazz2b = CsvRecord.define do |rec| ## try "yield"-style dsl with block.arity == 1
|
83
|
+
rec.string :brewery
|
84
|
+
rec.string :city
|
85
|
+
rec.string :name
|
86
|
+
rec.float :abv
|
87
|
+
end
|
88
|
+
pp clazz2b
|
89
|
+
pp clazz2b.class.name
|
90
|
+
pp clazz2b.fields
|
91
|
+
|
82
92
|
assert true ## assume ok if we get here
|
83
93
|
end
|
84
94
|
|
@@ -101,6 +111,34 @@ class TestBeer < MiniTest::Test
|
|
101
111
|
pp beers
|
102
112
|
|
103
113
|
pp BeerClassic.fields
|
114
|
+
pp BeerClassic.field_names
|
115
|
+
pp BeerClassic.columns ## try fields alias
|
116
|
+
pp BeerClassic.column_names ## try field_names alias
|
117
|
+
|
118
|
+
assert_equal [:brewery, :city, :name, :abv], BeerClassic.field_names
|
119
|
+
assert_equal [String, String, String, Float], BeerClassic.field_types
|
120
|
+
|
121
|
+
|
122
|
+
assert_equal ['Andechser Klosterbrauerei',
|
123
|
+
'Andechs',
|
124
|
+
'Doppelbock Dunkel',
|
125
|
+
7.0], beers[0].to_a ## typed values
|
126
|
+
|
127
|
+
beer_hash = { brewery: 'Andechser Klosterbrauerei',
|
128
|
+
city: 'Andechs',
|
129
|
+
name: 'Doppelbock Dunkel',
|
130
|
+
abv: 7.0 }
|
131
|
+
assert_equal beer_hash, beers[0].to_h ## typed name/value pairs (hash)
|
132
|
+
|
133
|
+
assert_equal ['Andechser Klosterbrauerei',
|
134
|
+
'Andechs',
|
135
|
+
'Doppelbock Dunkel',
|
136
|
+
'7.0'], beers[0].values ## all string values
|
137
|
+
assert_equal ['Andechser Klosterbrauerei',
|
138
|
+
'Andechs',
|
139
|
+
'Doppelbock Dunkel',
|
140
|
+
'7.0'], beers[0].to_csv ## try to_csv alias
|
141
|
+
|
104
142
|
|
105
143
|
beer = BeerClassic.new
|
106
144
|
pp beer
|
@@ -151,10 +189,26 @@ class TestBeer < MiniTest::Test
|
|
151
189
|
name: 'Doppelbock Dunkel' )
|
152
190
|
pp beer2
|
153
191
|
|
154
|
-
|
192
|
+
assert_nil beer2.abv
|
155
193
|
assert_equal 'Andechser Klosterbrauerei', beer2.brewery
|
156
194
|
assert_equal 'Andechs', beer2.city
|
157
195
|
assert_equal 'Doppelbock Dunkel', beer2.name
|
158
196
|
end
|
159
197
|
|
198
|
+
def test_parse
|
199
|
+
values = ['Andechser Klosterbrauerei',
|
200
|
+
'Andechs',
|
201
|
+
'Doppelbock Dunkel',
|
202
|
+
'7.0']
|
203
|
+
|
204
|
+
beer = Beer.new
|
205
|
+
beer.parse( values )
|
206
|
+
|
207
|
+
assert_equal values, beer.values
|
208
|
+
assert_equal values[0], beer.brewery
|
209
|
+
assert_equal values[1], beer.city
|
210
|
+
assert_equal values[2], beer.name
|
211
|
+
assert_equal values[3].to_f, beer.abv
|
212
|
+
end
|
213
|
+
|
160
214
|
end # class TestBeer
|