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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7bec20faf657130128c9a70953bc48160b3e1d99
4
- data.tar.gz: 92900195cb8f791286e4d07920b4f5d1ebb4b13a
3
+ metadata.gz: d1458e4244f8a88c335b08c7e4c21ff576ba07fe
4
+ data.tar.gz: c44ca7e8f52667c0a62895c97e86ebe501b18c82
5
5
  SHA512:
6
- metadata.gz: c02849458381b9dd23643f0a6f9358c04984ef69aa5e2b72c61a791b3da7340a431049dd08a0059a09c0c188c5c5fbafedeaf815fe062d9ab4c61f8f704ce75d
7
- data.tar.gz: b79e7e35a4d016ac38cbc281d59a534df054d508d8ccc45130f1b351850622ed0991e59336b564c42aa1919c14da62de973b1e797b44d8ae26e428e80db2a106
6
+ metadata.gz: dabfc7c26f4e69548f2791c9474f4b3579a90c17e14bccfc5f9c0db4285f8e4610b6ef70a4bd6560547a828ff55c4d38235a8201a57f7fb1c615e742c277bf15
7
+ data.tar.gz: 4f5879e6827cf1a1d56c989242bd13b672fe6abf28abff1743219921ed88d55ab238eedf763ff5bf592a07acd9aa0a3e0504673e79fbaeadd931c3e1dd6db7d6
@@ -12,36 +12,94 @@ module CsvRecord
12
12
  attr_reader :name, :type
13
13
 
14
14
  def initialize( name, type )
15
- ## todo: always symbol-ify (to_sym) name and type - why? why not?
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
- builder.instance_eval(&block)
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
- fields << Field.new( name, type )
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 == :string || type == 'string' || type == String
130
+ if type == String
65
131
  value ## pass through as is
66
- elsif type == :float || type == 'float' || type == Float
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 == :number || type == 'number' || type == Integer
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
- ## raise exception about unknow type
77
- puts "!!!! unknown type >#{type}< - don't know how to convert/typecast string value >#{value}<"
78
- value
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
- h = {}
89
- values.each_with_index do |value,i|
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
- h = self.build_hash( values )
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: use row.to_hash
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 )
@@ -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.add_field( name, type ) ## auto-add getter and setter
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, 'string' )
19
+ field( name, :string )
20
20
  end
21
21
 
22
- def number( name ) ## use for alias for integer ???
23
- puts " adding number field >#{name}<"
24
- field( name, 'number' )
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, 'float' )
29
+ field( name, :float )
30
30
  end
31
31
 
32
32
 
@@ -5,7 +5,7 @@ module CsvRecord
5
5
 
6
6
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
7
7
  MINOR = 1
8
- PATCH = 0
8
+ PATCH = 1
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
 
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
- assert_equal nil, beer2.abv
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csvrecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer