fat_table 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba8791fd0b89302cb5127adc325afd0a84bee337298955ecb48119805bcff53a
4
- data.tar.gz: da6f4923fe75df0707c3fc21f96fef52127188e76f8a9ae1b923ea768689d31e
3
+ metadata.gz: 4c16c57274c9f0f67abf924eeae6bce192f82844366199361ac68a7d9e1fdcf4
4
+ data.tar.gz: 42ea485003f76a56f66988a2bf4df159580dbbed022b1f637260ff8e1cd5888b
5
5
  SHA512:
6
- metadata.gz: 7f9b047a93e55f7752173dc7f2c927db2bf45669ede7de1d652b89f1052054f90763c7910b850f84d80ea86a959c82e48f9f9d5b5fe9192ff6b15f9dabb104a7
7
- data.tar.gz: 498eb355cce5f5fa8597ad23657d4699cfead201b6bbf5200386f50773c846061199de0373edbe41ff94c79e1a5d90da8c1f9a37d275c580a0a021109c2d8780
6
+ metadata.gz: 32a6d03a5effa045ef6db54654aef86d96e90846cd0002ada80eaf8218c573f8a430067119562b8369226f11bc45d1bd0c144ddb8c1947c7fc9256fca55389b0
7
+ data.tar.gz: f0a033ff19d04160b4137f1204df322174926be2a0d7f2c442388a358a8ddc4321f5edcc1670346b9b366aa8a55acbe6a23b6b44f0fa9a69bb038e9eee7d24ac
data/README.org CHANGED
@@ -445,27 +445,49 @@ or nil. There are only five permissible types for a ~Column~:
445
445
  4. *String* (for ruby ~String~ objects), or
446
446
  5. *NilClass* (for the undetermined column type).
447
447
 
448
- When a ~Table~ is constructed from an external source, all ~Columns~ start out
449
- having a type of ~NilClass~, that is, their type is as yet undetermined. When
450
- a string or object of one of the four determined types is added to a ~Column~
451
- and it can be converted into one of the permissible types, it fixes the type
452
- of the column, and all further items added to the ~Column~ must either be
453
- ~nil~ (indicating no value) or be capable of being coerced to the column's
454
- type. Otherwise, ~FatTable~ raises an exception.
448
+ By default, when a ~Table~ is constructed from an external source, all
449
+ ~Columns~ start out having a type of ~NilClass~, that is, their type is as yet
450
+ undetermined. When a string or object is added to a ~Column~ and it can be
451
+ converted into one of the permissible types, it fixes the type of the column,
452
+ and all further items added to the ~Column~ must either be ~nil~ (indicating
453
+ no value) or be capable of being coerced to the column's type. Otherwise,
454
+ ~FatTable~ raises an ~IncompatibleTypeError~ exception.
455
+
456
+ *** Type Keywords Arguments
457
+ All of the table constructors allow you to set the type for a column in
458
+ advance by adding keyword arguments to the end of the contructor arguments
459
+ where the keyword is a header symbol and the value is a string designating one
460
+ of the types. For example, suppose we are constructing a table from a CSV
461
+ file, and we know that one of the columns is labeled 'Start' and another
462
+ 'Price'. We want to require the items in the 'Start' column to be a valid
463
+ date and the items in the 'Price' column to be valid numbers:
464
+
465
+ #+begin_example
466
+ FatTable.from_csv_file('data.csv', start: 'date', price: 'num')
467
+ #+end_example
468
+
469
+ The type string can be anything that starts with 'dat', 'num', 'boo', or
470
+ 'str', regardless of case, to designate ~DateTime~, ~Numeric~, ~Boolean~, or
471
+ ~String~ types, respectively. Any other string keeps the type as NilClass,
472
+ that is, it remains open for automatic typing.
455
473
 
456
474
  The strictness of requiring all items to be of the same type can be relaxed by
