dreader 0.4.2 → 1.0.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/README.org ADDED
@@ -0,0 +1,794 @@
1
+ #+TITLE: Dreader
2
+ #+AUTHOR: Adolfo Villafiorita
3
+ #+STARTUP: showall
4
+
5
+ Dreader is a simple DSL built on top of [[https://github.com/roo-rb/roo][Roo]] to read and process
6
+ tabular data (CSV, LibreOffice, Excel) in a simple and structured way.
7
+
8
+ Main advantages:
9
+
10
+ 1. All code to parse input data has the same structure, simplifying
11
+ code management and understanding (convention over configuration).
12
+ 2. It favors a declarative approach, clearly identifying from which
13
+ data has to be read and in which way.
14
+ 3. Has facilities to run simulations, to debug and check code and
15
+ data.
16
+
17
+ We use Dreader for importing fairly big files (in the order of
18
+ 10K-100K records) in MIP, an ERP to manage distribution of bins to the
19
+ population. The main issues we had before using Dreader were errors
20
+ and exceptional cases in the input data. We also had to manage
21
+ several small variations in the input files (coming from different
22
+ ERPs) and Dreader helped us standardizing the input code.
23
+
24
+ The gem depends on =roo=, from which it leverages all data
25
+ reading/parsing facilities keeping its size in about 250 lines of
26
+ code.
27
+
28
+ It should be relatively easy to use; /dreader/ stands for /d/ata /r/eader.
29
+
30
+ * Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ #+BEGIN_EXAMPLE ruby
35
+ gem 'dreader'
36
+ #+END_EXAMPLE
37
+
38
+ And then execute:
39
+
40
+ #+BEGIN_EXAMPLE
41
+ $ bundle
42
+ #+END_EXAMPLE
43
+
44
+ Or install it yourself as:
45
+
46
+ #+BEGIN_EXAMPLE
47
+ $ gem install dreader
48
+ #+END_EXAMPLE
49
+
50
+
51
+ * Usage
52
+
53
+ ** Quick start
54
+
55
+ Print name and age of people from the following data:
56
+
57
+ | Name | Date of birth |
58
+ |------------------+-----------------|
59
+ | Forest Whitaker | July 15, 1961 |
60
+ | Daniel Day-Lewis | April 29, 1957 |
61
+ | Sean Penn | August 17, 1960 |
62
+
63
+ #+BEGIN_EXAMPLE ruby
64
+ require 'dreader'
65
+
66
+ class Reader < Dreader::Engine
67
+ options do
68
+ # we start reading from row 2
69
+ first_row 2
70
+ end
71
+
72
+ column :name do
73
+ doc "column A contains :name, a string; doc is optional"
74
+ colref 'A'
75
+ end
76
+
77
+ # column B contains :birthdate, a date. We can use a Hash and omit
78
+ # colref
79
+ column({ birthdate: 'B' }) do
80
+ process do |c|
81
+ Date.parse(c)
82
+ end
83
+ end
84
+
85
+ # add as many example lines as you want to show examples of good
86
+ # records these example lines are added to the template generated with
87
+ # generate_template
88
+ example { name: "John", birthday: "27/03/2020" }
89
+
90
+ # for each line, :age is computed from :birthdate
91
+ virtual_column :age do
92
+ process do |row|
93
+ birthdate = row[:birthdate][:value]
94
+ birthday = Date.new(Date.today.year, birthdate.month, birthdate.day)
95
+ today = Date.today
96
+ [0, today.year - birthdate.year - (birthday < today ? 1 : 0)].max
97
+ end
98
+ end
99
+
100
+ # this is how we process each line of the input file
101
+ mapping do |row|
102
+ r = Dreader::Util.simplify(row)
103
+ puts "#{r[:name]} is #{r[:age]} years old (born on #{r[:birthdate]})"
104
+ end
105
+ end
106
+
107
+ reader = Reader.new
108
+
109
+ # read the file
110
+ reader.read filename: "Birthdays.ods"
111
+ # compute the virtual columns
112
+ reader.virtual_columns
113
+ # run the mapping declaration
114
+ reader.process
115
+
116
+ #
117
+ # Here we can do further processing on the data
118
+ #
119
+ File.open("ages.txt", "w") do |file|
120
+ reader.table.each do |row|
121
+ unless row[:row_errors].any?
122
+ file.puts "#{row[:name][:value]} #{row[:age][:value]}"
123
+ end
124
+ end
125
+ end
126
+ #+END_EXAMPLE
127
+
128
+ ** Gentler Introduction
129
+
130
+ To write an import function with Dreader:
131
+
132
+ - Declare which is the input file and where we can find data (Sheet
133
+ and first row)
134
+ - Declare the content of columns and how to check raw data, parse data,
135
+ and check parsed data
136
+ - Add virtual columns, that is, columns computed from other values
137
+ in the row
138
+ - Specify how to process data each line. This is where you do the actual work
139
+ (for instance, if you process a file line by line) or put together data for
140
+ processing after the file has been fully read --- see the next step.
141
+
142
+ Dreader has now collected and shaped the data according to your instructions
143
+ and collected errors in the process. We are now ready to do the actual
144
+ processing:
145
+
146
+ - Do the processing
147
+
148
+ Each step is described in more details in the following sections.
149
+
150
+ *** Declare which is the input file and where we can find data
151
+
152
+ Require =dreader= and declare a class which inherits from =Dreader::Engine=:
153
+
154
+
155
+ #+BEGIN_EXAMPLE ruby
156
+ require 'dreader'
157
+
158
+ class Reader < Dreader::Engine
159
+ [...]
160
+ end
161
+ #+END_EXAMPLE
162
+
163
+ In the class specify parsing option, using the following syntax:
164
+
165
+ #+BEGIN_EXAMPLE ruby
166
+ options do
167
+ filename 'example.ods'
168
+
169
+ sheet 'Sheet 1'
170
+
171
+ first_row 1
172
+ last_row 20
173
+
174
+ # optional (this allows to integrate with other applications already
175
+ # using a logger)
176
+ logger Logger.new
177
+ logger_level Logger::INFO
178
+ end
179
+ #+END_EXAMPLE
180
+
181
+ where:
182
+
183
+ - (optional) =filename= is the file to read. If not specified, you will
184
+ have to supply a filename when loading the file (see =read=, below).
185
+ The extension determines the file type. *Use =.tsv= for tab-separated
186
+ files.*
187
+ - (optional) =first_row= is the first line to read (use =2= if your file
188
+ has a header)
189
+ - (optional) =last_row= is the last line to read. If not specified, we
190
+ will rely on =roo= to determine the last row. This is useful for
191
+ those files in which you only want to process some of the content or
192
+ contain "garbage" after the records.
193
+ - (optional) =sheet= is the sheet name or number to read from. If not
194
+ specified, the first (default) sheet is used
195
+
196
+ #+BEGIN_NOTES
197
+ You can override some of the defaults by passing a hash as argument to
198
+ the =read= function. For instance:
199
+
200
+ #+BEGIN_EXAMPLE ruby
201
+ i.read filename: another_filepath
202
+ #+END_EXAMPLE
203
+
204
+ will read data from =another_filepath=, rather than from the filename
205
+ specified in the options. This might be useful, for instance, if the
206
+ same specification has to be used for different files.
207
+ #+END_NOTES
208
+
209
+
210
+ *** Declare the content of columns and how to parse them
211
+
212
+ Declare the columns you want to read by assigning them a name and a column
213
+ reference.
214
+
215
+ There are two notations:
216
+
217
+ #+BEGIN_EXAMPLE ruby
218
+ # First notation, colref is put in the block
219
+ i.column :name do
220
+ colref 'A'
221
+ end
222
+
223
+ # Second notation, a hash is passed in the name
224
+ i.column({ name: 'A' }) do
225
+ end
226
+ #+END_EXAMPLE
227
+
228
+ The reference to a column can either be a letter or a number. First column
229
+ is ='A'= or =1=.
230
+
231
+ The =column= declaration can contain Ruby blocks:
232
+
233
+ - one or more =check_raw= block check raw data as read from the input
234
+ file. They can be used, for instance, to verify presence of a value in the
235
+ input file. *Check must return true if there are no errors; any other
236
+ value (e.g. an array of messages) is considered an error.*
237
+ - =process= can be used to transform data into something closer to the input
238
+ data required for the importing (e.g., it can be used for downcase or
239
+ strip a string)
240
+ - one or more =check= block perform a check on the =process=ed data, to check
241
+ for errors. They can be used, for instance, to check that a model built with
242
+ =process= is valid. *Check must return true if there are no errors.*
243
+
244
+ #+begin_example
245
+ i.column({ name: 'A' }) do
246
+ check_raw do |cell|
247
+ !cell.nil?
248
+ end
249
+ end
250
+ #+end_example
251
+
252
+ #+begin_quote
253
+ *If you declare more than a check block of the same type per column, use a
254
+ unique symbol to distinguish the blocks or the error messages will be
255
+ overwritten*.
256
+ #+end_quote
257
+
258
+ #+begin_example
259
+ i.column({ name: 'A' }) do
260
+ check_raw :must_be_non_nil do |cell|
261
+ !cell.nil?
262
+ end
263
+
264
+ check_raw :first_letter_must_be_a do |cell|
265
+ cell[0] == 'A'
266
+ end
267
+ end
268
+ #+end_example
269
+
270
+ #+begin_quote
271
+ =process= is always executed before =check=. If you want to check raw data
272
+ use the =check_raw= directive.
273
+ #+end_quote
274
+
275
+ #+begin_quote
276
+ There can be only one process block. *If you define more than one per
277
+ column, only the last one is executed.*
278
+ #+end_quote
279
+
280
+ #+begin_example
281
+ i.column({ name: 'A' }) do
282
+ check_raw do |cell|
283
+ # Here cell is like in the input file
284
+ end
285
+
286
+ process do |cell|
287
+ cell.upcase
288
+ end
289
+
290
+ check do |cell|
291
+ # Here cell is upcase and
292
+ end
293
+ end
294
+ #+end_example
295
+
296
+ For instance, given the tabular data:
297
+
298
+ | Name | Date of birth |
299
+ |------------------+-----------------|
300
+ | Forest Whitaker | July 15, 1961 |
301
+ | Daniel Day-Lewis | April 29, 1957 |
302
+ | Sean Penn | August 17, 1960 |
303
+
304
+ we could use the following declaration to specify the data to read:
305
+
306
+ #+BEGIN_EXAMPLE ruby
307
+ # we want to access column 1 using :name (1 and A are equivalent)
308
+ # :name should be non nil and of length greater than 0
309
+ column :name do
310
+ colref 1
311
+ check do |x|
312
+ x and x.length > 0
313
+ end
314
+ end
315
+
316
+ # we want to access column 2 (Date of birth) using :birthdate
317
+ column :birthdate do
318
+ colref 2
319
+
320
+ # make sure the column is transformed into a Date
321
+ process do |x|
322
+ Date.parse(x)
323
+ end
324
+
325
+ # check age is a date (check is invoked on the value returned
326
+ # by process)
327
+ check do |x|
328
+ x.class == Date
329
+ end
330
+ end
331
+ #+END_EXAMPLE
332
+
333
+ #+BEGIN_NOTES
334
+ 1. The column name can be anything Ruby can use as a key for a Hash,
335
+ such as, for instance, symbols, strings, and even object instances.
336
+ 2. =colref= can be a string (e.g., ='A'=) or an integer, with
337
+ 1 and "A" being the first column.
338
+ 3. *You need to declare only the columns you want to import.* For
339
+ instance, we could skip the declaration for column 1, if 'Date of
340
+ Birth' is the only data we want to import
341
+ 4. If =process= and =check= are specified, then =check= will receive the
342
+ result of invoking =process= on the cell value. This makes sense if
343
+ process is used to make the cell value more accessible to ruby code
344
+ (e.g., transforming a string into an integer).
345
+ #+END_NOTES
346
+
347
+ If there are different columns that have to be read and processed in the same
348
+ way, =columns= (notice the plural form) allows for a more compact
349
+ representation:
350
+
351
+ #+BEGIN_EXAMPLE ruby
352
+ columns { a: 'A', b: 'B' }
353
+ #+END_EXAMPLE
354
+
355
+ is equivalent to:
356
+
357
+ #+BEGIN_EXAMPLE ruby
358
+ column :a do
359
+ colref 'A'
360
+ end
361
+
362
+ column :b do
363
+ colref 'B'
364
+ end
365
+ #+END_EXAMPLE
366
+
367
+ =columns= accepts a code block, which can be used to add =process= and =check=
368
+ declarations:
369
+
370
+ #+BEGIN_EXAMPLE ruby
371
+ columns({ a: 'A', b: 'B' }) do
372
+ process do |cell|
373
+ ...
374
+ end
375
+ end
376
+ #+END_EXAMPLE
377
+
378
+ See [[file:examples/wikipedia_us_cities/us_cities_bulk_declare.rb][us_cities_bulk_declare.rb]] for an example of =columns=.
379
+
380
+ #+BEGIN_NOTES
381
+ If you use code blocks, don't forget to put in parentheses the
382
+ column mapping, or the Ruby parser won't be able to distinguish the
383
+ hash from the code block.
384
+ #+END_NOTES
385
+
386
+
387
+ *** Add virtual columns
388
+
389
+ Sometimes it is convenient to aggregate or otherwise manipulate the data
390
+ read from each row, before doing the actual processing.
391
+
392
+ For instance, we might have a table with dates of birth, while we are
393
+ really interested in the age of people.
394
+
395
+ In such cases, we can use virtual column. A *virtual column* allows
396
+ one to add a column to the data read, computed using the values of
397
+ other cells in the same row.
398
+
399
+ The following declaration adds an =age= column to each row of the data
400
+ read from the previous example:
401
+
402
+ #+BEGIN_EXAMPLE ruby
403
+ virtual_column :age do
404
+ process do |row|
405
+ # the function `compute_birthday` has to be defined
406
+ compute_birthday(row[:birthdate])
407
+ end
408
+ end
409
+ #+END_EXAMPLE
410
+
411
+ Virtual columns are, of course, available to the =mapping= directive
412
+ (see below).
413
+
414
+
415
+ *** Specify how to process each line
416
+
417
+ The =mapping= directive specifies what to do with each line read. The
418
+ =mapping= declaration takes an arbitrary piece of ruby code, which can
419
+ reference the fields using the column names we declared.
420
+
421
+ For instance the following code gets the value of column =:name=, the
422
+ value of column =:age= and prints them to standard output
423
+
424
+ #+BEGIN_EXAMPLE ruby
425
+ mapping do |row|
426
+ puts "#{row[:name][:value]} is #{row[:age][:value]} years old"
427
+ end
428
+ #+END_EXAMPLE
429
+
430
+ The data read from each row of our input data is stored in a hash. The hash
431
+ uses column names as the primary key and stores the values in the =:value=
432
+ key.
433
+
434
+
435
+ *** Process data
436
+
437
+ If =mapping= does not work for your data processing activities (e.g., you need
438
+ to make elaborations on data which span different rows), you can add your own
439
+ code after the =process= directive.
440
+
441
+ A typical scenario works as follows:
442
+
443
+ 1. Instantiate the class: ~i = Reader.new~
444
+
445
+ 1. Use =i.read= or =i.load= (synonyms), to read all data.
446
+
447
+ #+BEGIN_EXAMPLE ruby
448
+ i.read
449
+ #+END_EXAMPLE
450
+
451
+ 2. Use =errors= to see whether any of the check functions failed:
452
+
453
+ #+BEGIN_EXAMPLE ruby
454
+ array_of_hashes = i.errors
455
+ array_of_hashes.each do |error_hash|
456
+ puts error_hash
457
+ end
458
+ #+END_EXAMPLE
459
+
460
+ 3. Use =virtual_columns= to generate the virtual columns:
461
+
462
+ #+BEGIN_EXAMPLE ruby
463
+ i.virtual_columns
464
+ #+END_EXAMPLE
465
+
466
+ (Optionally: check again for errors.)
467
+
468
+ 4. Use the =process= function to execute the =mapping=
469
+ directive on each line read from the file.
470
+
471
+ #+BEGIN_EXAMPLE ruby
472
+ i.process
473
+ #+END_EXAMPLE
474
+
475
+ (Optionally: check again for errors.)
476
+
477
+ 5. Add your own code to process data. Use the =table= function to access data.
478
+
479
+ Look in the examples directory for further details and a couple of
480
+ working examples.
481
+
482
+
483
+ *** Managing Errors
484
+
485
+ **** Finding errors in input data
486
+
487
+ Dreader collects errors in three specific ways:
488
+
489
+ 1. In each column specification, using =check_raw= and =check=. This allows
490
+ to check each field for errors (e.g., a =nil= value in a cell)
491
+ 2. In virtual columns, using =check_raw= and =check=. This allows to perform
492
+ more complex checks by putting together all the values read from a row
493
+ (e.g., =to_date= occurs before =from_date=)
494
+
495
+ The following, for instance checks that name or surname have a valid value:
496
+
497
+ #+begin_example ruby
498
+ virtual_column :global_check do
499
+ doc "Name or Surname must exist"
500
+ check :name_or_surname_must_be_defined do |row|
501
+ row[:name] || row[:surname]
502
+ end
503
+ end
504
+ #+end_example
505
+
506
+ If you prefer, you can also define a virtual column that contains the value of
507
+ the check:
508
+
509
+ #+begin_example ruby
510
+ virtual_column :name_or_surname_exist do
511
+ doc "Name or Surname must exist"
512
+ process do |row|
513
+ row[:name] || row[:surname]
514
+ end
515
+ end
516
+ #+end_example
517
+
518
+ You can then act in the mapping directive according to value returned by the
519
+ virtual column:
520
+
521
+ #+begin_example ruby
522
+ mapping do |row|
523
+ unless row[:global_check][:value] == false
524
+ [...]
525
+ end
526
+ #+end_example
527
+
528
+ **** Managing Errors
529
+
530
+ You can check for errors in two different ways:
531
+
532
+ The first is in the =mapping= directive, where can check whether some checks for
533
+ the =row= failed, by:
534
+
535
+ 1. checking from the =:error= boolean key associated to each column, that is:
536
+
537
+ =row[<column_name>][:error]=
538
+
539
+ 2. looking at the value of the =:row_errors= key, which contains all error messages
540
+ generated for the row:
541
+
542
+ =row[:row_errors]=
543
+
544
+ 3. After the processing, by using the method =errors=, which lists all the errors.
545
+
546
+ The utility function =Dreader::Util.errors= takes as input the errors generated by
547
+ Dreader and extract those of a specific row and, optionally column:
548
+
549
+ #+begin_example ruby
550
+ # get all the errors at line 2
551
+ Dreader::Util.errors i.errors, 2
552
+
553
+ # get all the errors at line 2, column 'C'
554
+ Dreader::Util.errors i.errors, 2, 3
555
+ #+end_example
556
+
557
+
558
+ * Generating a Template from the specification
559
+
560
+ From version 0.6.0 =dreader= allows to generate a template starting from the
561
+ specification.
562
+
563
+ The template is generated by the following call:
564
+
565
+ #+begin_example ruby
566
+ generate_template template_filename: "template.xlsx"
567
+ #+end_example
568
+
569
+ (The =template_filename= directive can also be specified in the =options=
570
+ section).
571
+
572
+ The template contains the following rows:
573
+
574
+ - The first row contains the names of the columns, as specified in the
575
+ =columns= declarations and made into a human readable form.
576
+ - The second row contains the doc strings of the columns, if set.
577
+ - The remaining rows contain the example records added with the
578
+ =example= directive
579
+
580
+ The position of the first row is determined by the value of =first_row=, that
581
+ is, if =first_row= is 2 (content starts from the second row), the header row
582
+ is put in row 1.
583
+
584
+ Only Excel is supported, at the moment.
585
+
586
+ An example of template generation can be found in the Examples.
587
+
588
+ ** Digging deeper
589
+
590
+ If you need to perform elaborations which cannot be performed row by
591
+ row you can access all data, with the =table= method:
592
+
593
+ #+BEGIN_EXAMPLE ruby
594
+ i.read
595
+ i.table
596
+ #+END_EXAMPLE
597
+
598
+ The function =i.table= returns an array of Hashes. Each element of
599
+ the array is a row of the input file. Each element/row has the
600
+ following structure:
601
+
602
+ #+BEGIN_EXAMPLE ruby
603
+ {
604
+ col_name1: { <info about col_name_1 in row_j> },
605
+ [...]
606
+ col_nameN: { <info about col_name_N in row_j> },
607
+ row_errors: [ <errors associated to row> ],
608
+ row_number: <row number>
609
+ }
610
+ #+END_EXAMPLE
611
+
612
+ where =col_name1=, ..., =col_nameN= are the names you have assigned to
613
+ the columns and the information stored for each cell is the
614
+ following:
615
+
616
+ #+BEGIN_EXAMPLE ruby
617
+ {
618
+ value: ..., # the result of calling process on the cell
619
+ row_number: ..., # the row number
620
+ col_number: ..., # the column number
621
+ error: ... # the result of calling check on the cell processed value
622
+ }
623
+ #+END_EXAMPLE
624
+
625
+ (Note that virtual columns only store =value= and a Boolean =virtual=,
626
+ which is always =true=.)
627
+
628
+ Thus, for instance, given the example above returns:
629
+
630
+ #+BEGIN_EXAMPLE ruby
631
+ i.table
632
+ [
633
+ {
634
+ name: { value: "John", row_number: 1, col_number: 1, errors: nil },
635
+ age: { value: 30, row_number: 1, col_number: 2, errors: nil }
636
+ },
637
+ {
638
+ name: { value: "Jane", row_number: 2, col_number: 1, errors: nil },
639
+ age: { value: 31, row_number: 2, col_number: 2, errors: nil }
640
+ }
641
+ ]
642
+ #+END_EXAMPLE
643
+
644
+
645
+ * Simplifying the hash with the data read
646
+
647
+ The =Dreader::Util= class provides some functions to simplify the
648
+ hashes built by =dreader=. This is useful to simplify the code you
649
+ write and to genereate hashes you can pass, for instance, to
650
+ ActiveRecord creators.
651
+
652
+ ** Simplify removes everything but the values
653
+
654
+ =Dreader::Util.simplify hash= removes all information but the value
655
+ and making the value accessible directly from the name of the column.
656
+
657
+ #+BEGIN_EXAMPLE ruby
658
+ i.table[0]
659
+ { name: { value: "John", row_number: 1, col_number: 1, errors: nil },
660
+ age: { value: 30, row_number: 1, col_number: 2, errors: nil } }
661
+
662
+ Dreader::Util.simplify i.table[0]
663
+ { name: "John", age: 30 }
664
+ #+END_EXAMPLE
665
+
666
+ *As an additional bonus, it removes the keys =row_number= and =row_errors=,
667
+ which are not part of the data read, in the first place.*
668
+
669
+ ** Slice and Clean select columns
670
+
671
+ =Dreader::Util.slice hash, keys= and =Dreader::Util.clean hash, keys=,
672
+ where =keys= is an arrays of keys, are respectively used to select or
673
+ remove some keys from the hash returned by Dreader. (Notice that the
674
+ Ruby Hash class already provides similar methods.)
675
+
676
+ #+BEGIN_EXAMPLE ruby
677
+ i.table[0]
678
+ { name: { value: "John", row_number: 1, col_number: 1, errors: nil },
679
+ age: { value: 30, row_number: 1, col_number: 2, errors: nil }}
680
+
681
+ Dreader::Util.slice i.table[0], :name
682
+ { name: { value: "John", row_number: 1, col_number: 1, errors: nil}
683
+
684
+ Dreader::Util.clean i.table[0], :name
685
+ { age: { value: 30, row_number: 1, col_number: 2, errors: nil }
686
+ #+END_EXAMPLE
687
+
688
+ The methods =slice= and =clean= are more useful when used in
689
+ conjuction with =simplify=:
690
+
691
+ #+BEGIN_EXAMPLE ruby
692
+ hash = Dreader::Util.simplify i.table[0]
693
+ { name: "John", age: 30 }
694
+
695
+ Dreader::Util.slice hash, [:age]
696
+ { age: 30 }
697
+
698
+ Dreader::Util.clean hash, [:age]
699
+ { name: "John" }
700
+ #+END_EXAMPLE
701
+
702
+ The output produced by =slice= and =simplify= is a hash which can be used to
703
+ create an =ActiveRecord= object.
704
+
705
+ ** Better Integration with ActiveRecord
706
+
707
+ Finally, the =Dreader::Util.restructure= method helps building hashes
708
+ to create [[http://api.rubyonrails.org/classes/ActiveModel/Model.html][ActiveModel]] objects with nested attributes:
709
+
710
+ #+BEGIN_EXAMPLE ruby
711
+ hash = {name: "John", surname: "Doe", address: "Unknown", city: "NY" }
712
+
713
+ Dreader::Util.restructure hash, [:name, :surname], :address_attributes, [:address, :city]
714
+ {name: "John", surname: "Doe", address_attributes: {address: "Unknonw", city: "NY"}}
715
+ #+END_EXAMPLE
716
+
717
+
718
+ * Debugging your specification
719
+
720
+ The =debug= function prints the current configuration, reads some
721
+ records from the input file(s), and shows the records read:
722
+
723
+ #+BEGIN_EXAMPLE ruby
724
+ i.debug
725
+ i.debug n: 40 # read 40 lines (from first_row)
726
+ i.debug n: 40, filename: filepath # like above, but read from filepath
727
+ #+END_EXAMPLE
728
+
729
+ By default =debug= invokes the =check_raw=, =process=, and =check=
730
+ directives. Pass the following options, if you want to disable this behavior;
731
+ this might be useful, for instance, if you intend to check only what data is
732
+ read:
733
+
734
+ #+BEGIN_EXAMPLE ruby
735
+ i.debug process: false, check: false
736
+ #+END_EXAMPLE
737
+
738
+ Notice that =check= implies =process=, since =check= is invoked on the
739
+ output of the =process= directive.`
740
+
741
+ If you prefer, in alternative to =debug= you can also use configuration
742
+ variables (but then you need to change the configuration according to the
743
+ environment):
744
+
745
+ #+begin_example ruby
746
+ i.options do
747
+ debug true
748
+ end
749
+ #+end_example
750
+
751
+
752
+ * Changelog
753
+
754
+ See [[file:CHANGELOG.ORG][CHANGELOG]].
755
+
756
+ * Known Limitations
757
+
758
+ At the moment:
759
+
760
+ - it is not possible to specify column references using header names
761
+ (like Roo does).
762
+ - it is not possible to pass options to the file readers. As a
763
+ consequence tab-separated files must have the =.tsv= extension or
764
+ they will not be parsed correctly
765
+ - some more testing wouldn't hurt.
766
+
767
+ * Known Bugs
768
+
769
+ Some known bugs and an unknown number of unknown bugs.
770
+
771
+ (See the open issues for the known bugs.)
772
+
773
+ * Development
774
+
775
+ After checking out the repo, run =bin/setup= to install dependencies.
776
+ You can also run =bin/console= for an interactive prompt that will
777
+ allow you to experiment.
778
+
779
+ To install this gem onto your local machine, run =bundle exec rake
780
+ install=. To release a new version, update the version number in
781
+ =version.rb=, and then run =bundle exec rake release=, which will
782
+ create a git tag for the version, push git commits and tags, and push
783
+ the =.gem= file to [[https://rubygems.org][rubygems.org]].
784
+
785
+ * Contributing
786
+
787
+ Bug reports and pull requests are welcome.
788
+
789
+ You need to get in touch with me by email, till I figure how to enable
790
+ it in Gitea.
791
+
792
+ * License
793
+
794
+ [[https://opensource.org/licenses/MIT][MIT License]].