fat_table 0.6.6 → 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: b9661335d0ae8ec4d87b9e16c3055552af05a354c5222b9838d51f8109ddd749
4
- data.tar.gz: fe23c745a61b2fff7d3380221ee133b5e38c3bdd9d4ff429bdce2094cbe47755
3
+ metadata.gz: 4c16c57274c9f0f67abf924eeae6bce192f82844366199361ac68a7d9e1fdcf4
4
+ data.tar.gz: 42ea485003f76a56f66988a2bf4df159580dbbed022b1f637260ff8e1cd5888b
5
5
  SHA512:
6
- metadata.gz: 201d3df5ba9820b96e22b1a035013c128c2f8cf2efd054cd16b9b6f372c13381334db9cc11b268daa0537d25e2d0475c35cd73f1749d08d8a008a2498010ead1
7
- data.tar.gz: e067fc5bedae6541e0ce88e41025fffbff64a16a3545fccd89a6b171f2b6d648fd8e1716227ceafd770db509417361781c3c6d4cc718824ec8b9098a266eafab
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?
@@ -53,35 +53,38 @@ module FatTable
53
53
  # 'Average'.
54
54
  attr_reader :gfooters
55
55
 
56
- class_attribute :default_format
57
- self.default_format = {
58
- nil_text: '',
59
- case: :none,
60
- alignment: :left,
61
- bold: false,
62
- italic: false,
63
- color: 'none',
64
- bgcolor: 'none',
65
- hms: false,
66
- pre_digits: 0,
67
- post_digits: 0,
68
- commas: false,
69
- currency: false,
70
- datetime_fmt: '%F %H:%M:%S',
71
- date_fmt: '%F',
72
- true_text: 'T',
73
- false_text: 'F',
74
- true_color: 'none',
75
- true_bgcolor: 'none',
76
- false_color: 'none',
77
- false_bgcolor: 'none',
78
- underline: false,
79
- blink: false,
80
- }
81
-
82
56
  class_attribute :valid_colors
83
57
  self.valid_colors = ['none']
84
58
 
59
+ def self.default_format
60
+ {
61
+ nil_text: '',
62
+ case: :none,
63
+ alignment: :left,
64
+ bold: false,
65
+ italic: false,
66
+ color: 'none',
67
+ bgcolor: 'none',
68
+ hms: false,
69
+ pre_digits: 0,
70
+ post_digits: 0,
71
+ commas: false,
72
+ currency: false,
73
+ datetime_fmt: '%F %H:%M:%S',
74
+ date_fmt: '%F',
75
+ true_text: 'T',
76
+ false_text: 'F',
77
+ true_color: 'none',
78
+ true_bgcolor: 'none',
79
+ false_color: 'none',
80
+ false_bgcolor: 'none',
81
+ underline: false,
82
+ blink: false,
83
+ _h: nil,
84
+ _location: nil
85
+ }
86
+ end
87
+
85
88
  # :category: Constructors
86
89
 
87
90
  # Return a new Formatter for the given +table+ which must be of the class
@@ -659,7 +662,7 @@ module FatTable
659
662
  # hash.
660
663
  format_h =
661
664
  if format_at[location][h].empty?
662
- default_format.dup
665
+ default_format
663
666
  else
664
667
  format_at[location][h].to_h
665
668
  end
@@ -700,17 +703,17 @@ module FatTable
700
703
  # format_for call with those locations.
701
704
  if location == :body
702
705
  format_h.each_pair do |k, v|
703
- if format_at[:bfirst][h].send(k) == default_format[k]
706
+ if format_at[:bfirst][h].send(k) == self.class.default_format[k]
704
707
  format_at[:bfirst][h].send("#{k}=", v)
705
708
  end
706
- if format_at[:gfirst][h].send(k) == default_format[k]
709
+ if format_at[:gfirst][h].send(k) == self.class.default_format[k]
707
710
  format_at[:gfirst][h].send("#{k}=", v)
708
711
  end
709
712
  end
710
713
  elsif location == :gfirst
711
714
  # Copy :gfirst formatting to :bfirst if it is still the default
712
715
  format_h.each_pair do |k, v|
713
- if format_at[:bfirst][h].send(k) == default_format[k]
716
+ if format_at[:bfirst][h].send(k) == self.class.default_format[k]
714
717
  format_at[:bfirst][h].send("#{k}=", v)
715
718
  end
716
719
  end
@@ -942,9 +945,10 @@ module FatTable
942
945
  # Convert a value to a string based on the instructions in istruct,
943
946
  # depending on the type of val. "Formatting," which changes the content of
944
947
  # the string, such as adding commas, is always performed, except alignment
945
- # which is only performed when the width parameter is non-nil. "Decorating",
946
- # which changes the appearance without changing the content, is performed
947
- # only if the decorate parameter is true.
948
+ # which is only performed when the width parameter is
949
+ # non-nil. "Decorating", which changes the appearance without changing the
950
+ # content, is performed only if the decorate parameter is true. Priority:
951
+ # lowest to highest: type, location, column_name
948
952
  def format_cell(val, istruct, width: nil, decorate: false)
949
953
  case val
950
954
  when Numeric
@@ -982,8 +986,6 @@ module FatTable
982
986
  end
983
987
  end
984
988
 
985
- private
986
-
987
989
  # Add LaTeX control sequences, ANSI terminal escape codes, or other
988
990
  # decorations to string to decorate it with the given attributes. None of