457
- declaring a column to be "tolerant." You can do so when you create the table
458
- by adding a tolerant_columns keyword parameter. If a Column is tolerant,
459
- ~FatTable~ tries to convert new items into a type other than a ~String~ and,
460
- if it can do so, sets /that/ as the Column's type. Any later items that
461
- cannot be converted into the Column's type are converted to strings. These
462
- interloper strings are treated like nils for purposes of sorting and
463
- evaluation, but are displayed according to any string formatting on output.
464
- See [[*Designating "Tolerant" Columns][Designating "Tolerant" Columns]] below.
465
-
466
- It is also possible to force ~FatTable~ to treat a column as a String type,
467
- even its items look like one of the other types. See [[*Forcing String Type][Forcing String Type]]
468
- below.
475
+ declaring a column to be "tolerant." You can do so by adding a '~' to the end
476
+ of a keyword type specifier in the table constructor. In the above example,
477
+ if we wanted to allow strings to be mixed up with the numeric prices, we would
478
+ use the following:
479
+
480
+ #+begin_example
481
+ FatTable.from_csv_file('data.csv', start: 'date', price: 'num~')
482
+ #+end_example
483
+
484
+ If a Column is tolerant, ~FatTable~ tries to convert new items into the
485
+ column's specified type, or if the type is still open, to one of ~DateTime~,
486
+ ~Numeric~, or ~Boolean~ and then fixing the column's type, or, if it cannot do
487
+ so converts the item into a ~String~ but does not raise an
488
+ ~IncompatibleTypeError~ exception. These interloper strings are treated like
489
+ nils for purposes of sorting and evaluation, but are displayed according to
490
+ any string formatting on output. See [[*Designating "Tolerant" Columns][Designating "Tolerant" Columns]] below.
469
491
 
470
492
  Items of input must be either one of the permissible ruby objects or strings. If
471
493
  they are strings, ~FatTable~ attempts to parse them as one of the permissible
@@ -626,14 +648,32 @@ columns to be created:
626
648
 
627
649
  Occasionally, ~FatTable~'s automatic type detection can get in the way and you
628
650
  just want it to treat one or more columns as Strings regardless of their
629
- appearance. Think, for example, of zip codes. If headers are given when a
630
- table is contructed, you can designate a forced-string column by appending a
631
- ~!~ to the end of the header. It will not become part of the header, it will
632
- just mark it as a forced-string Column.
651
+ appearance. Think, for example, of zip codes. As mentioned above, when a
652
+ table is contructed, you can designate a 'String' type for a column by
653
+ using a keyword parameter.
633
654
 
634
- #+begin_SRC emacs-lisp :wrap EXAMPLE
635
- tab = FatTable.new(:a, 'b', 'C!', :d, :zip!)
636
- #+end_SRC
655
+ #+begin_src ruby :wrap EXAMPLE
656
+ require 'fat_table'
657
+ tab = FatTable.new(:a, 'b!', 'C', :d, :zip, zip: 'str')
658
+ tab << { a: 1, b: 2, c: "<2017-01-21>", d: 'f', e: '', zip: 18552 }
659
+ tab << { a: 3.14, b: 2.17, c: '[2016-01-21 Thu]', d: 'Y', e: nil }
660
+ tab << { zip: '01879--7884' }
661
+ tab << { zip: '66210', b: 'Not a Number' }
662
+ tab << { zip: '90210' }
663
+ tab.to_text
664
+ #+end_src
665
+
666
+ #+begin_EXAMPLE
667
+ +===+===+============+===+=============+===+
668
+ | A | B | C | D | Zip | E |
669
+ +---+---+------------+---+-------------+---+
670
+ | 1 | 2 | 2017-01-21 | F | 18552 | |
671
+ | 3 | 2 | 2016-01-21 | T | | |
672
+ | | | | | 01879--7884 | |
673
+ | | | | | 90210 | |
674
+ | | | | | | |
675
+ +===+===+============+===+=============+===+
676
+ #+end_EXAMPLE
637
677
 
638
678
  In addition, at any time after creating a table, you can force the String type
639
679
  on any number of columns with the ~force_string!~ method. When you do so, all
@@ -662,84 +702,9 @@ exisiting items in the column are converted to strings with the #to_s method.
662
702
  +======+======+============+===+=======+===+
663
703
  #+end_EXAMPLE
664
704
 
