csvrecord 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77369b93b97cff13a7159870a4343cb22ad86dec
4
- data.tar.gz: cbc687395deb700c55ed7ca79687da11a7fb8913
3
+ metadata.gz: 3f9b5900a09323251bb9cf7fef7211c5692a5ac3
4
+ data.tar.gz: db705e1ba91bcd30ac538947d1518521a92ce76a
5
5
  SHA512:
6
- metadata.gz: f8d27de0a2c8e8ee2ddfcc024802845a6bce1f05dbc94aa1911fec1cc287837a5b5739e46e93cd5d6db6af678c6ad80e326ea09850c95bb342a27c5063d61fdc
7
- data.tar.gz: 548b5db8a78788d5c5e624ed44420b961bf0668f9289c876825fb7f09314bc0281f42ca2eb7d6dba11ece65c58de985e6630889230652524f31481e2603cecf2
6
+ metadata.gz: 34c7f0d23df73725a8f6616024cda30ab89938e94e1032727b8caee434263dc2551547c8f6599c6294f6fa9fe0d1a9255ccd957a2ef6dda277c6e7f044506bc2
7
+ data.tar.gz: 49a205a98277d5e7842f06a052c5306cb3ce475de9fe3fa056fef411f923a96bf2f254b70b07f70db771f09832630bd95b40b9675992bcb4c8b7a8a750ed7ba1
@@ -5,7 +5,6 @@ README.md
5
5
  Rakefile
6
6
  lib/csvrecord.rb
7
7
  lib/csvrecord/base.rb
8
- lib/csvrecord/builder.rb
9
8
  lib/csvrecord/version.rb
10
9
  test/data/beer.csv
11
10
  test/data/beer11.csv
data/Rakefile CHANGED
@@ -18,6 +18,7 @@ Hoe.spec 'csvrecord' do
18
18
  self.history_file = 'HISTORY.md'
19
19
 
20
20
  self.extra_deps = [
21
+ ['record', '>=1.1.1'],
21
22
  ['csvreader']
22
23
  ]
23
24
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  ###
4
4
  # 3rd party gems
5
+ require 'record'
5
6
  require 'csvreader'
6
7
 
7
8
 
@@ -9,7 +10,6 @@ require 'csvreader'
9
10
  # our own code
10
11
  require 'csvrecord/version' # let version always go first
11
12
  require 'csvrecord/base'
12
- require 'csvrecord/builder'
13
13
 
14
14
 
15
15
  puts CsvRecord.banner # say hello
@@ -3,270 +3,10 @@
3
3
 
4
4
  module CsvRecord
5
5
 
