dreader 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{CHANGELOG.ORG → CHANGELOG.org} +47 -0
- data/Gemfile.lock +3 -3
- data/README.org +99 -72
- data/examples/age/age.rb +30 -30
- data/examples/age_with_multiple_checks/age_with_multiple_checks.rb +5 -3
- data/examples/local_vars/local_vars.rb +28 -0
- data/examples/wikipedia_big_us_cities/big_us_cities.rb +6 -4
- data/examples/wikipedia_us_cities/us_cities.rb +5 -3
- data/examples/wikipedia_us_cities/us_cities_bulk_declare.rb +5 -3
- data/lib/dreader/engine.rb +155 -133
- data/lib/dreader/util.rb +25 -10
- data/lib/dreader/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40c16cce6fa4d2c813f381b7adefd8a6dd241db8b2133188aaf483c433e054ed
|
4
|
+
data.tar.gz: 4a35caf1ee4703628db442a9852090dbe87055301c0963ba00580bc7eb9f4e38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b17fc5631ac685e5117d6ef9c1199422c7ef9a347c8d05436b2fd28fbf421465a9035930c0f45adcc19bc94279bf7ff2ba8a4f7f6b932ef2befcc82743e03ee4
|
7
|
+
data.tar.gz: 9a124ddd059c973710800f2b058482b13d155852e0ebb1740e54e90d92100396f9b391a17806c9e1f561c527680d0ab50d59ef3b2162f1fa9ce5da350b29fe3a
|
@@ -1,5 +1,52 @@
|
|
1
1
|
#+TITLE: Changelog
|
2
2
|
|
3
|
+
|
4
|
+
* Version 1.1.0
|
5
|
+
** Fixes an issue with visibility of variables
|
6
|
+
|
7
|
+
Version 1.1.0 makes Engine a module and requires to use extend
|
8
|
+
|
9
|
+
This allows to isolate declarations in different variables.
|
10
|
+
|
11
|
+
|
12
|
+
** Renames process to mappings
|
13
|
+
|
14
|
+
** Renames the variables in a more consistent way
|
15
|
+
|
16
|
+
#+begin_example ruby
|
17
|
+
attr_accessor :declared_options
|
18
|
+
# the specification of the columns to process
|
19
|
+
attr_accessor :declared_columns
|
20
|
+
# some example lines
|
21
|
+
attr_accessor :declared_examples
|
22
|
+
# the specification of the virtual columns
|
23
|
+
attr_accessor :declared_virtual_columns
|
24
|
+
# the mapping rules
|
25
|
+
attr_accessor :declared_mapping
|
26
|
+
#+end_example
|
27
|
+
|
28
|
+
** Declares =data= as a synonym of =table=
|
29
|
+
** Adds options to do everything in one pass
|
30
|
+
|
31
|
+
By passing the options
|
32
|
+
|
33
|
+
- =virtual=
|
34
|
+
- =mapping=
|
35
|
+
|
36
|
+
to =read= you can read and process the data in one step.
|
37
|
+
|
38
|
+
See the README for more details.
|
39
|
+
|
40
|
+
** Revises the logging messages
|
41
|
+
** Refactor some code to make it more readable
|
42
|
+
** Refactors the restructure function to make it more flexible
|
43
|
+
|
44
|
+
Now refactor takes as input symbols and hashes and reshapes
|
45
|
+
according to the specification.
|
46
|
+
|
47
|
+
See the README for an example.
|
48
|
+
|
49
|
+
|
3
50
|
* Version 1.0.0
|
4
51
|
** Changes the DSL to allow declaration in a class
|
5
52
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dreader (
|
4
|
+
dreader (1.2.0)
|
5
5
|
fast_excel
|
6
6
|
roo
|
7
7
|
|
@@ -13,11 +13,11 @@ GEM
|
|
13
13
|
reline (>= 0.3.1)
|
14
14
|
fast_excel (0.4.1)
|
15
15
|
ffi (> 1.9, < 2)
|
16
|
-
ffi (1.
|
16
|
+
ffi (1.16.2)
|
17
17
|
io-console (0.6.0)
|
18
18
|
irb (1.7.2)
|
19
19
|
reline (>= 0.3.6)
|
20
|
-
nokogiri (1.15.
|
20
|
+
nokogiri (1.15.4-x86_64-linux)
|
21
21
|
racc (~> 1.4)
|
22
22
|
racc (1.7.1)
|
23
23
|
rake (10.5.0)
|
data/README.org
CHANGED
@@ -63,7 +63,9 @@ Print name and age of people from the following data:
|
|
63
63
|
#+BEGIN_EXAMPLE ruby
|
64
64
|
require 'dreader'
|
65
65
|
|
66
|
-
class Reader
|
66
|
+
class Reader
|
67
|
+
extend Dreader::Engine
|
68
|
+
|
67
69
|
options do
|
68
70
|
# we start reading from row 2
|
69
71
|
first_row 2
|
@@ -104,14 +106,14 @@ Print name and age of people from the following data:
|
|
104
106
|
end
|
105
107
|
end
|
106
108
|
|
107
|
-
reader = Reader
|
109
|
+
reader = Reader
|
108
110
|
|
109
111
|
# read the file
|
110
112
|
reader.read filename: "Birthdays.ods"
|
111
113
|
# compute the virtual columns
|
112
114
|
reader.virtual_columns
|
113
115
|
# run the mapping declaration
|
114
|
-
reader.
|
116
|
+
reader.mappings
|
115
117
|
|
116
118
|
#
|
117
119
|
# Here we can do further processing on the data
|
@@ -130,33 +132,36 @@ Print name and age of people from the following data:
|
|
130
132
|
To write an import function with Dreader:
|
131
133
|
|
132
134
|
- 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 first row) (This can also be specified in each call.)
|
136
|
+
- Declare the content of columns and, then, how to check raw data, parse data,
|
135
137
|
and check parsed data
|
136
138
|
- Add virtual columns, that is, columns computed from other values
|
137
139
|
in the row
|
138
|
-
- Specify how to
|
140
|
+
- Specify how to map line. This is where you do the actual work
|
139
141
|
(for instance, if you process a file line by line) or put together data for
|
140
142
|
processing after the file has been fully read --- see the next step.
|
141
143
|
|
142
|
-
Dreader
|
143
|
-
|
144
|
-
|
144
|
+
Dreader now knows ho to collect, shape, and tranform (map) data according to
|
145
|
+
your instructions. We are now ready to do the actual work. This consists of
|
146
|
+
the following steps, various of which can be performed together:
|
145
147
|
|
146
|
-
-
|
148
|
+
- Read the file
|
149
|
+
- Do the parsing/transformations
|
150
|
+
- Compute the virtual columns
|
151
|
+
- Do the mappings
|
147
152
|
|
148
153
|
Each step is described in more details in the following sections.
|
149
154
|
|
150
155
|
*** Declare which is the input file and where we can find data
|
151
156
|
|
152
|
-
Require =dreader= and declare a class which
|
153
|
-
|
157
|
+
Require =dreader= and declare a class which extends =Dreader::Engine=:
|
154
158
|
|
155
159
|
#+BEGIN_EXAMPLE ruby
|
156
160
|
require 'dreader'
|
157
161
|
|
158
|
-
class Reader
|
159
|
-
|
162
|
+
class Reader
|
163
|
+
extend Dreader::Engine
|
164
|
+
[...]
|
160
165
|
end
|
161
166
|
#+END_EXAMPLE
|
162
167
|
|
@@ -192,19 +197,20 @@ where:
|
|
192
197
|
contain "garbage" after the records.
|
193
198
|
- (optional) =sheet= is the sheet name or number to read from. If not
|
194
199
|
specified, the first (default) sheet is used
|
200
|
+
- (optional) =debug= specifies that we are debugging
|
201
|
+
- (optional) =logger= specifies the logger
|
202
|
+
- (optional) =logger_level= specifies the logger level
|
195
203
|
|
196
|
-
#+BEGIN_NOTES
|
197
204
|
You can override some of the defaults by passing a hash as argument to
|
198
205
|
the =read= function. For instance:
|
199
206
|
|
200
207
|
#+BEGIN_EXAMPLE ruby
|
201
|
-
|
208
|
+
Reader.read filename: another_filepath
|
202
209
|
#+END_EXAMPLE
|
203
210
|
|
204
211
|
will read data from =another_filepath=, rather than from the filename
|
205
212
|
specified in the options. This might be useful, for instance, if the
|
206
213
|
same specification has to be used for different files.
|
207
|
-
#+END_NOTES
|
208
214
|
|
209
215
|
|
210
216
|
*** Declare the content of columns and how to parse them
|
@@ -216,12 +222,12 @@ There are two notations:
|
|
216
222
|
|
217
223
|
#+BEGIN_EXAMPLE ruby
|
218
224
|
# First notation, colref is put in the block
|
219
|
-
|
225
|
+
column :name do
|
220
226
|
colref 'A'
|
221
227
|
end
|
222
228
|
|
223
229
|
# Second notation, a hash is passed in the name
|
224
|
-
|
230
|
+
column({ name: 'A' }) do
|
225
231
|
end
|
226
232
|
#+END_EXAMPLE
|
227
233
|
|
@@ -242,7 +248,7 @@ The =column= declaration can contain Ruby blocks:
|
|
242
248
|
=process= is valid. *Check must return true if there are no errors.*
|
243
249
|
|
244
250
|
#+begin_example
|
245
|
-
|
251
|
+
column({ name: 'A' }) do
|
246
252
|
check_raw do |cell|
|
247
253
|
!cell.nil?
|
248
254
|
end
|
@@ -256,7 +262,7 @@ The =column= declaration can contain Ruby blocks:
|
|
256
262
|
#+end_quote
|
257
263
|
|
258
264
|
#+begin_example
|
259
|
-
|
265
|
+
column({ name: 'A' }) do
|
260
266
|
check_raw :must_be_non_nil do |cell|
|
261
267
|
!cell.nil?
|
262
268
|
end
|
@@ -278,7 +284,7 @@ The =column= declaration can contain Ruby blocks:
|
|
278
284
|
#+end_quote
|
279
285
|
|
280
286
|
#+begin_example
|
281
|
-
|
287
|
+
column({ name: 'A' }) do
|
282
288
|
check_raw do |cell|
|
283
289
|
# Here cell is like in the input file
|
284
290
|
end
|
@@ -435,17 +441,20 @@ key.
|
|
435
441
|
*** Process data
|
436
442
|
|
437
443
|
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
|
439
|
-
|
444
|
+
to make elaborations on data which span different rows), you can add your perform
|
445
|
+
your elaborations on the data transformed by =mappings=.
|
440
446
|
|
441
447
|
A typical scenario works as follows:
|
442
448
|
|
443
|
-
1.
|
444
|
-
|
445
|
-
1. Use =i.read= or =i.load= (synonyms), to read all data.
|
449
|
+
1. Reference the class =i = Reader= and use =i.read= or =i.load=
|
450
|
+
(synonyms), to read all data.
|
446
451
|
|
447
452
|
#+BEGIN_EXAMPLE ruby
|
453
|
+
i = Reader
|
448
454
|
i.read
|
455
|
+
|
456
|
+
# alternatively
|
457
|
+
Reader.read
|
449
458
|
#+END_EXAMPLE
|
450
459
|
|
451
460
|
2. Use =errors= to see whether any of the check functions failed:
|
@@ -465,20 +474,38 @@ A typical scenario works as follows:
|
|
465
474
|
|
466
475
|
(Optionally: check again for errors.)
|
467
476
|
|
468
|
-
4. Use the =
|
469
|
-
|
477
|
+
4. Use the =mappings= function to execute the =mapping= directive on each line
|
478
|
+
read from the file.
|
470
479
|
|
471
480
|
#+BEGIN_EXAMPLE ruby
|
472
|
-
i.
|
481
|
+
i.mappings
|
473
482
|
#+END_EXAMPLE
|
474
483
|
|
475
484
|
(Optionally: check again for errors.)
|
476
485
|
|
477
|
-
5. Add your own code to process data
|
486
|
+
5. Add your own code to process the data returned after =mappings=, which you
|
487
|
+
can access with =i.table= or =i.data= (synonyms).
|
478
488
|
|
479
|
-
Look in the examples directory for further details and a couple of
|
480
|
-
|
489
|
+
Look in the examples directory for further details and a couple of working
|
490
|
+
examples.
|
481
491
|
|
492
|
+
*** Improving performances
|
493
|
+
|
494
|
+
While debugging your specification executing =read=, =virtual_columns=, and
|
495
|
+
=mappings= in distinct steps is a good idea. When you go in production, you
|
496
|
+
might want to reduce the number of passes you perform on the data.
|
497
|
+
|
498
|
+
You can pass the option =virtual: true= to =read= to compute virtual
|
499
|
+
columns while you are reading data.
|
500
|
+
|
501
|
+
You can pass the option =mapping: true= to =read= to compute virtual
|
502
|
+
columns and perform the mapping while you are reading data. Notice that:
|
503
|
+
|
504
|
+
- =mapping= implies =virtual=, that is, if you pass =mapping: true= the read
|
505
|
+
function will also compute virtual columns
|
506
|
+
- =mapping= alters the content of =@table= and **subsequent calls to
|
507
|
+
=virtual_column= and =mapping= will fail.** You have reset by invoking
|
508
|
+
=read= again.
|
482
509
|
|
483
510
|
*** Managing Errors
|
484
511
|
|
@@ -529,22 +556,23 @@ end
|
|
529
556
|
|
530
557
|
You can check for errors in two different ways:
|
531
558
|
|
532
|
-
The first is in the =mapping= directive, where can check whether some checks
|
533
|
-
the =row= failed, by:
|
559
|
+
The first is in the =mapping= directive, where can check whether some checks
|
560
|
+
for the =row= failed, by:
|
534
561
|
|
535
562
|
1. checking from the =:error= boolean key associated to each column, that is:
|
536
563
|
|
537
564
|
=row[<column_name>][:error]=
|
538
565
|
|
539
|
-
2. looking at the value of the =:row_errors= key, which contains all error
|
540
|
-
generated for the row:
|
566
|
+
2. looking at the value of the =:row_errors= key, which contains all error
|
567
|
+
messages generated for the row:
|
541
568
|
|
542
569
|
=row[:row_errors]=
|
543
570
|
|
544
|
-
3. After the processing, by using the method =errors=, which lists all the
|
571
|
+
3. After the processing, by using the method =errors=, which lists all the
|
572
|
+
errors.
|
545
573
|
|
546
|
-
The utility function =Dreader::Util.errors= takes as input the errors generated
|
547
|
-
Dreader and extract those of a specific row and, optionally column:
|
574
|
+
The utility function =Dreader::Util.errors= takes as input the errors generated
|
575
|
+
by Dreader and extract those of a specific row and, optionally column:
|
548
576
|
|
549
577
|
#+begin_example ruby
|
550
578
|
# get all the errors at line 2
|
@@ -644,39 +672,39 @@ Thus, for instance, given the example above returns:
|
|
644
672
|
|
645
673
|
* Simplifying the hash with the data read
|
646
674
|
|
647
|
-
The =Dreader::Util= class provides some functions to simplify the
|
648
|
-
|
649
|
-
|
650
|
-
ActiveRecord creators.
|
675
|
+
The =Dreader::Util= class provides some functions to simplify the hashes built
|
676
|
+
by =dreader=. This is useful to simplify the code you write and to genereate
|
677
|
+
hashes you can pass, for instance, to ActiveRecord creators.
|
651
678
|
|
652
679
|
** Simplify removes everything but the values
|
653
680
|
|
654
|
-
=Dreader::Util.simplify
|
655
|
-
|
681
|
+
=Dreader::Util.simplify(hash)= removes all information but the value and makes
|
682
|
+
the value accessible directly from the name of the column.
|
656
683
|
|
657
684
|
#+BEGIN_EXAMPLE ruby
|
658
685
|
i.table[0]
|
659
|
-
{
|
660
|
-
|
686
|
+
{
|
687
|
+
name: { value: "John", row_number: 1, col_number: 1, errors: nil },
|
688
|
+
age: { value: 30, row_number: 1, col_number: 2, errors: nil }
|
689
|
+
}
|
661
690
|
|
662
691
|
Dreader::Util.simplify i.table[0]
|
663
692
|
{ name: "John", age: 30 }
|
664
693
|
#+END_EXAMPLE
|
665
694
|
|
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
695
|
** Slice and Clean select columns
|
670
696
|
|
671
|
-
=Dreader::Util.slice
|
672
|
-
|
673
|
-
|
674
|
-
|
697
|
+
=Dreader::Util.slice(hash, keys)= and =Dreader::Util.clean(hash, keys)=, where
|
698
|
+
=keys= is an arrays of keys, are respectively used to select or remove some
|
699
|
+
keys from the hash returned by Dreader. (Notice that the Ruby Hash class
|
700
|
+
already provides similar methods.)
|
675
701
|
|
676
702
|
#+BEGIN_EXAMPLE ruby
|
677
703
|
i.table[0]
|
678
|
-
{
|
679
|
-
|
704
|
+
{
|
705
|
+
name: { value: "John", row_number: 1, col_number: 1, errors: nil },
|
706
|
+
age: { value: 30, row_number: 1, col_number: 2, errors: nil }
|
707
|
+
}
|
680
708
|
|
681
709
|
Dreader::Util.slice i.table[0], :name
|
682
710
|
{ name: { value: "John", row_number: 1, col_number: 1, errors: nil}
|
@@ -685,8 +713,8 @@ Ruby Hash class already provides similar methods.)
|
|
685
713
|
{ age: { value: 30, row_number: 1, col_number: 2, errors: nil }
|
686
714
|
#+END_EXAMPLE
|
687
715
|
|
688
|
-
The methods =slice= and =clean= are more useful when used in
|
689
|
-
|
716
|
+
The methods =slice= and =clean= are more useful when used in conjuction with
|
717
|
+
=simplify=:
|
690
718
|
|
691
719
|
#+BEGIN_EXAMPLE ruby
|
692
720
|
hash = Dreader::Util.simplify i.table[0]
|
@@ -704,21 +732,23 @@ create an =ActiveRecord= object.
|
|
704
732
|
|
705
733
|
** Better Integration with ActiveRecord
|
706
734
|
|
707
|
-
Finally, the =Dreader::Util.restructure= method helps building hashes
|
708
|
-
|
735
|
+
Finally, the =Dreader::Util.restructure= method helps building hashes to create
|
736
|
+
[[http://api.rubyonrails.org/classes/ActiveModel/Model.html][ActiveModel]] objects with nested attributes.
|
737
|
+
|
738
|
+
**The starting point is a simplified row.**
|
709
739
|
|
710
740
|
#+BEGIN_EXAMPLE ruby
|
711
|
-
hash = {name: "John", surname: "Doe", address: "Unknown", city: "NY"
|
741
|
+
hash = { name: "John", surname: "Doe", address: "Unknown", city: "NY" }
|
712
742
|
|
713
|
-
Dreader::Util.restructure hash, [:name, :surname
|
714
|
-
{name: "John", surname: "Doe", address_attributes: {address: "
|
743
|
+
Dreader::Util.restructure hash, [:name, :surname, :address_attributes, [:address, :city]]
|
744
|
+
{ name: "John", surname: "Doe", address_attributes: { address: "Unknown", city: "NY" } }
|
715
745
|
#+END_EXAMPLE
|
716
746
|
|
717
747
|
|
718
748
|
* Debugging your specification
|
719
749
|
|
720
|
-
The =debug= function prints the current configuration, reads some
|
721
|
-
|
750
|
+
The =debug= function prints the current configuration, reads some records from
|
751
|
+
the input file(s), and shows the records read:
|
722
752
|
|
723
753
|
#+BEGIN_EXAMPLE ruby
|
724
754
|
i.debug
|
@@ -735,8 +765,8 @@ read:
|
|
735
765
|
i.debug process: false, check: false
|
736
766
|
#+END_EXAMPLE
|
737
767
|
|
738
|
-
Notice that =check= implies =process=, since =check= is invoked on the
|
739
|
-
|
768
|
+
Notice that =check= implies =process=, since =check= is invoked on the output
|
769
|
+
of the =process= directive.`
|
740
770
|
|
741
771
|
If you prefer, in alternative to =debug= you can also use configuration
|
742
772
|
variables (but then you need to change the configuration according to the
|
@@ -751,7 +781,7 @@ environment):
|
|
751
781
|
|
752
782
|
* Changelog
|
753
783
|
|
754
|
-
See [[file:CHANGELOG.
|
784
|
+
See [[file:CHANGELOG.org][CHANGELOG]].
|
755
785
|
|
756
786
|
* Known Limitations
|
757
787
|
|
@@ -759,9 +789,6 @@ At the moment:
|
|
759
789
|
|
760
790
|
- it is not possible to specify column references using header names
|
761
791
|
(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
792
|
- some more testing wouldn't hurt.
|
766
793
|
|
767
794
|
* Known Bugs
|
data/examples/age/age.rb
CHANGED
@@ -1,47 +1,47 @@
|
|
1
|
-
require
|
1
|
+
require "dreader"
|
2
2
|
|
3
|
-
class Reader
|
3
|
+
class Reader
|
4
|
+
extend Dreader::Engine
|
4
5
|
|
5
|
-
options do
|
6
|
-
|
7
|
-
|
8
|
-
end
|
6
|
+
options do
|
7
|
+
first_row 2
|
8
|
+
debug true
|
9
|
+
end
|
9
10
|
|
10
|
-
column :name do
|
11
|
-
|
12
|
-
|
13
|
-
end
|
11
|
+
column :name do
|
12
|
+
doc "A is the name string"
|
13
|
+
colref 'A'
|
14
|
+
end
|
14
15
|
|
15
|
-
column :birthdate do
|
16
|
-
|
17
|
-
|
16
|
+
column :birthdate do
|
17
|
+
doc "Birthdate contains a full date (i.e., including the year)"
|
18
|
+
colref 'B'
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
process do |c|
|
21
|
+
Date.parse(c)
|
22
|
+
end
|
21
23
|
end
|
22
|
-
end
|
23
24
|
|
24
|
-
virtual_column :age do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
virtual_column :age do
|
26
|
+
process do |row|
|
27
|
+
birthdate = row[:birthdate][:value]
|
28
|
+
birthday = Date.new(Date.today.year, birthdate.month, birthdate.day)
|
29
|
+
today = Date.today
|
29
30
|
|
30
|
-
|
31
|
+
[0, today.year - birthdate.year - (birthday < today ? 1 : 0)].max
|
32
|
+
end
|
31
33
|
end
|
32
|
-
end
|
33
34
|
|
34
|
-
mapping do |row|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
35
|
+
mapping do |row|
|
36
|
+
r = Dreader::Util.simplify(row)
|
37
|
+
puts "#{r[:name]} is #{r[:age]} years old (born on #{r[:birthdate]})"
|
38
|
+
end
|
38
39
|
end
|
39
40
|
|
40
|
-
i = Reader
|
41
|
-
|
41
|
+
i = Reader
|
42
42
|
i.read filename: "Birthdays.ods"
|
43
43
|
i.virtual_columns
|
44
|
-
i.
|
44
|
+
i.mappings
|
45
45
|
|
46
46
|
#
|
47
47
|
# Here we can do further processing on the data
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'dreader'
|
2
2
|
|
3
|
-
class Reader
|
3
|
+
class Reader
|
4
|
+
extend Dreader::Engine
|
5
|
+
|
4
6
|
options { first_row 2; debug true }
|
5
7
|
|
6
8
|
#
|
@@ -54,9 +56,9 @@ class Reader < Dreader::Engine
|
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
|
-
i = Reader
|
59
|
+
i = Reader
|
58
60
|
|
59
61
|
i.read filename: "Birthdays.ods"
|
60
62
|
i.virtual_columns
|
61
|
-
i.
|
63
|
+
i.mappings
|
62
64
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# This demonstrates that variables are local
|
3
|
+
#
|
4
|
+
|
5
|
+
require "dreader"
|
6
|
+
|
7
|
+
class OneReader
|
8
|
+
extend Dreader::Engine
|
9
|
+
|
10
|
+
options do
|
11
|
+
first_row 2
|
12
|
+
debug true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class AnotherReader
|
17
|
+
extend Dreader::Engine
|
18
|
+
|
19
|
+
options do
|
20
|
+
filename "filename"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
r1 = OneReader
|
25
|
+
r2 = AnotherReader
|
26
|
+
|
27
|
+
puts r1.declared_options
|
28
|
+
puts r2.declared_options
|
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'dreader'
|
4
4
|
|
5
|
-
class Processor
|
5
|
+
class Processor
|
6
|
+
extend Dreader::Engine
|
7
|
+
|
6
8
|
options do
|
7
9
|
first_row 2
|
8
10
|
filename "cities_by_state.ods"
|
@@ -25,7 +27,7 @@ class Processor < Dreader::Engine
|
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
|
-
processor = Processor
|
30
|
+
processor = Processor
|
29
31
|
|
30
32
|
printf "Loading the spreadsheet..."
|
31
33
|
processor.load
|
@@ -43,8 +45,8 @@ else
|
|
43
45
|
end
|
44
46
|
puts "done!"
|
45
47
|
|
46
|
-
puts "
|
47
|
-
processor.
|
48
|
+
puts "Applying mapping rules to the spreadsheet..."
|
49
|
+
processor.mappings
|
48
50
|
puts "... done"
|
49
51
|
|
50
52
|
|
@@ -13,7 +13,9 @@ class City
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
class Importer
|
16
|
+
class Importer
|
17
|
+
extend Dreader::Engine
|
18
|
+
|
17
19
|
# read from us_cities.tsv, lines from 2 to 10 (included)
|
18
20
|
options do
|
19
21
|
filename "us_cities.tsv"
|
@@ -50,7 +52,7 @@ class Importer < Dreader::Engine
|
|
50
52
|
end
|
51
53
|
|
52
54
|
cities = []
|
53
|
-
importer = Importer
|
55
|
+
importer = Importer
|
54
56
|
|
55
57
|
importer.mapping do |row|
|
56
58
|
# remove all additional information stored in each cell
|
@@ -81,7 +83,7 @@ importer.debug process: false, check: false
|
|
81
83
|
# load and process
|
82
84
|
importer.load
|
83
85
|
cities = []
|
84
|
-
importer.
|
86
|
+
importer.mappings
|
85
87
|
|
86
88
|
# output everything to see whether it works
|
87
89
|
puts "First ten cities in the US (source Wikipedia)"
|
@@ -13,7 +13,9 @@ class City
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
class Importer
|
16
|
+
class Importer
|
17
|
+
extend Dreader::Engine
|
18
|
+
|
17
19
|
# read from us_cities.tsv, lines from 2 to 10 (included)
|
18
20
|
options do
|
19
21
|
filename "us_cities.tsv"
|
@@ -42,7 +44,7 @@ class Importer < Dreader::Engine
|
|
42
44
|
end
|
43
45
|
|
44
46
|
cities = []
|
45
|
-
importer = Importer
|
47
|
+
importer = Importer
|
46
48
|
|
47
49
|
importer.mapping do |row|
|
48
50
|
# remove all additional information stored in each cell
|
@@ -73,7 +75,7 @@ importer.debug process: false, check: false
|
|
73
75
|
# load and process
|
74
76
|
importer.load
|
75
77
|
cities = []
|
76
|
-
importer.
|
78
|
+
importer.mappings
|
77
79
|
|
78
80
|
# output everything to see whether it works
|
79
81
|
puts "First ten cities in the US (source Wikipedia)"
|
data/lib/dreader/engine.rb
CHANGED
@@ -10,47 +10,29 @@ module Dreader
|
|
10
10
|
#
|
11
11
|
# This is where the real stuff begins
|
12
12
|
#
|
13
|
-
|
14
|
-
# TODO: make the writer into private methods (need to be accessed only
|
15
|
-
# in the initializer) and demote to attr_reader
|
16
|
-
|
13
|
+
module Engine
|
17
14
|
# the options we passed
|
18
|
-
attr_accessor :
|
15
|
+
attr_accessor :declared_options
|
19
16
|
# the specification of the columns to process
|
20
|
-
attr_accessor :
|
17
|
+
attr_accessor :declared_columns
|
21
18
|
# some example lines
|
22
|
-
attr_accessor :
|
19
|
+
attr_accessor :declared_examples
|
23
20
|
# the specification of the virtual columns
|
24
|
-
attr_accessor :
|
21
|
+
attr_accessor :declared_virtual_columns
|
25
22
|
# the mapping rules
|
26
|
-
attr_accessor :
|
23
|
+
attr_accessor :declared_mapping
|
27
24
|
|
28
25
|
# the data we read
|
29
26
|
attr_reader :table
|
30
27
|
|
31
|
-
# variables declared in the class which need to be propagated in
|
32
|
-
# the instance
|
33
|
-
INSTANTIATE = %i[options colspec examples virtualcols]
|
34
|
-
|
35
|
-
def initialize
|
36
|
-
@logger = Logger.new($stdout)
|
37
|
-
@logger.level = Logger::WARN
|
38
|
-
|
39
|
-
# populate the instance with the variables defined in the class
|
40
|
-
@options = defined?(@@options) ? @@options : {}
|
41
|
-
@colspec = defined?(@@colspec) ? @@colspec : []
|
42
|
-
@examples = defined?(@@examples) ? @@examples : []
|
43
|
-
@virtualcols = defined?(@@virtualcols) ? @@virtualcols : []
|
44
|
-
end
|
45
|
-
|
46
28
|
# define a DSL for options
|
47
29
|
# any string is processed as an option and it ends up in the
|
48
30
|
# @options hash
|
49
|
-
def
|
31
|
+
def options(&block)
|
50
32
|
options = Options.new
|
51
33
|
options.instance_eval(&block)
|
52
34
|
|
53
|
-
|
35
|
+
@declared_options = options.to_hash
|
54
36
|
end
|
55
37
|
|
56
38
|
# define a DSL for column specification
|
@@ -58,18 +40,18 @@ module Dreader
|
|
58
40
|
# - `block` contains two declarations, `process` and `check`, which are
|
59
41
|
# used, respectively, to make a cell into the desired data and to check
|
60
42
|
# whether the desired data is ok
|
61
|
-
def
|
43
|
+
def column(name, &block)
|
62
44
|
column = Column.new
|
63
45
|
column.instance_eval(&block)
|
64
46
|
|
65
|
-
|
47
|
+
@declared_columns ||= []
|
66
48
|
|
67
49
|
if name.instance_of?(Hash)
|
68
|
-
|
50
|
+
@declared_columns << column.to_hash.merge(
|
69
51
|
{ name: name.keys.first, colref: name.values.first }
|
70
52
|
)
|
71
53
|
else
|
72
|
-
|
54
|
+
@declared_columns << column.to_hash.merge({ name: name })
|
73
55
|
end
|
74
56
|
end
|
75
57
|
|
@@ -106,20 +88,20 @@ module Dreader
|
|
106
88
|
# cell.strip
|
107
89
|
# end
|
108
90
|
# end
|
109
|
-
def
|
91
|
+
def columns(hash, &block)
|
110
92
|
hash.each_key do |key|
|
111
93
|
column = Column.new
|
112
94
|
column.colref hash[key]
|
113
95
|
column.instance_eval(&block) if block
|
114
96
|
|
115
|
-
|
116
|
-
|
97
|
+
@declared_columns ||= []
|
98
|
+
@declared_columns << column.to_hash.merge({ name: key })
|
117
99
|
end
|
118
100
|
end
|
119
101
|
|
120
|
-
def
|
121
|
-
|
122
|
-
|
102
|
+
def example(hash)
|
103
|
+
@declared_examples ||= []
|
104
|
+
@declared_examples << hash
|
123
105
|
end
|
124
106
|
|
125
107
|
# virtual columns define derived attributes
|
@@ -128,12 +110,12 @@ module Dreader
|
|
128
110
|
#
|
129
111
|
# virtual colum declarations are executed in the order in which
|
130
112
|
# they are defined
|
131
|
-
def
|
113
|
+
def virtual_column(name, &block)
|
132
114
|
column = Column.new
|
133
115
|
column.instance_eval &block
|
134
116
|
|
135
|
-
|
136
|
-
|
117
|
+
@declared_virtual_columns ||= []
|
118
|
+
@declared_virtual_columns << column.to_hash.merge({ name: name })
|
137
119
|
end
|
138
120
|
|
139
121
|
# define what we do with each line we read
|
@@ -141,8 +123,8 @@ module Dreader
|
|
141
123
|
# `row` is a hash in which each spreadsheet cell is accessible under
|
142
124
|
# the column names. Each cell has the following values:
|
143
125
|
# :value, :error, :row_number, :col_number
|
144
|
-
def
|
145
|
-
|
126
|
+
def mapping(&block)
|
127
|
+
@declared_mapping = block
|
146
128
|
end
|
147
129
|
|
148
130
|
# read a file and store it internally
|
@@ -155,18 +137,20 @@ module Dreader
|
|
155
137
|
# @return the data read from filename, in the form of an array of
|
156
138
|
# hashes
|
157
139
|
def read(args = {})
|
140
|
+
# args override values in options (if defined)
|
141
|
+
# the initializer guarantees @options is at least {}
|
142
|
+
options = (@declared_options || {}).merge(args)
|
143
|
+
|
144
|
+
@logger = options[:logger] || Logger.new($stdout)
|
145
|
+
@logger.level = options[:logger_level] || Logger::WARN
|
146
|
+
@debug = options[:debug] == true
|
147
|
+
|
158
148
|
if !args.instance_of?(Hash)
|
159
149
|
@logger.error "#{__callee__}: this function takes a Hash as input"
|
160
150
|
raise Exception
|
161
151
|
end
|
162
152
|
|
163
|
-
|
164
|
-
|
165
|
-
@logger = options[:logger] if options[:logger]
|
166
|
-
@logger.level = options[:logger_level] if options[:logger_level]
|
167
|
-
@debug = options[:debug] == true
|
168
|
-
|
169
|
-
spreadsheet = Dreader::Engine.open_spreadsheet (options[:filename])
|
153
|
+
spreadsheet = open_spreadsheet (options[:filename])
|
170
154
|
sheet = spreadsheet.sheet(options[:sheet] || 0)
|
171
155
|
first_row = options[:first_row] || 1
|
172
156
|
last_row = options[:last_row] || sheet.last_row
|
@@ -176,64 +160,74 @@ module Dreader
|
|
176
160
|
@logger.level = Logger::DEBUG
|
177
161
|
|
178
162
|
# override the number of lines read
|
179
|
-
options[:n]
|
180
|
-
last_row =
|
181
|
-
|
163
|
+
n_lines = options[:n] ? options[:n].to_i : 10
|
164
|
+
last_row = first_row + n_lines - 1
|
165
|
+
|
182
166
|
# apply some defaults for debugging, if not defined in the options
|
183
167
|
[:check_raw, :process, :check].map do |key|
|
184
168
|
options[key] = true unless options.key?(key)
|
185
169
|
end
|
186
170
|
end
|
187
171
|
|
188
|
-
{ current: @
|
189
|
-
@logger.debug "#{k.capitalize} configuration:"
|
172
|
+
{ current: @declared_options, debug: options }.each do |k, v|
|
173
|
+
@logger.debug "[dreader] #{k.capitalize} configuration:"
|
190
174
|
v.each do |key, value|
|
191
175
|
@logger.debug " #{key}: #{value}"
|
192
176
|
end
|
193
177
|
end
|
194
178
|
|
195
|
-
@table = []
|
196
179
|
@errors = []
|
197
180
|
|
198
|
-
(first_row..last_row).
|
181
|
+
@table = (first_row..last_row).map do |row_number|
|
199
182
|
r = { row_number: row_number, row_errors: [] }
|
200
183
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
184
|
+
# this has side-effects on r
|
185
|
+
columns_on(r, row_number, sheet)
|
186
|
+
|
187
|
+
# this has side-effects on r
|
188
|
+
virtual_columns_on(r) if options[:virtual] || options[:mapping]
|
189
|
+
|
190
|
+
options[:mapping] ? mappings_on(r) : r
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# TODO: PASS A ROW (and not row_number and sheet)
|
195
|
+
def columns_on(r, row_number, sheet)
|
196
|
+
@declared_columns.each_with_index do |colspec, index|
|
197
|
+
colname = colspec[:name]
|
198
|
+
colref = colspec[:colref]
|
199
|
+
cell = sheet.cell(row_number, colref)
|
200
|
+
|
201
|
+
r[colname] = {
|
202
|
+
row: row_number,
|
203
|
+
col: colspec[:colref],
|
204
|
+
value: cell,
|
205
|
+
error: false
|
206
|
+
}
|
207
|
+
|
208
|
+
# Repeated below
|
209
|
+
# @logger.debug "[dreader] Processing #{coord(row_number, colref)}"
|
227
210
|
|
228
|
-
|
229
|
-
|
230
|
-
|
211
|
+
# check raw data
|
212
|
+
check_data(colspec[:checks_raw], r, colname)
|
213
|
+
|
214
|
+
# process data
|
215
|
+
coord = coord(row_number, colspec[:colref], cell)
|
216
|
+
begin
|
217
|
+
processed = colspec[:process] ? colspec[:process].call(cell) : cell
|
218
|
+
@logger.debug "[dreader] #{colname} process #{coord} yields '#{processed}' (#{processed.class})"
|
219
|
+
r[colname][:value] = processed
|
220
|
+
rescue => e
|
221
|
+
@logger.error "[dreader] #{colname} process #{coord} raises an exception"
|
222
|
+
raise e
|
231
223
|
end
|
232
224
|
|
233
|
-
|
225
|
+
# check data after process - notice that now r contains the value
|
226
|
+
# processed by process
|
227
|
+
check_data(colspec[:checks], r, colname)
|
234
228
|
end
|
235
229
|
|
236
|
-
|
230
|
+
r
|
237
231
|
end
|
238
232
|
|
239
233
|
alias load read
|
@@ -254,51 +248,65 @@ module Dreader
|
|
254
248
|
# You need to invoke read first
|
255
249
|
def get_row(row_number)
|
256
250
|
if row_number > @table.size
|
257
|
-
@logger.error "
|
251
|
+
@logger.error "[dreader] 'row_number' is out of range (did you invoke read?)"
|
258
252
|
exit
|
259
253
|
elsif row_number <= 0
|
260
|
-
@logger.error "
|
254
|
+
@logger.error "[dreader] 'row_number' is zero or negative (first row is 1)."
|
261
255
|
else
|
262
256
|
@table[row_number - 1]
|
263
257
|
end
|
264
258
|
end
|
265
259
|
|
266
|
-
# return an array of hashes with all the errors we have
|
260
|
+
# return an array of hashes with all the errors we have encountered
|
267
261
|
# an empty array is a good news
|
268
262
|
attr_reader :errors
|
269
263
|
|
270
|
-
def virtual_columns
|
264
|
+
def virtual_columns
|
271
265
|
# execute the virtual column specification
|
272
|
-
@table.each
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
r[colname][:value] = virtualcol[:process].call(r)
|
283
|
-
end
|
284
|
-
rescue => e
|
285
|
-
row = r[:row_number]
|
286
|
-
@logger.error "#{__callee__}: process for virtual column :#{colname} raised an exception at row #{row}"
|
287
|
-
raise e
|
288
|
-
end
|
266
|
+
@table.each { |row| virtual_columns_on(row) }
|
267
|
+
end
|
268
|
+
|
269
|
+
# Compute virtual columns for, with side effect on row
|
270
|
+
def virtual_columns_on(row)
|
271
|
+
@declared_virtual_columns.each do |virtualcol|
|
272
|
+
colname = virtualcol[:name]
|
273
|
+
row[colname] = { virtual: true }
|
274
|
+
|
275
|
+
check_data(virtualcol[:checks_raw], row, colname, full_row: true)
|
289
276
|
|
290
|
-
|
291
|
-
|
277
|
+
begin
|
278
|
+
# add the cell to the table
|
279
|
+
if virtualcol[:process]
|
280
|
+
row[colname][:value] = virtualcol[:process].call(row)
|
281
|
+
end
|
282
|
+
rescue => e
|
283
|
+
r = row[:row_number]
|
284
|
+
@logger.error "[dreader] #{colname} process raises an exception at row #{r}"
|
285
|
+
raise e
|
292
286
|
end
|
287
|
+
|
288
|
+
# check data after process -- we also have the processed value of
|
289
|
+
# the virtual column
|
290
|
+
check_data(virtualcol[:checks], row, colname, full_row: true)
|
293
291
|
end
|
294
292
|
end
|
295
293
|
|
296
|
-
# apply the mapping code to the array
|
297
|
-
#
|
294
|
+
# apply the mapping code to the array it makes sense to invoke it only
|
295
|
+
# once.
|
298
296
|
#
|
299
|
-
# the mapping is applied only if it defined
|
300
|
-
|
301
|
-
|
297
|
+
# the mapping is applied only if it defined and it uses map, so that
|
298
|
+
# it can be used functionally
|
299
|
+
def mappings
|
300
|
+
@table.map { |row| mappings_on(row) }
|
301
|
+
end
|
302
|
+
|
303
|
+
def mappings_on(row)
|
304
|
+
@declared_mapping&.call(row)
|
305
|
+
end
|
306
|
+
|
307
|
+
# an alias
|
308
|
+
def data
|
309
|
+
@table
|
302
310
|
end
|
303
311
|
|
304
312
|
def to_s
|
@@ -314,14 +322,14 @@ module Dreader
|
|
314
322
|
end
|
315
323
|
|
316
324
|
def compare_headers(hash = {})
|
317
|
-
options = @
|
325
|
+
options = @declared_options.merge(hash)
|
318
326
|
|
319
|
-
spreadsheet =
|
327
|
+
spreadsheet = open_spreadsheet(options[:filename])
|
320
328
|
sheet = spreadsheet.sheet(options[:sheet] || 0)
|
321
329
|
header_row_number = options[:first_row] - 1 || 1
|
322
330
|
|
323
331
|
output_hash = {}
|
324
|
-
@
|
332
|
+
@declared_columns.map do |colspec|
|
325
333
|
cell = sheet.cell(row_number, colspec[:colref])
|
326
334
|
human_readable = colspec[:name].to_s.split("_").map(&:capitalize).join(" ")
|
327
335
|
|
@@ -341,7 +349,7 @@ module Dreader
|
|
341
349
|
# second row includes the documentation string, to document values in
|
342
350
|
# the columns
|
343
351
|
def template(hash = {})
|
344
|
-
options = @
|
352
|
+
options = @declared_options.merge(hash)
|
345
353
|
filename = options[:template_filename]
|
346
354
|
|
347
355
|
workbook = FastExcel.open(filename, constant_memory: true)
|
@@ -357,22 +365,22 @@ module Dreader
|
|
357
365
|
#
|
358
366
|
|
359
367
|
# here we write the first row
|
360
|
-
@
|
368
|
+
@declared_columns.each do |colspec|
|
361
369
|
human_readable = colspec[:name].to_s.split("_").map(&:capitalize).join(" ")
|
362
370
|
colref = colref_to_i(colspec[:colref])
|
363
371
|
worksheet.write_string(first_row, colref, human_readable, bold)
|
364
372
|
end
|
365
373
|
|
366
374
|
# here we create a note with the legenda
|
367
|
-
@
|
375
|
+
@declared_columns.each do |colspec|
|
368
376
|
colref = colref_to_i(colspec[:colref])
|
369
377
|
worksheet.write_string(first_row + 1, colref, colspec[:doc], nil)
|
370
378
|
end
|
371
379
|
|
372
380
|
# here we write some example records
|
373
|
-
@
|
381
|
+
@declared_examples.each_with_index do |example_hash, index|
|
374
382
|
example_hash.each do |colname, value|
|
375
|
-
colspec = @
|
383
|
+
colspec = @declared_columns.select { |x| x[:name] == colname }.first
|
376
384
|
if colspec
|
377
385
|
colref = colref_to_i(colspec[:colref])
|
378
386
|
worksheet.write_string(index + 3, colref, value, nil)
|
@@ -387,16 +395,30 @@ module Dreader
|
|
387
395
|
|
388
396
|
private
|
389
397
|
|
390
|
-
|
398
|
+
# list of keys we support in options. We remove them when reading
|
399
|
+
# the CSV file
|
400
|
+
OPTION_KEYS = %i[
|
401
|
+
filename sheet first_row last_row logger logger_level
|
402
|
+
]
|
403
|
+
|
404
|
+
def open_spreadsheet(filename)
|
391
405
|
ext = File.extname(filename)
|
392
406
|
|
393
407
|
case ext
|
394
|
-
when ".csv"
|
395
|
-
|
396
|
-
|
397
|
-
when ".
|
398
|
-
|
399
|
-
|
408
|
+
when ".csv"
|
409
|
+
csv_options = @declared_options.except(*OPTION_KEYS)
|
410
|
+
Roo::CSV.new(filename, csv_options:)
|
411
|
+
when ".tsv"
|
412
|
+
csv_options = @declared_options.except(*OPTION_KEYS).merge({ col_sep: "\t" })
|
413
|
+
Roo::CSV.new(filename, csv_options:)
|
414
|
+
when ".ods"
|
415
|
+
Roo::OpenOffice.new(filename)
|
416
|
+
when ".xls"
|
417
|
+
Roo::Excel.new(filename)
|
418
|
+
when ".xlsx"
|
419
|
+
Roo::Excelx.new(filename)
|
420
|
+
else
|
421
|
+
raise "Unknown extension: #{ext}"
|
400
422
|
end
|
401
423
|
end
|
402
424
|
|
@@ -445,7 +467,7 @@ module Dreader
|
|
445
467
|
|
446
468
|
begin
|
447
469
|
pass = check_function.call(value)
|
448
|
-
@logger.debug "check
|
470
|
+
@logger.debug "[dreader] check #{colname}/#{error_message} at #{coord} yields: '#{pass}'"
|
449
471
|
|
450
472
|
if pass != true
|
451
473
|
hash[colname][:error] = true
|
@@ -460,14 +482,14 @@ module Dreader
|
|
460
482
|
hash[:row_errors] << error
|
461
483
|
end
|
462
484
|
rescue => e
|
463
|
-
@logger.error "
|
485
|
+
@logger.error "[dreader] check #{colname}/#{error_message} raises an exception at #{coord}"
|
464
486
|
raise e
|
465
487
|
end
|
466
488
|
end
|
467
489
|
end
|
468
490
|
|
469
|
-
def coord(row, col,
|
470
|
-
"
|
491
|
+
def coord(row, col, value = nil)
|
492
|
+
["#{row}#{col}", (value ? "(#{value})" : nil)].join(" ")
|
471
493
|
end
|
472
494
|
end
|
473
495
|
end
|
data/lib/dreader/util.rb
CHANGED
@@ -19,20 +19,35 @@ module Dreader
|
|
19
19
|
end.to_h
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
22
|
+
# Given a "simplified" hash restructure it according to the second
|
23
|
+
# argument.
|
24
|
+
#
|
25
|
+
# Use it for generating hashes with nested attributes, which
|
26
|
+
# follows Rails conventions.
|
27
|
+
#
|
28
|
+
# @params:
|
29
|
+
# - hash the hash to restructure
|
30
|
+
# - args splat arguments which specify how to (re)structure the
|
31
|
+
# values in Hash. Each element is either a symbol or a Hash
|
25
32
|
#
|
26
33
|
# Example
|
27
34
|
#
|
28
|
-
# hash = { name: "A", surname: "B", address: "via
|
29
|
-
#
|
30
|
-
#
|
35
|
+
# hash = { name: "A", surname: "B", address: "via Piave", city: "Genoa" }
|
36
|
+
#
|
37
|
+
# restructure(hash, :name, :surname)
|
38
|
+
# { name: "A", surname: "B" }
|
31
39
|
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
40
|
+
# restructure(hash, :name, address_attributes: [:address, :city])
|
41
|
+
# {name: "A", address_attributes: { address: "via Piave", city: "Genoa" }
|
42
|
+
#
|
43
|
+
def self.restructure(hash, *new_structure)
|
44
|
+
new_structure.to_h do |value|
|
45
|
+
if value.instance_of?(Hash)
|
46
|
+
[value.keys.first, self.restructure(hash, *value.values.first)]
|
47
|
+
else
|
48
|
+
[value, hash[value]]
|
49
|
+
end
|
50
|
+
end
|
36
51
|
end
|
37
52
|
|
38
53
|
# an alias for Hash.slice
|
data/lib/dreader/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dreader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adolfo Villafiorita
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: roo
|
@@ -97,7 +97,7 @@ extensions: []
|
|
97
97
|
extra_rdoc_files: []
|
98
98
|
files:
|
99
99
|
- ".gitignore"
|
100
|
-
- CHANGELOG.
|
100
|
+
- CHANGELOG.org
|
101
101
|
- Gemfile
|
102
102
|
- Gemfile.lock
|
103
103
|
- LICENSE.txt
|
@@ -110,6 +110,7 @@ files:
|
|
110
110
|
- examples/age/age.rb
|
111
111
|
- examples/age_with_multiple_checks/Birthdays.ods
|
112
112
|
- examples/age_with_multiple_checks/age_with_multiple_checks.rb
|
113
|
+
- examples/local_vars/local_vars.rb
|
113
114
|
- examples/template/template_generation.rb
|
114
115
|
- examples/wikipedia_big_us_cities/big_us_cities.rb
|
115
116
|
- examples/wikipedia_big_us_cities/cities_by_state.ods
|