665
- **** Designating "Tolerant" Columns
666
-
667
- Related to the problem just discussed is the problem of reading files in from
668
- the wild where a column may get typed as, say Numeric, but then contain
669
- something that can't be parsed as a Numeric. ~FatTable~ raises an exception
670
- is such cases, and that may be what you want if you can control the input.
671
- But, especially when you cannot do so, it can be helpful to designate one or
672
- more columns as "tolerant." This means that when a conversion problem occurs,
673
- the column item is retained as a string type in a column that is otherwise of
674
- one of the types Numeric, DateTime, or Boolean. Those string items are
675
- treated as nils for purposes of sorting or evaluation in a ~select~ method.
676
- When formatted, they participate in string formatting directive, but not those
677
- for other types.
678
-
679
- All of the table construction methods, allow a keyword parameter,
680
- ~tolerant_columns~, where you can designate what columns should be convert to
681
- String type when conversion to the auto-typed column type is not possible.
682
- The parameter should be an array of headers, in either string or symbol form,
683
- for which this behavior is desired. In addition, it can be set to the special
684
- string '*' or symbol ~:*~ to indicate that all the columns should be made
685
- tolerant.
686
-
687
- #+begin_src ruby :wrap EXAMPLE
688
- require 'fat_table'
689
- tab = FatTable.new(:a, 'b', 'C', :d, :zip, tolerant_columns: [:zip])
690
- tab << { a: 1, b: 2, c: "<2017-01-21>", d: 'f', e: '', zip: 18552 }
691
- tab << { a: 3.14, b: 2.17, c: '[2016-01-21 Thu]', d: 'Y', e: nil }
692
- tab << { zip: '01879--7884' }
693
- tab << { zip: '66210' }
694
- tab << { zip: '90210' }
695
- tab.to_text
696
- #+end_src
697
-
698
- #+RESULTS:
699
- #+begin_EXAMPLE
700
- +======+======+============+===+=============+===+
701
- | A | B | C | D | Zip | E |
702
- +------+------+------------+---+-------------+---+
703
- | 1 | 2 | 2017-01-21 | F | 18552 | |
704
- | 3.14 | 2.17 | 2016-01-21 | T | | |
705
- | | | | | 01879--7884 | |
706
- | | | | | 66210 | |
707
- | | | | | 90210 | |
708
- +======+======+============+===+=============+===+
709
- #+end_EXAMPLE
710
-
711
- Another way to designate a column as tolerant is to end a column you want to
712
- designate as tolerant with a ~!~. The ~!~ will be stripped from the header,
713
- but it will be marked as tolerant.
714
- #+begin_src ruby :wrap EXAMPLE
715
- require 'fat_table'
716
- tab = FatTable.new(:a, 'b!', 'C', :d, :zip!)
717
- tab << { a: 1, b: 2, c: "<2017-01-21>", d: 'f', e: '', zip: 18552 }
718
- tab << { a: 3.14, b: 2.17, c: '[2016-01-21 Thu]', d: 'Y', e: nil }
719
- tab << { zip: '01879--7884' }
720
- tab << { zip: '66210', b: 'Not a Number' }
721
- tab << { zip: '90210' }
722
- tab.to_text
723
- #+end_src
724
-
725
- #+RESULTS:
726
- #+begin_EXAMPLE
727
- +======+==============+============+===+=============+===+
728
- | A | B | C | D | Zip | E |
729
- +------+--------------+------------+---+-------------+---+
730
- | 1 | 2 | 2017-01-21 | F | 18552 | |
731
- | 3.14 | 2.17 | 2016-01-21 | T | | |
732
- | | | | | 01879--7884 | |
733
- | | Not a Number | | | 66210 | |
734
- | | | | | 90210 | |
735
- +======+==============+============+===+=============+===+
736
- #+end_EXAMPLE
737
-
738
705
  *** From CSV or Org Mode files or strings
739
706
  Tables can also be read from ~.csv~ files or files containing ~org-mode~
740
- tables. Remember that you can make any column tolerant with a
741
- ~tolerant_columns:~ keyword argument or make them all tolerant by designating
742
- the pseudo-column ~:*~ as tolerant.
707
+ tables.
743
708
 
744
709
  In the case of org-mode files, ~FatTable~ skips through the file until it finds
745
710
  a line that look like a table, that is, it begins with any number of spaces
@@ -799,10 +764,10 @@ header row, and the headers are converted to symbols as described above.
799
764
 
800
765
  You can also initialize a table directly from ruby data structures. You can,
801
766
  for example, build a table from an array of arrays. Remember that you can
802
- make any column tolerant with a ~tolerant_columns:~ keyword argument or make
803
- them all tolerant by designating the pseudo-column ~:*~ as tolerant.
767
+ make any column tolerant with a keyword argument for the column symbol and
768
+ ending it with a '~'.
804
769
 