6
- ## note on naming:
7
- ## use naming convention from awk and tabular data package/json schema for now
8
- ## use - records (use rows for "raw" untyped (string) data rows )
9
- ## - fields (NOT columns or attributes) -- might add an alias later - why? why not?
10
-
11
- class Field ## ruby record class field
12
- attr_reader :name, :type
13
-
14
- ## zero-based position index (0,1,2,3,...)
15
- ## todo: use/rename to index/idx/pos - add an alias name - why?
16
- attr_reader :num
17
-
18
- def initialize( name, num, type )
19
- ## note: always symbol-ify (to_sym) name and type
20
-
21
- ## todo: add title or titles for header field/column title as is e.g. 'Team 1' etc.
22
- ## incl. numbers only or even an empty header title
23
- @name = name.to_sym
24
- @num = num
25
-
26
- if type.is_a?( Class )
27
- @type = type ## assign class to its own property - why? why not?
28
- else
29
- @type = Type.registry[type.to_sym]
30
- if @type.nil?
31
- puts "!!!! warn unknown type >#{type}< - no class mapping found; add missing type to CsvRecord::Type.registry[]"
32
- ## todo/fix: raise exception!!!!
33
- end
34
- end
35
- end
36
-
37
-
38
- def typecast( value ) ## cast (convert) from string value to type (e.g. float, integer, etc.)
39
- ## todo: add typecast to class itself (monkey patch String/Float etc. - why? why not)
40
- ## use __typecast or something?
41
- ## or use a new "wrapper" class Type::String or StringType - why? why not?
42
-
43
- ## convert string value to (field) type
44
- if @type == String
45
- value ## pass through as is
46
- elsif @type == Float
47
- ## note: allow/check for nil values - why? why not?
48
- float = (value.nil? || value.empty?) ? nil : value.to_f
49
- puts "typecast >#{value}< to float number >#{float}<"
50
- float
51
- elsif @type == Integer
52
- number = (value.nil? || value.empty?) ? nil : value.to_i(10) ## always use base10 for now (e.g. 010 => 10 etc.)
53
- puts "typecast >#{value}< to integer number >#{number}<"
54
- number
55
- else
56
- ## todo/fix: raise exception about unknow type
57
- pp @type
58
- puts "!!!! unknown type >#{@type}< - don't know how to convert/typecast string value >#{value}<"
59
- value
60
- end
61
- end
62
- end # class Field
63
-
64
-
65
-
66
- class Type ## todo: use a module - it's just a namespace/module now - why? why not?
67
-
68
- ## e.g. use Type.registry[:string] = String etc.
69
- ## note use @@ - there is only one registry
70
- def self.registry() @@registry ||={} end
71
-
72
- ## add built-in types:
73
- registry[:string] = String
74
- registry[:integer] = Integer ## todo/check: add :number alias for integer? why? why not?
75
- registry[:float] = Float
76
- ## todo: add some more
77
- end # class Type
78
-
79
-
80
-
81
- def self.define( &block )
82
- builder = Builder.new
83
- if block.arity == 1
84
- block.call( builder )
85
- ## e.g. allows "yield" dsl style e.g.
86
- ## CsvRecord.define do |rec|
87
- ## rec.string :team1
88
- ## rec.string :team2
89
- ## end
90
- ##
91
- else
92
- builder.instance_eval( &block )
93
- ## e.g. shorter "instance eval" dsl style e.g.
94
- ## CsvRecord.define do
95
- ## string :team1
96
- ## string :team2
97
- ## end
98
- end
99
- builder.to_record
100
- end
101
-
102
-
103
- ###########################################
104
- ## "magic" lazy auto-build schema from headers versions
105
-
106
- def self.build_class( headers ) ## check: find a better name - why? why not?
107
- ## (auto-)build record class from an array of headers
108
- ## add fields (all types will be string for now)
109
- clazz = Class.new(Base)
110
- headers.each do |header|
111
- ## downcase and remove all non-ascii chars etc.
112
- ## todo/fix: remove all non-ascii chars!!!
113
- ## todo: check if header starts with a number too!!
114
- name = header.downcase.gsub( ' ', '_' )
115
- name = name.to_sym ## symbol-ify
116
- clazz.field( name )
117
- end
118
- clazz
119
- end
120
-
121
- def self.read( path, sep: Csv.config.sep )
122
- headers = CsvReader.header( path, sep: sep )
123
-
124
- clazz = build_class( headers )
125
- clazz.read( path, sep: sep )
126
- end
127
-
128
- def self.foreach( path, sep: Csv.config.sep, &block )
129
- headers = CsvReader.header( path, sep: sep )
130
-
131
- clazz = build_class( headers )
132
- clazz.foreach( path, sep: sep, &block )
133
- end
134
-
135
-
136
-
137
-
138
- class Base
139
-
140
- def self.fields ## note: use class instance variable (@fields and NOT @@fields)!!!! (derived classes get its own copy!!!)
141
- @fields ||= []
142
- end
143
-
144
- def self.field_names ## rename to header - why? why not?
145
- ## return header row, that is, all field names in an array
146
- ## todo: rename to field_names or just names - why? why not?
147
- ## note: names are (always) symbols!!!
148
- fields.map {|field| field.name }
149
- end
150
-
151
- def self.field_types
152
- ## note: types are (always) classes!!!
153
- fields.map {|field| field.type }
154
- end
155
-
156
-
157
-
158
- def self.field( name, type=:string )
159
- num = fields.size ## auto-calc num(ber) / position index - always gets added at the end
160
- field = Field.new( name, num, type )
161
- fields << field
162
-
163
- define_field( field ) ## auto-add getter,setter,parse/typecast
164
- end
165
-
166
- def self.define_field( field )
167
- name = field.name ## note: always assumes a "cleaned-up" (symbol) name
168
- num = field.num
169
-
170
- define_method( name ) do
171
- instance_variable_get( "@values" )[num]
172
- end
173
-
174
- define_method( "#{name}=" ) do |value|
175
- instance_variable_get( "@values" )[num] = value
176
- end
177
-
178
- define_method( "parse_#{name}") do |value|
179
- instance_variable_get( "@values" )[num] = field.typecast( value )
180
- end
181
- end
182
-
183
- ## column/columns aliases for field/fields
184
- ## use self << with alias_method - possible? works? why? why not?
185
- def self.column( name, type=:string ) field( name, type ); end
186
- def self.columns() fields; end
187
- def self.column_names() field_names; end
188
- def self.column_types() field_types; end
189
-
190
-
191
-
192
-
193
- def self.build_hash( values ) ## find a better name - build_attrib? or something?
194
- ## convert to key-value (attribute) pairs
195
- ## puts "== build_hash:"
196
- ## pp values
197
-
198
- ## e.g. [[],[]] return zipped pairs in array as (attribute - name/value pair) hash
199
- Hash[ field_names.zip(values) ]
200
- end
201
-
202
-
203
-
204
- def self.typecast( new_values )
205
- values = []
206
-
207
- ##
208
- ## todo: check that new_values.size <= fields.size
209
- ##
210
- ## fields without values will get auto-filled with nils (or default field values?)
211
-
212
- ##
213
- ## use fields.zip( new_values ).map |field,value| ... instead - why? why not?
214
- fields.each_with_index do |field,i|
215
- value = new_values[i] ## note: returns nil if new_values.size < fields.size
216
- values << field.typecast( value )
217
- end
218
- values
219
- end
220
-
221
-
222
- def parse( new_values ) ## use read (from array) or read_values or read_row - why? why not?
223
-
224
- ## todo: check if values overshadowing values attrib is ok (without warning?) - use just new_values (not values)
225
-
226
- ## todo/fix:
227
- ## check if values is a string
228
- ## use Csv.parse_line to convert to array
229
- ## otherwise assume array of (string) values
230
- @values = self.class.typecast( new_values )
231
- self ## return self for chaining
232
- end
233
-
234
-
235
- def values
236
- ## return array of all record values (typed e.g. float, integer, date, ..., that is,
237
- ## as-is and NOT auto-converted to string
238
- ## use to_csv or values for all string values)
239
- @values
240
- end
241
-
242
-
243
-
244
- def [](key)
245
- if key.is_a? Integer
246
- @values[ key ]
247
- elsif key.is_a? Symbol
248
- ## try attribute access
249
- send( key )
250
- else ## assume string
251
- ## downcase and symbol-ize
252
- ## remove spaces too -why? why not?
253
- ## todo/fix: add a lookup mapping for (string) titles (Team 1, etc.)
254
- send( key.downcase.to_sym )
255
- end
256
- end
257
-
258
-
259
-
260
- def to_h ## use to_hash - why? why not? - add attributes alias - why? why not?
261
- self.class.build_hash( @values )
262
- end
263
-
264
-
265
- def to_csv ## use/rename/alias to to_row too - why? why not?
266
- ## todo/fix: check for date and use own date to string format!!!!
267
- @values.map{ |value| value.to_s }
268
- end
6
+ ########################
7
+ # Base
269
8
 
9
+ class Base < Record::Base
270
10
 
271
11
  def self.foreach( path, sep: Csv.config.sep, headers: true )
272
12
  CsvReader.foreach( path, sep: sep, headers: headers ) do |row|
@@ -309,25 +49,54 @@ end
309
49
 
310
50
 
311
51
 
312
- def initialize( **kwargs )
313
- @values = []
314
- update( kwargs )
52
+ def to_csv ## use/rename/alias to to_row too - why? why not?
53
+ ## todo/fix: check for date and use own date to string format!!!!
54
+ @values.map{ |value| value.to_s }
315
55
  end
316
56
 
317
- def update( **kwargs )
318
- pp kwargs
319
- kwargs.each do |name,value|
320
- ## note: only convert/typecast string values
321
- if value.is_a?( String )
322
- send( "parse_#{name}", value ) ## note: use parse_<name> setter (for typecasting)
323
- else ## use "regular" plain/classic attribute setter
324
- send( "#{name}=", value )
325
- end
57
+ end # class Base
58
+
59
+
60
+
61
+
62
+ ###########################################
63
+ ## "magic" lazy auto-build schema from headers versions
64
+
65
+ def self.build_class( headers ) ## check: find a better name - why? why not?
66
+ ## (auto-)build record class from an array of headers
67
+ ## add fields (all types will be string for now)
68
+ clazz = Class.new( Base )
69
+ headers.each do |header|
70
+ ## downcase and remove all non-ascii chars etc.
71
+ ## todo/fix: remove all non-ascii chars!!!
72
+ ## todo: check if header starts with a number too!!
73
+ name = header.downcase.gsub( ' ', '_' )
74
+ name = name.to_sym ## symbol-ify
75
+ clazz.field( name )
326
76
  end
