fat_table 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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