csvrecord 0.4.0 → 0.4.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: 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