77
+ clazz
78
+ end
327
79
 
328
- ## todo: check if args.first is an array (init/update from array)
329
- self ## return self for chaining
80
+ def self.read( path, sep: Csv.config.sep )
81
+ headers = CsvReader.header( path, sep: sep )
82
+
83
+ clazz = build_class( headers )
84
+ clazz.read( path, sep: sep )
85
+ end
86
+
87
+ def self.foreach( path, sep: Csv.config.sep, &block )
88
+ headers = CsvReader.header( path, sep: sep )
89
+
90
+ clazz = build_class( headers )
91
+ clazz.foreach( path, sep: sep, &block )
92
+ end
93
+
94
+
95
+ #########
96
+ # alternative class (record) builder
97
+
98
+ def self.define( &block ) ## check: rename super_class to base - why? why not?
99
+ Record.define( Base, &block )
330
100
  end
331
101
 
332
- end # class Base
333
102
  end # module CsvRecord
@@ -5,7 +5,7 @@ module CsvRecord
5
5
 
6
6
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
7
7
  MINOR = 4
8
- PATCH = 0
8
+ PATCH = 1
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
 
@@ -21,4 +21,4 @@ module CsvRecord
21
21
  File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
22
22
  end
23
23
 
24
- end # class CsvRecord
24
+ end # module CsvRecord
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.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
@@ -10,6 +10,20 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2018-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: record
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.1.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: csvreader
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -70,7 +84,6 @@ files:
70
84
  - Rakefile
71
85
  - lib/csvrecord.rb
72
86
  - lib/csvrecord/base.rb
73
- - lib/csvrecord/builder.rb
74
87
  - lib/csvrecord/version.rb
75
88
  - test/data/beer.csv
76
89
  - test/data/beer11.csv
@@ -1,37 +0,0 @@
1
- # encoding: utf-8
2
-
3
- ## (record) builder mini language / domain-specific language (dsl)
4
-
5
- module CsvRecord
6
-
7
- class Builder # check: rename to RecordDefinition or RecordDsl or similar - why? why not?
8
- def initialize
9
- @clazz = Class.new(Base)
10
- end
11
-
12
- def field( name, type=:string ) ## note: type defaults to string
13
- puts " adding field >#{name}< with type >#{type}<"
14
- @clazz.field( name, type ) ## auto-add getter and setter
15
- end
16
-
17
- def string( name )
18
- puts " adding string field >#{name}<"
19
- field( name, :string )
20
- end
21
-
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
- end
26
-
27
- def float( name )
28
- puts " adding float number field >#{name}<"
29
- field( name, :float )
30
- end
31
-
32
-
33
- def to_record ## check: rename to just record or obj or finish or end something?
34
- @clazz
35
- end
36
- end # class Builder
37
- end # module CsvRecord