fat_table 0.3.3 → 0.5.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 +4 -4
- data/.rspec +2 -1
- data/.rubocop.yml +3 -5
- data/README.org +1334 -457
- data/README.rdoc +1 -2
- data/TODO.org +17 -10
- data/examples/create_trans.sql +14 -0
- data/examples/quick.pdf +0 -0
- data/examples/quick.png +0 -0
- data/examples/quick.ppm +0 -0
- data/examples/quick.tex +8 -0
- data/examples/quick_small.png +0 -0
- data/examples/quicktable.tex +123 -0
- data/examples/trades.db +0 -0
- data/examples/trans.csv +13 -0
- data/fat_table.gemspec +2 -2
- data/lib/ext/array.rb +15 -0
- data/lib/fat_table/column.rb +71 -208
- data/lib/fat_table/convert.rb +173 -0
- data/lib/fat_table/evaluator.rb +7 -0
- data/lib/fat_table/footer.rb +228 -0
- data/lib/fat_table/formatters/formatter.rb +200 -163
- data/lib/fat_table/formatters/latex_formatter.rb +9 -7
- data/lib/fat_table/table.rb +229 -57
- data/lib/fat_table/version.rb +1 -1
- data/lib/fat_table.rb +5 -2
- data/md/README.md +1 -2
- metadata +30 -18
data/README.rdoc
CHANGED
@@ -888,8 +888,7 @@ will raise an exception.
|
|
888
888
|
|
889
889
|
+last+:: the last non-nil item in the column,
|
890
890
|
|
891
|
-
+
|
892
|
-
values in the column,
|
891
|
+
+range+- :: form a Range ~~{min}..{max}~ to show the range of values in the column,
|
893
892
|
|
894
893
|
+sum+:: for Numeric and String columns, apply '+' to all the non-nil values,
|
895
894
|
|
data/TODO.org
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
* TODO Conversion to Spreadsheets
|
2
|
+
- State "TODO" from [2017-04-21 Fri 10:36]
|
3
|
+
This is a [[https://github.com/westonganger/spreadsheet_architect][gem]] that I can include into the Table model to convert a table into
|
4
|
+
a spread-sheet, or even a sheet in a multi-sheet spreadsheet file.
|
5
|
+
|
6
|
+
* TODO Add from_yql for fetching from Yahoo
|
7
|
+
- State "TODO" from [2017-04-21 Fri 10:35]
|
8
|
+
Add a constructor to allow fetching stock data from yql. Perhaps grab all
|
9
|
+
available fields, then allow a select of those of interest.
|
10
|
+
|
11
|
+
* DONE Allow sorting by expression
|
12
|
+
CLOSED: [2022-01-20 Thu 12:47]
|
13
|
+
Either by a single string argument as the sole argument to order_by, or use
|
14
|
+
another method, such a order_with. Note that this can be done now by creating
|
15
|
+
a new column having the sort expression with select and then just order_by
|
16
|
+
that column. Perhaps that is an easy way to implement it.
|
17
|
+
|
1
18
|
* DONE Ensure that columns resulting from aggregates have proper type
|
2
19
|
CLOSED: [2017-12-29 Fri 05:34]
|
3
20
|
- State "WAIT" from "TODO" [2017-12-29 Fri 05:34]
|
@@ -5,11 +22,6 @@ CLOSED: [2017-12-29 Fri 05:34]
|
|
5
22
|
After applying avg, does the column have the proper Numeric or Date, or DateTime
|
6
23
|
type. How about Boolean aggregates?
|
7
24
|
|
8
|
-
* TODO Conversion to Spreadsheets
|
9
|
-
- State "TODO" from [2017-04-21 Fri 10:36]
|
10
|
-
This is a [[https://github.com/westonganger/spreadsheet_architect][gem]] that I can include into the Table model to convert a table into
|
11
|
-
a spread-sheet, or even a sheet in a multi-sheet spreadsheet file.
|
12
|
-
|
13
25
|
* DONE Formatters
|
14
26
|
CLOSED: [2017-04-21 Fri 10:36]
|
15
27
|
- State "WAIT" from "TODO" [2017-04-21 Fri 10:36]
|
@@ -31,8 +43,3 @@ CLOSED: [2017-03-02 Thu 15:54]
|
|
31
43
|
- State "TODO" from [2017-03-02 Thu 15:54]
|
32
44
|
For tables, add a method that eliminates any duplicate rows. Perhaps just apply
|
33
45
|
Array#uniq to the columns?
|
34
|
-
|
35
|
-
* TODO Add from_yql for fetching from Yahoo
|
36
|
-
- State "TODO" from [2017-04-21 Fri 10:35]
|
37
|
-
Add a constructor to allow fetching stock data from yql. Perhaps grab all
|
38
|
-
available fields, then allow a select of those of interest.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
drop table trans;
|
2
|
+
create table trans(date text, code text, raw, shares, price, info text, ok text);
|
3
|
+
insert into trans values('2013-05-29', 'S', 15700.00, 6601.85, 24.7790, 'ENTITY3', 'F');
|
4
|
+
insert into trans values('2013-05-02', 'P', 118186.40, 118186.4, 11.8500, 'ENTITY1', 'T');
|
5
|
+
insert into trans values('2013-05-20', 'S', 12000.00, 5046.00, 28.2804, 'ENTITY3', 'F');
|
6
|
+
insert into trans values('2013-05-23', 'S', 8000.00, 3364.00, 27.1083, 'ENTITY3', 'T');
|
7
|
+
insert into trans values('2013-05-23', 'S', 39906.00, 16780.47, 25.1749, 'ENTITY3', 'T');
|
8
|
+
insert into trans values('2013-05-20', 'S', 85000.00, 35742.50, 28.3224, 'ENTITY3', 'T');
|
9
|
+
insert into trans values('2013-05-02', 'P', 795546.20, 795546.2, 1.1850, 'ENTITY1', 'T');
|
10
|
+
insert into trans values('2013-05-29', 'S', 13459.00, 5659.51, 24.7464, 'ENTITY3', 'T');
|
11
|
+
insert into trans values('2013-05-20', 'S', 33302.00, 14003.49, 28.6383, 'ENTITY3', 'T');
|
12
|
+
insert into trans values('2013-05-29', 'S', 15900.00, 6685.95, 24.5802, 'ENTITY3', 'T');
|
13
|
+
insert into trans values('2013-05-30', 'S', 6679.00, 2808.52, 25.0471, 'ENTITY3', 'T');
|
14
|
+
insert into trans values('2013-05-23', 'S', 23054.00, 9694.21, 26.8015, 'ENTITY3', 'F');
|
data/examples/quick.pdf
ADDED
Binary file
|
data/examples/quick.png
ADDED
Binary file
|
data/examples/quick.ppm
ADDED
Binary file
|
data/examples/quick.tex
ADDED
Binary file
|
@@ -0,0 +1,123 @@
|
|
1
|
+
\begin{longtable}{clcrrc}
|
2
|
+
\bfseries{Ref}&
|
3
|
+
\multicolumn{1}{c}{\bfseries{Date}}&
|
4
|
+
\bfseries{Code}&
|
5
|
+
\multicolumn{1}{c}{\bfseries{Shares}}&
|
6
|
+
\multicolumn{1}{c}{\bfseries{Price}}&
|
7
|
+
\bfseries{Ok}\\
|
8
|
+
\endhead
|
9
|
+
\bfseries{1}&
|
10
|
+
2013-05-02&
|
11
|
+
P&
|
12
|
+
\cellcolor{lightgray}{\textcolor{blue}{118,186.4}}&
|
13
|
+
\$11.8500&
|
14
|
+
Y\\
|
15
|
+
\bfseries{2}&
|
16
|
+
2013-05-02&
|
17
|
+
P&
|
18
|
+
\cellcolor{lightgray}{\textcolor{blue}{795,546.2}}&
|
19
|
+
1.1850&
|
20
|
+
Y\\
|
21
|
+
\bfseries{Avg}&
|
22
|
+
&
|
23
|
+
\multicolumn{1}{l}{}&
|
24
|
+
\bfseries{456,866.3}&
|
25
|
+
\bfseries{6.5175}&
|
26
|
+
\\
|
27
|
+
\bfseries{3}&
|
28
|
+
2013-05-20&
|
29
|
+
S&
|
30
|
+
\cellcolor{lightgray}{\textcolor{blue}{5,046.0}}&
|
31
|
+
28.2804&
|
32
|
+
N\\
|
33
|
+
\bfseries{4}&
|
34
|
+
2013-05-20&
|
35
|
+
S&
|
36
|
+
\cellcolor{lightgray}{\textcolor{blue}{35,742.5}}&
|
37
|
+
28.3224&
|
38
|
+
Y\\
|
39
|
+
\bfseries{5}&
|
40
|
+
2013-05-20&
|
41
|
+
S&
|
42
|
+
\cellcolor{lightgray}{\textcolor{blue}{14,003.5}}&
|
43
|
+
28.6383&
|
44
|
+
Y\\
|
45
|
+
\bfseries{Avg}&
|
46
|
+
&
|
47
|
+
\multicolumn{1}{l}{}&
|
48
|
+
\bfseries{18,264.0}&
|
49
|
+
\bfseries{28.4137}&
|
50
|
+
\\
|
51
|
+
\bfseries{6}&
|
52
|
+
2013-05-23&
|
53
|
+
S&
|
54
|
+
\cellcolor{lightgray}{\textcolor{blue}{3,364.0}}&
|
55
|
+
27.1083&
|
56
|
+
Y\\
|
57
|
+
\bfseries{7}&
|
58
|
+
2013-05-23&
|
59
|
+
S&
|
60
|
+
\cellcolor{lightgray}{\textcolor{blue}{16,780.5}}&
|
61
|
+
25.1749&
|
62
|
+
Y\\
|
63
|
+
\bfseries{8}&
|
64
|
+
2013-05-23&
|
65
|
+
S&
|
66
|
+
\cellcolor{lightgray}{\textcolor{blue}{9,694.2}}&
|
67
|
+
26.8015&
|
68
|
+
N\\
|
69
|
+
\bfseries{Avg}&
|
70
|
+
&
|
71
|
+
\multicolumn{1}{l}{}&
|
72
|
+
\bfseries{9,946.2}&
|
73
|
+
\bfseries{26.3616}&
|
74
|
+
\\
|
75
|
+
\bfseries{9}&
|
76
|
+
2013-05-29&
|
77
|
+
S&
|
78
|
+
\cellcolor{lightgray}{\textcolor{blue}{6,601.9}}&
|
79
|
+
24.7790&
|
80
|
+
N\\
|
81
|
+
\bfseries{10}&
|
82
|
+
2013-05-29&
|
83
|
+
S&
|
84
|
+
\cellcolor{lightgray}{\textcolor{blue}{5,659.5}}&
|
85
|
+
24.7464&
|
86
|
+
Y\\
|
87
|
+
\bfseries{11}&
|
88
|
+
2013-05-29&
|
89
|
+
S&
|
90
|
+
\cellcolor{lightgray}{\textcolor{blue}{6,686.0}}&
|
91
|
+
24.5802&
|
92
|
+
Y\\
|
93
|
+
\bfseries{Avg}&
|
94
|
+
&
|
95
|
+
\multicolumn{1}{l}{}&
|
96
|
+
\bfseries{6,315.8}&
|
97
|
+
\bfseries{24.7019}&
|
98
|
+
\\
|
99
|
+
\bfseries{12}&
|
100
|
+
2013-05-30&
|
101
|
+
S&
|
102
|
+
\cellcolor{lightgray}{\textcolor{blue}{2,808.5}}&
|
103
|
+
25.0471&
|
104
|
+
Y\\
|
105
|
+
\bfseries{Avg}&
|
106
|
+
&
|
107
|
+
\multicolumn{1}{l}{}&
|
108
|
+
\bfseries{2,808.5}&
|
109
|
+
\bfseries{25.0471}&
|
110
|
+
\\
|
111
|
+
\bfseries{Average}&
|
112
|
+
&
|
113
|
+
\multicolumn{1}{l}{}&
|
114
|
+
\bfseries{85,009.9}&
|
115
|
+
\bfseries{23.0428}&
|
116
|
+
\\
|
117
|
+
\bfseries{Total}&
|
118
|
+
&
|
119
|
+
\multicolumn{1}{l}{}&
|
120
|
+
\bfseries{1,020,119.1}&
|
121
|
+
\bfseries{}&
|
122
|
+
\\
|
123
|
+
\end{longtable}
|
data/examples/trades.db
ADDED
Binary file
|
data/examples/trans.csv
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Ok',
|
2
|
+
'2013-05-29', 'S', 15700.00, 6601.85, 24.7790, 'ENTITY3', FALSE,
|
3
|
+
'2013-05-02', 'P', 118186.40, 118186.4, 11.8500, 'ENTITY1', TRUE,
|
4
|
+
'2013-05-20', 'S', 12000.00, 5046.00, 28.2804, 'ENTITY3', FALSE,
|
5
|
+
'2013-05-23', 'S', 8000.00, 3364.00, 27.1083, 'ENTITY3', TRUE,
|
6
|
+
'2013-05-23', 'S', 39906.00, 16780.47, 25.1749, 'ENTITY3', TRUE,
|
7
|
+
'2013-05-20', 'S', 85000.00, 35742.50, 28.3224, 'ENTITY3', TRUE,
|
8
|
+
'2013-05-02', 'P', 795546.20, 795546.2, 1.1850, 'ENTITY1', TRUE,
|
9
|
+
'2013-05-29', 'S', 13459.00, 5659.51, 24.7464, 'ENTITY3', TRUE,
|
10
|
+
'2013-05-20', 'S', 33302.00, 14003.49, 28.6383, 'ENTITY3', TRUE,
|
11
|
+
'2013-05-29', 'S', 15900.00, 6685.95, 24.5802, 'ENTITY3', TRUE,
|
12
|
+
'2013-05-30', 'S', 6679.00, 2808.52, 25.0471, 'ENTITY3', TRUE,
|
13
|
+
'2013-05-23', 'S', 23054.00, 9694.21, 26.8015, 'ENTITY3', FALSE
|
data/fat_table.gemspec
CHANGED
@@ -64,13 +64,13 @@ Gem::Specification.new do |spec|
|
|
64
64
|
spec.metadata['yard.run'] = 'yri' # use "yard" to build full HTML docs.
|
65
65
|
|
66
66
|
spec.add_development_dependency 'bundler'
|
67
|
-
spec.add_development_dependency '
|
67
|
+
spec.add_development_dependency 'debug', '>= 1.0.0'
|
68
68
|
spec.add_development_dependency 'pry'
|
69
|
-
spec.add_development_dependency 'pry-byebug'
|
70
69
|
spec.add_development_dependency 'pry-doc'
|
71
70
|
spec.add_development_dependency 'rake', '~> 13.0'
|
72
71
|
spec.add_development_dependency 'redcarpet'
|
73
72
|
spec.add_development_dependency 'pg'
|
73
|
+
spec.add_development_dependency 'sqlite3'
|
74
74
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
75
75
|
spec.add_development_dependency 'rubocop-rspec'
|
76
76
|
spec.add_development_dependency 'rubocop-performance'
|
data/lib/ext/array.rb
ADDED
data/lib/fat_table/column.rb
CHANGED
@@ -83,7 +83,7 @@ module FatTable
|
|
83
83
|
# col.type #=> 'Numeric'
|
84
84
|
# col.header #=> :prices
|
85
85
|
# col.sum #=> 18376.75
|
86
|
-
def initialize(header:, items: [])
|
86
|
+
def initialize(header:, items: [], type: 'NilClass')
|
87
87
|
@raw_header = header
|
88
88
|
@header =
|
89
89
|
if @raw_header.is_a?(Symbol)
|
@@ -91,7 +91,7 @@ module FatTable
|
|
91
91
|
else
|
92
92
|
@raw_header.to_s.as_sym
|
93
93
|
end
|
94
|
-
@type =
|
94
|
+
@type = type
|
95
95
|
msg = "unknown column type '#{type}"
|
96
96
|
raise UserError, msg unless TYPES.include?(@type.to_s)
|
97
97
|
|
@@ -143,7 +143,7 @@ module FatTable
|
|
143
143
|
|
144
144
|
# Force the column to have String type and then convert all items to
|
145
145
|
# strings.
|
146
|
-
def
|
146
|
+
def force_string!
|
147
147
|
# msg = "Can only force an empty column to String type"
|
148
148
|
# raise UserError, msg unless empty?
|
149
149
|
@type = 'String'
|
@@ -175,66 +175,92 @@ module FatTable
|
|
175
175
|
|
176
176
|
# The names of the known aggregate operations that can be performed on a
|
177
177
|
# Column.
|
178
|
-
VALID_AGGREGATES = %s(first last
|
179
|
-
sum count min max
|
178
|
+
VALID_AGGREGATES = %s(first last range
|
179
|
+
sum count min max
|
180
|
+
avg var pvar dev pdev
|
180
181
|
any? all? none? one?)
|
181
182
|
|
182
183
|
# :category: Aggregates
|
183
184
|
|
184
185
|
# Return the first non-nil item in the Column. Works with any Column type.
|
185
186
|
def first
|
186
|
-
|
187
|
+
if type == 'String'
|
188
|
+
items.reject(&:blank?).first
|
189
|
+
else
|
190
|
+
items.compact.first
|
191
|
+
end
|
187
192
|
end
|
188
193
|
|
189
194
|
# :category: Aggregates
|
190
195
|
|
191
196
|
# Return the last non-nil item in the Column. Works with any Column type.
|
192
197
|
def last
|
193
|
-
|
198
|
+
if type == 'String'
|
199
|
+
items.reject(&:blank?).last
|
200
|
+
else
|
201
|
+
items.compact.last
|
202
|
+
end
|
194
203
|
end
|
195
204
|
|
196
205
|
# :category: Aggregates
|
197
206
|
|
198
|
-
# Return a
|
199
|
-
#
|
200
|
-
def
|
201
|
-
|
207
|
+
# Return a count of the non-nil items in the Column. Works with any Column
|
208
|
+
# type.
|
209
|
+
def count
|
210
|
+
if type == 'String'
|
211
|
+
items.reject(&:blank?).count.to_d
|
212
|
+
else
|
213
|
+
items.compact.count.to_d
|
214
|
+
end
|
202
215
|
end
|
203
216
|
|
204
217
|
# :category: Aggregates
|
205
218
|
|
206
|
-
# Return the
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
219
|
+
# Return the smallest non-nil, non-blank item in the Column. Works with
|
220
|
+
# numeric, string, and datetime Columns.
|
221
|
+
def min
|
222
|
+
only_with('min', 'NilClass', 'Numeric', 'String', 'DateTime')
|
223
|
+
if type == 'String'
|
224
|
+
items.reject(&:blank?).min
|
225
|
+
else
|
226
|
+
items.compact.min
|
227
|
+
end
|
212
228
|
end
|
213
229
|
|
214
230
|
# :category: Aggregates
|
215
231
|
|
216
|
-
# Return
|
217
|
-
#
|
218
|
-
def
|
219
|
-
|
232
|
+
# Return the largest non-nil, non-blank item in the Column. Works with
|
233
|
+
# numeric, string, and datetime Columns.
|
234
|
+
def max
|
235
|
+
only_with('max', 'NilClass', 'Numeric', 'String', 'DateTime')
|
236
|
+
if type == 'String'
|
237
|
+
items.reject(&:blank?).max
|
238
|
+
else
|
239
|
+
items.compact.max
|
240
|
+
end
|
220
241
|
end
|
221
242
|
|
222
243
|
# :category: Aggregates
|
223
244
|
|
224
|
-
# Return the smallest
|
225
|
-
# string, and datetime Columns.
|
226
|
-
def
|
227
|
-
only_with('
|
228
|
-
|
245
|
+
# Return a Range object for the smallest to largest value in the column.
|
246
|
+
# Works with numeric, string, and datetime Columns.
|
247
|
+
def range
|
248
|
+
only_with('range', 'NilClass', 'Numeric', 'String', 'DateTime')
|
249
|
+
Range.new(min, max)
|
229
250
|
end
|
230
251
|
|
231
252
|
# :category: Aggregates
|
232
253
|
|
233
|
-
# Return the
|
234
|
-
# string,
|
235
|
-
|
236
|
-
|
237
|
-
|
254
|
+
# Return the sum of the non-nil items in the Column. Works with numeric and
|
255
|
+
# string Columns. For a string Column, it will return the concatenation of
|
256
|
+
# the non-nil items.
|
257
|
+
def sum
|
258
|
+
only_with('sum', 'Numeric', 'String')
|
259
|
+
if type == 'String'
|
260
|
+
items.reject(&:blank?).join(' ')
|
261
|
+
else
|
262
|
+
items.compact.sum
|
263
|
+
end
|
238
264
|
end
|
239
265
|
|
240
266
|
# :category: Aggregates
|
@@ -392,187 +418,24 @@ module FatTable
|
|
392
418
|
|
393
419
|
private
|
394
420
|
|
395
|
-
# Convert val to the type of key, a ruby class constant, such as Date,
|
396
|
-
# Numeric, etc. If type is NilClass, the type is open, and a non-blank val
|
397
|
-
# will attempt conversion to one of the allowed types, typing it as a String
|
398
|
-
# if no other type is recognized. If the val is blank, and the type is nil,
|
399
|
-
# the Column type remains open. If the val is nil or a blank and the type is
|
400
|
-
# already determined, the val is set to nil, and should be filtered from any
|
401
|
-
# Column computations. If the val is non-blank and the Column type
|
402
|
-
# determined, raise an error if the val cannot be converted to the Column
|
403
|
-
# type. Otherwise, returns the converted val as an object of the correct
|
404
|
-
# class.
|
405
421
|
def convert_to_type(val)
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
new_val
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
new_val
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
convert_to_string(val)
|
420
|
-
else
|
421
|
-
bool_val
|
422
|
-
end
|
423
|
-
@type =
|
424
|
-
if [true, false].include?(new_val)
|
425
|
-
'Boolean'
|
426
|
-
elsif new_val.is_a?(Date) || new_val.is_a?(DateTime)
|
427
|
-
'DateTime'
|
428
|
-
elsif new_val.is_a?(Numeric)
|
429
|
-
'Numeric'
|
430
|
-
elsif new_val.is_a?(String)
|
431
|
-
'String'
|
432
|
-
else
|
433
|
-
msg = "can't add #{val} of type #{new_val.class.name} to a column"
|
434
|
-
raise UserError, msg
|
435
|
-
end
|
436
|
-
end
|
437
|
-
new_val
|
438
|
-
when 'Boolean'
|
439
|
-
if val.is_a?(String) && val.blank? || val.nil?
|
440
|
-
nil
|
441
|
-
else
|
442
|
-
new_val = convert_to_boolean(val)
|
443
|
-
if new_val.nil?
|
444
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
422
|
+
new_val = Convert.convert_to_type(val, type)
|
423
|
+
if new_val && type == 'NilClass'
|
424
|
+
@type =
|
425
|
+
if [true, false].include?(new_val)
|
426
|
+
'Boolean'
|
427
|
+
elsif new_val.is_a?(Date) || new_val.is_a?(DateTime)
|
428
|
+
'DateTime'
|
429
|
+
elsif new_val.is_a?(Numeric)
|
430
|
+
'Numeric'
|
431
|
+
elsif new_val.is_a?(String)
|
432
|
+
'String'
|
433
|
+
else
|
434
|
+
msg = "can't add value '#{val}' of type #{new_val.class.name} to a column"
|
445
435
|
raise UserError, msg
|
446
436
|
end
|
447
|
-
new_val
|
448
|
-
end
|
449
|
-
when 'DateTime'
|
450
|
-
if val.blank?
|
451
|
-
nil
|
452
|
-
else
|
453
|
-
new_val = convert_to_date_time(val)
|
454
|
-
if new_val.nil?
|
455
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
456
|
-
raise UserError, msg
|
457
|
-
end
|
458
|
-
new_val
|
459
|
-
end
|
460
|
-
when 'Numeric'
|
461
|
-
if val.blank?
|
462
|
-
nil
|
463
|
-
else
|
464
|
-
new_val = convert_to_numeric(val)
|
465
|
-
if new_val.nil?
|
466
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
467
|
-
raise UserError, msg
|
468
|
-
end
|
469
|
-
new_val
|
470
|
-
end
|
471
|
-
when 'String'
|
472
|
-
if val.nil?
|
473
|
-
nil
|
474
|
-
else
|
475
|
-
new_val = convert_to_string(val)
|
476
|
-
if new_val.nil?
|
477
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
478
|
-
raise UserError, msg
|
479
|
-
end
|
480
|
-
new_val
|
481
|
-
end
|
482
|
-
else
|
483
|
-
raise UserError, "Mysteriously, column has unknown type '#{type}'"
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
# Convert the val to a boolean if it looks like one, otherwise return nil.
|
488
|
-
# Any boolean or a string of t, f, true, false, y, n, yes, or no, regardless
|
489
|
-
# of case is assumed to be a boolean.
|
490
|
-
def convert_to_boolean(val)
|
491
|
-
return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
492
|
-
val = val.to_s.clean
|
493
|
-
return nil if val.blank?
|
494
|
-
if val.match?(/\A(false|f|n|no)\z/i)
|
495
|
-
false
|
496
|
-
elsif val.match?(/\A(true|t|y|yes)\z/i)
|
497
|
-
true
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
ISO_DATE_RE = %r{(?<yr>\d\d\d\d)[-\/]
|
502
|
-
(?<mo>\d\d?)[-\/]
|
503
|
-
(?<dy>\d\d?)\s*
|
504
|
-
(T?\s*\d\d:\d\d(:\d\d)?
|
505
|
-
([-+](\d\d?)(:\d\d?))?)?}x
|
506
|
-
|
507
|
-
AMR_DATE_RE = %r{(?<dy>\d\d?)[-/](?<mo>\d\d?)[-/](?<yr>\d\d\d\d)\s*
|
508
|
-
(?<tm>T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?}x
|
509
|
-
|
510
|
-
# A Date like 'Tue, 01 Nov 2016' or 'Tue 01 Nov 2016' or '01 Nov 2016'.
|
511
|
-
# These are emitted by Postgresql, so it makes from_sql constructor
|
512
|
-
# possible without special formatting of the dates.
|
513
|
-
INV_DATE_RE = %r{((mon|tue|wed|thu|fri|sat|sun)[a-zA-z]*,?)?\s+ # looks like dow
|
514
|
-
(?<dy>\d\d?)\s+ # one or two-digit day
|
515
|
-
(?<mo_name>[jfmasondJFMASOND][A-Za-z]{2,})\s+ # looks like a month name
|
516
|
-
(?<yr>\d\d\d\d) # and a 4-digit year
|
517
|
-
}xi
|
518
|
-
|
519
|
-
# Convert the val to a DateTime if it is either a DateTime, a Date, a Time, or a
|
520
|
-
# String that can be parsed as a DateTime, otherwise return nil. It only
|
521
|
-
# recognizes strings that contain a something like '2016-01-14' or '2/12/1985'
|
522
|
-
# within them, otherwise DateTime.parse would treat many bare numbers as dates,
|
523
|
-
# such as '2841381', which it would recognize as a valid date, but the user
|
524
|
-
# probably does not intend it to be so treated.
|
525
|
-
def convert_to_date_time(val)
|
526
|
-
return val if val.is_a?(DateTime)
|
527
|
-
return val if val.is_a?(Date)
|
528
|
-
return val.to_datetime if val.is_a?(Time)
|
529
|
-
begin
|
530
|
-
str = val.to_s.clean
|
531
|
-
return nil if str.blank?
|
532
|
-
|
533
|
-
if str.match(ISO_DATE_RE)
|
534
|
-
date = DateTime.parse(val)
|
535
|
-
elsif str =~ AMR_DATE_RE
|
536
|
-
date = DateTime.new(Regexp.last_match[:yr].to_i,
|
537
|
-
Regexp.last_match[:mo].to_i,
|
538
|
-
Regexp.last_match[:dy].to_i)
|
539
|
-
elsif str =~ INV_DATE_RE
|
540
|
-
mo = Date.mo_name_to_num(last_match[:mo_name])
|
541
|
-
date = DateTime.new(Regexp.last_match[:yr].to_i, mo,
|
542
|
-
Regexp.last_match[:dy].to_i)
|
543
|
-
else
|
544
|
-
return nil
|
545
|
-
end
|
546
|
-
# val = val.to_date if
|
547
|
-
date.seconds_since_midnight.zero? ? date.to_date : date
|
548
|
-
rescue ArgumentError
|
549
|
-
nil
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
|
-
# Convert the val to a Numeric if is already a Numeric or is a String that
|
554
|
-
# looks like one. Any Float is promoted to a BigDecimal. Otherwise return
|
555
|
-
# nil.
|
556
|
-
def convert_to_numeric(val)
|
557
|
-
return BigDecimal(val, Float::DIG) if val.is_a?(Float)
|
558
|
-
return val if val.is_a?(Numeric)
|
559
|
-
# Eliminate any commas, $'s (or other currency symbol), or _'s.
|
560
|
-
cursym = Regexp.quote(FatTable.currency_symbol)
|
561
|
-
clean_re = /[,_#{cursym}]/
|
562
|
-
val = val.to_s.clean.gsub(clean_re, '')
|
563
|
-
return nil if val.blank?
|
564
|
-
case val
|
565
|
-
when /(\A[-+]?\d+\.\d*\z)|(\A[-+]?\d*\.\d+\z)/
|
566
|
-
BigDecimal(val.to_s.clean)
|
567
|
-
when /\A[-+]?[\d]+\z/
|
568
|
-
val.to_i
|
569
|
-
when %r{\A(?<nm>[-+]?\d+)\s*[:/]\s*(?<dn>[-+]?\d+)\z}
|
570
|
-
Rational(Regexp.last_match[:nm], Regexp.last_match[:dn])
|
571
437
|
end
|
572
|
-
|
573
|
-
|
574
|
-
def convert_to_string(val)
|
575
|
-
val.to_s
|
438
|
+
new_val
|
576
439
|
end
|
577
440
|
end
|
578
441
|
end
|