989
991
  # the decorations may affect the displayed width of the string. Return the
@@ -992,6 +994,8 @@ module FatTable
992
994
  str
993
995
  end
994
996
 
997
+ private
998
+
995
999
  # Convert a boolean to a string according to instructions in istruct, which
996
1000
  # is assumed to be the result of parsing a formatting instruction string as
997
1001
  # above. Only device-independent formatting is done here. Device dependent
@@ -1099,7 +1103,7 @@ module FatTable
1099
1103
  val
1100
1104
  end
1101
1105
  if width && aligned?
1102
- pad = width - width(val)
1106
+ pad = [width - width(val), 0].max
1103
1107
  case istruct.alignment
1104
1108
  when :left
1105
1109
  val += ' ' * pad
@@ -95,18 +95,6 @@ module FatTable
95
95
  result
96
96
  end
97
97
 
98
- private
99
-
100
- def color_valid?(clr)
101
- valid_colors.include?(clr)
102
- end
103
-
104
- def invalid_color_msg(clr)
105
- valid_colors_list = valid_colors.join(' ').wrap
106
- "LaTeXFormatter invalid color '#{clr}'. Valid colors are:\n" +
107
- valid_colors_list
108
- end
109
-
110
98
  # Add LaTeX control sequences. Ignore background color, underline, and
111
99
  # blink. Alignment needs to be done by LaTeX, so we have to take it into
112
100
  # account unless it's the same as the body alignment, since that is the
@@ -119,15 +107,29 @@ module FatTable
119
107
  result = "{\\textcolor{#{istruct.color}}{#{result}}}"
120
108
  end
121
109
  if istruct.bgcolor && istruct.bgcolor != 'none'
122
- result = "\\cellcolor{#{istruct.bgcolor}}#{result}"
110
+ result = "{\\cellcolor{#{istruct.bgcolor}}{#{result}}}"
123
111
  end
124
- unless istruct.alignment == format_at[:body][istruct._h].alignment
112
+ if (istruct._h && format_at[:body][istruct._h] &&
113
+ istruct.alignment != format_at[:body][istruct._h].alignment) ||
114
+ (istruct._h.nil? && istruct.alignment.to_sym != :left)
125
115
  ac = alignment_code(istruct.alignment)
126
116
  result = "\\multicolumn{1}{#{ac}}{#{result}}"
127
117
  end
128
118
  result
129
119
  end
130
120
 
121
+ private
122
+
123
+ def color_valid?(clr)
124
+ valid_colors.include?(clr)
125
+ end
126
+
127
+ def invalid_color_msg(clr)
128
+ valid_colors_list = valid_colors.join(' ').wrap
129
+ "LaTeXFormatter invalid color '#{clr}'. Valid colors are:\n" +
130
+ valid_colors_list
131
+ end
132
+
131
133
  # Return +str+ with quote marks oriented and special TeX characters quoted.
132
134
  def quote(str)
133
135
  # Replace single and double quotes with TeX oriented quotes.
@@ -159,7 +161,7 @@ module FatTable
159
161
  end
160
162
 
161
163
  def alignment_code(al_sym)
162
- case al_sym
164
+ case al_sym.to_sym
163
165
  when :center
164
166
  'c'
165
167
  when :right
@@ -6,9 +6,12 @@ module FatTable
6
6
  # timestamps and the connector at the beginning of hlines is a '|' rather than
7
7
  # a '+' as for text tables.
8
8
  class OrgFormatter < Formatter
9
- self.default_format = default_format.dup
10
- default_format[:date_fmt] = '[%F %a]'
11
- default_format[:datetime_fmt] = '[%F %a %H:%M:%S]'
9
+ def self.default_format
10
+ fmt = super
11
+ fmt[:date_fmt] = '[%F %a]'
12
+ fmt[:datetime_fmt] = '[%F %a %H:%M:%S]'
13
+ fmt
14
+ end
12
15
 
13
16
  private
14
17
 
@@ -38,6 +38,17 @@ module FatTable
38
38
  self.valid_colors = ['none'] +
39
39
  ::Rainbow::X11ColorNames::NAMES.keys.map(&:to_s).sort
40
40
 
41
+ # Add ANSI codes to string to implement the given decorations
42
+ def decorate_string(str, istruct)
43
+ result = Rainbow(str)
44
+ result = colorize(result, istruct.color, istruct.bgcolor)
45
+ result = result.bold if istruct.bold
46
+ result = result.italic if istruct.italic
47
+ result = result.underline if istruct.underline
48
+ result = result.blink if istruct.blink
49
+ result
50
+ end
51
+
41
52
  private
42
53
 
43
54
  def color_valid?(clr)
@@ -66,17 +77,6 @@ module FatTable
66
77
  str.gsub(/\e\[[0-9;]+m/, '')
67
78
  end
68
79
 
69
- # Add ANSI codes to string to implement the given decorations
70
- def decorate_string(str, istruct)
71
- result = Rainbow(str)
72
- result = colorize(result, istruct.color, istruct.bgcolor)
73
- result = result.bold if istruct.bold
74
- result = result.italic if istruct.italic
75
- result = result.underline if istruct.underline
76
- result = result.blink if istruct.blink
77
- result
78
- end
79
-
80
80
  def colorize(str, fg_color, bg_color)
81
81
  fg_color = nil if fg_color == 'none'
82
82
  bg_color = nil if bg_color == 'none'
@@ -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.6.6'
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.6.6
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-01-09 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