805
- #+BEGIN_SRC ruby :eval never
770
+ #+BEGIN_SRC ruby
806
771
  aoa = [
807
772
  ['Ref', 'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Bool'],
808
773
  [1, '2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ENTITY1', 'T'],
@@ -816,11 +781,27 @@ them all tolerant by designating the pseudo-column ~:*~ as tolerant.
816
781
  [13, '2013-05-29', 'S', 13_459.00, 5659.51, 24.7464, 'ENTITY3', 'T'],
817
782
  [14, '2013-05-29', 'S', 15_700.00, 6601.85, 24.7790, 'ENTITY3', 'F'],
818
783
  [15, '2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'ENTITY3', 'T'],
819
- [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ENTITY3', 'T']
820
- ]
821
- tab = FatTable.from_aoa(aoa)
784
+ [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ENTITY3', 'T'] ]
785
+
786
+ tab = FatTable.from_aoa(aoa).to_aoa
822
787
  #+END_SRC
823
788
 
789
+ #+RESULTS:
790
+ | Ref | Date | Code | Raw | Shares | Price | Info | Bool |
791
+ |-----+------------+------+--------+--------+-------+---------+------|
792
+ | 1 | 2013-05-02 | P | 795546 | 795546 | 1 | ENTITY1 | T |
793
+ | 2 | 2013-05-02 | P | 118186 | 118186 | 12 | ENTITY1 | T |
794
+ | 7 | 2013-05-20 | S | 12000 | 5046 | 28 | ENTITY3 | F |
795
+ | 8 | 2013-05-20 | S | 85000 | 35743 | 28 | ENTITY3 | T |
796
+ | 9 | 2013-05-20 | S | 33302 | 14003 | 29 | ENTITY3 | T |
797
+ | 10 | 2013-05-23 | S | 8000 | 3364 | 27 | ENTITY3 | T |
798
+ | 11 | 2013-05-23 | S | 23054 | 9694 | 27 | ENTITY3 | F |
799
+ | 12 | 2013-05-23 | S | 39906 | 16780 | 25 | ENTITY3 | T |
800
+ | 13 | 2013-05-29 | S | 13459 | 5660 | 25 | ENTITY3 | T |
801
+ | 14 | 2013-05-29 | S | 15700 | 6602 | 25 | ENTITY3 | F |
802
+ | 15 | 2013-05-29 | S | 15900 | 6686 | 25 | ENTITY3 | T |
803
+ | 16 | 2013-05-30 | S | 6679 | 2809 | 25 | ENTITY3 | T |
804
+
824
805
  Notice that the values can either be ruby objects, such as the Integer ~85_000~,
825
806
  or strings that can be parsed into one of the permissible column types.
826
807
 
@@ -898,9 +879,7 @@ This example illustrates several things:
898
879
  A second ruby data structure that can be used to initialize a ~FatTable~ table
899
880
  is an array of ruby Hashes. Each hash represents a row of the table, and the
900
881
  headers of the table are taken from the keys of the hashes. Accordingly, all
901
- the hashes must have the same keys. Remember that you can make any column
902
- tolerant with a ~tolerant_columns:~ keyword argument or make them all tolerant
903
- by designating the pseudo-column ~:*~ as tolerant.
882
+ the hashes must have the same keys.
904
883
 
905
884
  This same method can in fact take an array of any objects that can be converted
906
885
  to a Hash with the ~#to_h~ method, so you can use an array of your own objects
@@ -989,10 +968,6 @@ The ~.connect~ function need only be called once, and the database handle it
989
968
  creates will be used for all subsequent ~.from_sql~ calls until ~.connect~ is
990
969
  called again.
991
970
 
992
- Remember that you can make any column tolerant with a ~tolerant_columns:~
993
- keyword argument or make them all tolerant by designating the pseudo-column
994
- ~:*~ as tolerant.
995
-
996
971
  *** Marking Groups in Input
997
972
  **** Manually
998
973
 
@@ -63,19 +63,6 @@ module FatTable
63
63
  when 'String'
64
64
  if val.nil?
65
65
  nil
66
- elsif tolerant
67
- # Allow String to upgrade to one of Numeric, DateTime, or Boolean if
68
- # possible.
69
- if (new_val = convert_to_numeric(val))
70
- new_val
71
- elsif (new_val = convert_to_date_time(val))
72
- new_val
73
- elsif (new_val = convert_to_boolean(val))
74
- new_val
75
- else
76
- new_val = convert_to_string(val)
77
- end
78
- new_val
79
66
  else
80
67
  new_val = convert_to_string(val)
81
68
  if new_val.nil?
@@ -54,6 +54,10 @@ module FatTable
54
54
  # An Array of FatTable::Columns that constitute the table.
55
55
  attr_reader :columns
56
56
 
57
+ # Headers of columns that are to be tolerant when they are built.
58
+ attr_accessor :tolerant_cols
59
+ attr_reader :omni_typ, :omni_tol
60
+
57
61
  # Record boundaries set explicitly with mark_boundaries or from reading
58
62
  # hlines from input. When we want to access boundaries, however, we want
59
63
  # to add an implict boundary at the last row of the table. Since, as the
@@ -62,55 +66,58 @@ module FatTable
62
66
  # method call.
63
67
  attr_accessor :explicit_boundaries
64
68
 
65
- # An Array of FatTable::Columns that should be tolerant.
66
- attr_reader :tolerant_columns
67
-
68
69
  ###########################################################################
69
70
  # Constructors
70
- ###########################################################################
71
-
71
+ #
72
+ #
72
73
  # :category: Constructors
73
-
74
+ #
74
75
  # Return an empty FatTable::Table object. Specifying headers is optional.
75
- # Any headers ending with a ! are marked as tolerant, in that, if an
76
- # incompatible type is added to it, the column is re-typed as a String
77
- # column, and construction proceeds. The ! is stripped from the header to
78
- # form the column key, though. You can also provide the names of columns
79
- # that should be tolerant by using the +tolerant_columns key-word to
80
- # provide an array of headers that should be tolerant. The special string
81
- # '*' or the symbol :* indicates that all columns should be created
82
- # tolerant.
83
- def initialize(*heads, tolerant_columns: [])
76
+ # By default, all columns start our as having an "open" type and get
77
+ # assigned a type based on their contents. For example, if a column
78
+ # contains items that can be interpreted as dates, the column gets
79
+ # assigned a DateTime type. Other types are Numeric, Boolean, and String.
80
+ # Once a type is assigned to a column, any non-conforming vaules in that
81
+ # column raise an IncompatibleType error. If a column is marked
82
+ # "tolerant", however, the incompatible item is converted to a string and
83
+ # allowed to remain in the column without raising an error. They count as
84
+ # nils when calculations are performed on the column and paricipate only
85
+ # in string formatting directives on output.
86
+ #
87
+ # Rather than have a column's type determined by content, you can also
88
+ # specify a column type by providing a type hash, where the key is the
89
+ # header's name and the value is the desired type. In that case, any
90
+ # incompatible type raises an an IncompatibleTypeError unless the column
91
+ # is also marked tolerant, in which case it gets converted to a string as
92
+ # discussed above. If the type name in the types hash ends in a '~', it
93
+ # is treated as a specifying the given type but marking it as tolerant as
94
+ # well. The values in the type hash can be any string or sybol that
95
+ # starts with 'num', 'dat', 'bool', or 'str' to specify Numeric,
96
+ # DateTime, Boolean, or String types respectively.
97
+ def initialize(*heads, **types)
84
98
  @columns = []
85
- @explicit_boundaries = []
86
- @tolerant_columns =
87
- case tolerant_columns
88
- when Array
89
- tolerant_columns.map { |h| h.to_s.as_sym }
90
- when String
91
- if tolerant_columns.strip == '*'
92
- ['*'.to_sym]
93
- else
94
- [tolerant_columns.as_sym]
95
- end
96
- when Symbol
97
- if tolerant_columns.to_s.strip == '*'
98
- ['*'.to_sym]
99
- else
100
- [tolerant_columns.to_s.as_sym]
101
- end
102
- else
103
- raise ArgumentError, "set tolerant_columns to String, Symbol, or an Array of either"
104
- end
105
- unless heads.empty?
106
- heads.each do |h|
107
- if h.to_s.end_with?('!') || @tolerant_columns.include?(h)
108
- @columns << Column.new(header: h.to_s.sub(/!\s*\z/, ''), type: 'String')
109
- else
110
- @columns << Column.new(header: h)
111
- end
112
- end
99
+ @tolerant_cols = []
100
+ @headers = []
101
+ # Check for the special 'omni' key
102
+ @omni_type = 'NilClass'
103
+ @omni_tol = false
104
+ if types.keys.map(&:to_s).include?('omni')
105
+ # All columns not otherwise included in types should have the type and
106
+ # tolerance of omni.
107
+ omni_val = (types['omni'] || types[:omni])
108
+ @omni_type, @omni_tol = Table.typ_tol(omni_val)
109
+ # Remove omni from types.
110
+ types.delete(:omni)
111
+ types.delete('omni')
112
+ end
113
+ heads += types.keys
114
+ heads.uniq.each do |h|
115
+ typ, tol = Table.typ_tol(types[h])
116
+ @tolerant_cols << h.to_s.as_sym if tol
117
+ @columns << Column.new(header: h.to_s.sub(/~\s*\z/, ''), type: typ,
118
+ tolerant: tol)
113
119
  end
120
+ @explicit_boundaries = []
114
121
  end
115
122
 
116
123
  # :category: Constructors
@@ -133,9 +140,9 @@ module FatTable
133
140
 
134
141
  # Construct a Table from the contents of a CSV file named +fname+. Headers
135
142
  # will be taken from the first CSV row and converted to symbols.
136
- def self.from_csv_file(fname, tolerant_columns: [])
143
+ def self.from_csv_file(fname, **types)
137
144
  File.open(fname, 'r') do |io|
138
- from_csv_io(io, tolerant_columns: tolerant_columns)
145
+ from_csv_io(io, **types)
139
146
  end
140
147
  end
141
148
 
@@ -143,8 +150,8 @@ module FatTable
143
150
 
144
151
  # Construct a Table from a CSV string +str+, treated in the same manner as
145
152
  # the input from a CSV file in ::from_org_file.
146
- def self.from_csv_string(str, tolerant_columns: [])
147
- from_csv_io(StringIO.new(str), tolerant_columns: tolerant_columns)
153
+ def self.from_csv_string(str, **types)
154
+ from_csv_io(StringIO.new(str), **types)
148
155
  end
149
156
 
150
157
  # :category: Constructors
@@ -153,9 +160,9 @@ module FatTable
153
160
  # file named +fname+. Headers are taken from the first row if the second row
154
161
  # is an hrule. Otherwise, synthetic headers of the form +:col_1+, +:col_2+,
155
162
  # etc. are created.
156
- def self.from_org_file(fname, tolerant_columns: [])
163
+ def self.from_org_file(fname, **types)
157
164
  File.open(fname, 'r') do |io|
158
- from_org_io(io, tolerant_columns: tolerant_columns)
165
+ from_org_io(io, **types)
159
166
  end
160
167
  end
161
168
 
@@ -163,8 +170,8 @@ module FatTable
163
170
 
164
171
  # Construct a Table from a string +str+, treated in the same manner as the
165
172
  # contents of an org-mode file in ::from_org_file.
166
- def self.from_org_string(str, tolerant_columns: [])
167
- from_org_io(StringIO.new(str), tolerant_columns: tolerant_columns)
173
+ def self.from_org_string(str, **types)
174
+ from_org_io(StringIO.new(str), **types)
168
175
  end
169
176
 
170
177
  # :category: Constructors
@@ -183,8 +190,8 @@ module FatTable
183
190
  # :hlines no +) org-mode strips all hrules from the table; otherwise (+
184
191
  # HEADER: :hlines yes +) they are indicated with nil elements in the outer
185
192
  # array.
186
- def self.from_aoa(aoa, hlines: false, tolerant_columns: [])
187
- from_array_of_arrays(aoa, hlines: hlines, tolerant_columns: tolerant_columns)
193
+ def self.from_aoa(aoa, hlines: false, **types)
194
+ from_array_of_arrays(aoa, hlines: hlines, **types)
188
195
  end
189
196
 
190
197
  # :category: Constructors
@@ -194,9 +201,9 @@ module FatTable
194
201
  # keys, which, when converted to symbols will become the headers for the
195
202
  # Table. If hlines is set true, mark a group boundary whenever a nil, rather
196
203
  # than a hash appears in the outer array.
197
- def self.from_aoh(aoh, hlines: false, tolerant_columns: [])
204
+ def self.from_aoh(aoh, hlines: false, **types)
198
205
  if aoh.first.respond_to?(:to_h)
199
- from_array_of_hashes(aoh, hlines: hlines, tolerant_columns: tolerant_columns)
206
+ from_array_of_hashes(aoh, hlines: hlines, **types)
200
207
  else
201
208
  raise UserError,
202
209
  "Cannot initialize Table with an array of #{input[0].class}"
@@ -215,7 +222,7 @@ module FatTable
215
222
 
216
223
  # Construct a Table by running a SQL +query+ against the database set up
217
224
  # with FatTable.connect, with the rows of the query result as rows.
218
- def self.from_sql(query, tolerant_columns: [])
225
+ def self.from_sql(query, **types)
219
226
  msg = 'FatTable.db must be set with FatTable.connect'
220
227
  raise UserError, msg if FatTable.db.nil?
221
228
 
@@ -232,13 +239,32 @@ module FatTable
232
239
  ############################################################################
233
240
 
234
241
  class << self
242
+ # Return [typ, tol] based on the type string, str.
243
+ def typ_tol(str)
244
+ tol = str ? str.match?(/~\s*\Z/) : false
245
+ typ =
246
+ case str
247
+ when /\A\s*num/i
248
+ 'Numeric'
249
+ when /\A\s*boo/i
250
+ 'Boolean'
251
+ when /\A\s*dat/i
252
+ 'DateTime'
253
+ when /\A\s*str/i
254
+ 'String'
255
+ else
256
+ 'NilClass'
257
+ end
258
+ [typ, tol]
259
+ end
260
+
235
261
  private
236
262
 
237
263
  # Construct table from an array of hashes or an array of any object that
238
264
  # can respond to #to_h. If an array element is a nil, mark it as a group
239
265
  # boundary in the Table.
240
- def from_array_of_hashes(hashes, hlines: false, tolerant_columns: [])
241
- result = new(tolerant_columns: tolerant_columns)
266
+ def from_array_of_hashes(hashes, hlines: false, **types)
267
+ result = new(**types)
242
268
  hashes.each do |hsh|
243
269
  if hsh.nil?
244
270
  unless hlines
@@ -266,8 +292,8 @@ module FatTable
266
292
  # hlines are stripped from the table, otherwise (:hlines yes) they are
267
293
  # indicated with nil elements in the outer array as expected by this
268
294
  # method when hlines is set true.
269
- def from_array_of_arrays(rows, hlines: false, tolerant_columns: [])
270
- result = new(tolerant_columns: tolerant_columns)
295
+ def from_array_of_arrays(rows, hlines: false, **types)
296
+ result = new(**types)
271
297
  headers = []
272
298
  if !hlines
273
299
  # Take the first row as headers
@@ -303,8 +329,8 @@ module FatTable
303
329
  result
304
330
  end
305
331
 
306
- def from_csv_io(io, tolerant_columns: [])
307
- result = new(tolerant_columns: tolerant_columns)
332
+ def from_csv_io(io, **types)
333
+ result = new(**types)
308
334
  ::CSV.new(io, headers: true, header_converters: :symbol,
309
335
  skip_blanks: true).each do |row|
310
336
  result << row.to_h
@@ -317,7 +343,7 @@ module FatTable
317
343
  # header row must be marked with an hline (i.e, a row that looks like
318
344
  # '|---+--...--|') and groups of rows may be marked with hlines to
319
345
  # indicate group boundaries.
320
- def from_org_io(io, tolerant_columns: [])
346
+ def from_org_io(io, **types)
321
347
  table_re = /\A\s*\|/
322
348
  hrule_re = /\A\s*\|[-+]+/
323
349
  rows = []
@@ -352,7 +378,7 @@ module FatTable
352
378
  rows << line.split('|').map(&:clean)
353
379
  end
354
380
  end
355
- from_array_of_arrays(rows, hlines: true, tolerant_columns: tolerant_columns)
381
+ from_array_of_arrays(rows, hlines: true, **types)
356
382
  end
357
383
  end
358
384
 
@@ -377,6 +403,16 @@ module FatTable
377
403
  column(key).type
378
404
  end
379
405
 
406
+ # Return the type of the Column with the given +key+ as its
407
+ # header as a String.
408
+ def types
409
+ result = {}
410
+ headers.each do |h|
411
+ result[h] = type(h)
412
+ end
413
+ result
414
+ end
415
+
380
416
  # :category: Attributes
381
417
 
382
418
  # Set the column type for Column with the given +key+ as a String type.
@@ -428,7 +464,7 @@ module FatTable
428
464
  # :category: Attributes
429
465
 
430
466
  # Return a Hash of the Table's Column header symbols to type strings.
431
- def types
467
+ def col_types
432
468
  result = {}
433
469
  columns.each do |c|
434
470
  result[c.header] = c.type
@@ -445,11 +481,11 @@ module FatTable
445
481
 
446
482
  # :category: Attributes
447
483
 
448
- # Return whether the column with the given head should be made tolerant.
484
+ # Return whether the column with the given head is supposed to be
485
+ # tolerant. We can't just look up the Column because it may not be build
486
+ # yet, as when we do a row-by-row add.
449
487
  def tolerant_col?(h)
450
- return true if tolerant_columns.include?(:'*')
451
-
452
- tolerant_columns.include?(h)
488
+ tolerant_cols.include?(h.to_s.as_sym) || self.omni_tol
453
489
  end
454
490
 
455
491
  # :category: Attributes
@@ -994,11 +1030,11 @@ module FatTable
994
1030
  result = empty_dup
995
1031
  headers.each do |h|
996
1032
  col =
997
- if tolerant_col?(h)
998
- Column.new(header: h, tolerant: true)
999
- else
1000
- Column.new(header: h)
1001
- end
1033
+ if tolerant_col?(h)
1034
+ Column.new(header: h, tolerant: true)
1035
+ else
1036
+ Column.new(header: h)
1037
+ end
1002
1038
  result.add_column(col)
1003
1039
  end
1004
1040
  ev = Evaluator.new(ivars: { row: 0, group: 0 })
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.7.0'
5
+ VERSION = '0.8.0'
6
6
  end
data/lib/fat_table.rb CHANGED
@@ -61,22 +61,22 @@ module FatTable
61
61
 
62
62
  # Return an empty FatTable::Table object. You can use FatTable::Table#add_row
63
63
  # or FatTable::Table#add_column to populate the table with data.
64
- def self.new(*args, tolerant_columns: [])
65
- Table.new(*args, tolerant_columns: tolerant_columns)
64
+ def self.new(*args, **types)
65
+ Table.new(*args, **types)
66
66
  end
67
67
 
68
68
  # Construct a FatTable::Table from the contents of a CSV file given by the
69
69
  # file name +fname+. Headers will be taken from the first row and converted to
70
70
  # symbols.
71
- def self.from_csv_file(fname, tolerant_columns: [])
72
- Table.from_csv_file(fname, tolerant_columns: tolerant_columns)
71
+ def self.from_csv_file(fname, **types)
72
+ Table.from_csv_file(fname, **types)
73
73
  end
74
74
 
75
75
  # Construct a FatTable::Table from the string +str+, treated in the same
76
76
  # manner as if read the input from a CSV file. Headers will be taken from the
77
77
  # first row and converted to symbols.
78
- def self.from_csv_string(str, tolerant_columns: [])
79
- Table.from_csv_string(str, tolerant_columns: tolerant_columns)
78
+ def self.from_csv_string(str, **types)
79
+ Table.from_csv_string(str, **types)
80
80
  end
81
81
 
82
82
  # Construct a FatTable::Table from the first table found in the Emacs org-mode
@@ -84,8 +84,8 @@ module FatTable
84
84
  # is an hline. Otherwise, synthetic headers of the form +:col_1+, +:col_2+,
85
85
  # etc. are created. Any other hlines will be treated as marking a boundary in
86
86
  # the table.
87
- def self.from_org_file(fname, tolerant_columns: [])
88
- Table.from_org_file(fname, tolerant_columns: tolerant_columns)
87
+ def self.from_org_file(fname, **types)
88
+ Table.from_org_file(fname, **types)
89
89
  end
90
90
 
91
91
  # Construct a FatTable::Table from the first table found in the string +str+,
@@ -93,8 +93,8 @@ module FatTable
93
93
  # are taken from the first row if the second row is an hrule. Otherwise,
94
94
  # synthetic headers of the form :col_1, :col_2, etc. are created. Any other
95
95
  # hlines will be treated as marking a boundary in the table.
96
- def self.from_org_string(str, tolerant_columns: [])
97
- Table.from_org_string(str, tolerant_columns: tolerant_columns)
96
+ def self.from_org_string(str, **types)
97
+ Table.from_org_string(str, **types)
98
98
  end
99
99
 
100
100
  # Construct a FatTable::Table from the array of arrays +aoa+. By default, with
@@ -108,8 +108,8 @@ module FatTable
108
108
  # org-mode code blocks, by default (+:hlines no+) all hlines are stripped from
109
109
  # the table, otherwise (+:hlines yes+) they are indicated with nil elements in
110
110
  # the outer array.
111
- def self.from_aoa(aoa, hlines: false, tolerant_columns: [])
112
- Table.from_aoa(aoa, hlines: hlines, tolerant_columns: tolerant_columns)
111
+ def self.from_aoa(aoa, hlines: false, **types)
112
+ Table.from_aoa(aoa, hlines: hlines, **types)
113
113
  end
114
114
 
115
115
  # Construct a FatTable::Table from the array of hashes +aoh+, which can be an
@@ -117,8 +117,8 @@ module FatTable
117
117
  # interpret nil separators as marking boundaries in the new Table. All hashes
118
118
  # must have the same keys, which, converted to symbols, become the headers for
119
119
  # the new Table.
120
- def self.from_aoh(aoh, hlines: false, tolerant_columns: [])
121
- Table.from_aoh(aoh, hlines: hlines, tolerant_columns: tolerant_columns)
120
+ def self.from_aoh(aoh, hlines: false, **types)
121
+ Table.from_aoh(aoh, hlines: hlines, **types)
122
122
  end
123
123
 
124
124
  # Construct a FatTable::Table from another FatTable::Table. Inherit any group
@@ -130,8 +130,8 @@ module FatTable
130
130
  # Construct a Table by running a SQL query against the database set up with
131
131
  # FatTable.connect. Return the Table with the query results as rows and the
132
132
  # headers from the query, converted to symbols, as headers.
133
- def self.from_sql(query, tolerant_columns: [])
134
- Table.from_sql(query, tolerant_columns: tolerant_columns)
133
+ def self.from_sql(query, **types)
134
+ Table.from_sql(query, **types)
135
135
  end
136
136
 
137
137
  ########################################################################
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-02 00:00:00.000000000 Z
11
+ date: 2023-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler