csv_plus_plus 0.1.2 → 0.1.3

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +23 -13
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +32 -7
  9. data/lib/csv_plus_plus/compiler.rb +82 -60
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
  11. data/lib/csv_plus_plus/entities/boolean.rb +18 -9
  12. data/lib/csv_plus_plus/entities/builtins.rb +23 -9
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
  14. data/lib/csv_plus_plus/entities/date.rb +38 -5
  15. data/lib/csv_plus_plus/entities/entity.rb +27 -61
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +23 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +24 -9
  19. data/lib/csv_plus_plus/entities/number.rb +24 -10
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
  21. data/lib/csv_plus_plus/entities/string.rb +19 -6
  22. data/lib/csv_plus_plus/entities/variable.rb +16 -4
  23. data/lib/csv_plus_plus/entities.rb +20 -13
  24. data/lib/csv_plus_plus/error/error.rb +11 -1
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
  28. data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
  29. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  30. data/lib/csv_plus_plus/error.rb +1 -0
  31. data/lib/csv_plus_plus/google_api_client.rb +7 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
  34. data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
  35. data/lib/csv_plus_plus/lexer.rb +24 -0
  36. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  37. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  38. data/lib/csv_plus_plus/modifier/expand.rb +61 -0
  39. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  40. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  41. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  42. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  43. data/lib/csv_plus_plus/modifier.rb +82 -158
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
  46. data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
  48. data/lib/csv_plus_plus/row.rb +53 -12
  49. data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
  50. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
  51. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  52. data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
  53. data/lib/csv_plus_plus/runtime/references.rb +110 -0
  54. data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
  55. data/lib/csv_plus_plus/runtime.rb +34 -191
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +62 -35
  58. data/lib/csv_plus_plus/version.rb +2 -1
  59. data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
  60. data/lib/csv_plus_plus/writer/csv.rb +11 -9
  61. data/lib/csv_plus_plus/writer/excel.rb +9 -2
  62. data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
  64. data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
  65. data/lib/csv_plus_plus/writer/open_document.rb +6 -1
  66. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +29 -12
  69. metadata +18 -14
  70. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  71. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  72. data/lib/csv_plus_plus/data_validation.rb +0 -138
  73. data/lib/csv_plus_plus/expand.rb +0 -20
  74. data/lib/csv_plus_plus/graph.rb +0 -62
  75. data/lib/csv_plus_plus/references.rb +0 -68
  76. data/lib/csv_plus_plus/scope.rb +0 -196
  77. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  78. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  79. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -0,0 +1,243 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Modifier
6
+ # Validates and coerces modifier user inputs as they are parsed.
7
+ #
8
+ # Previously this logic was handled in the parser's grammar, but with the introduction of variable binding the
9
+ # grammar is no longer context free so we need the parser to be a little looser on what it accepts and validate it
10
+ # here. Having this layer is also nice because we can provide better error messages to the user for what went
11
+ # wrong during the parse.
12
+ # rubocop:disable Metrics/ClassLength
13
+ class ModifierValidator
14
+ extend ::T::Sig
15
+
16
+ sig { returns(::CSVPlusPlus::Modifier::Modifier) }
17
+ attr_reader :modifier
18
+
19
+ sig { params(modifier: ::CSVPlusPlus::Modifier::Modifier).void }
20
+ # @param modifier [Modifier::Modifier] The modifier to set the validated attributes on.
21
+ def initialize(modifier)
22
+ @modifier = modifier
23
+ end
24
+
25
+ sig { params(border_side: ::String).void }
26
+ # Validates that +border_side+ is 'all', 'top', 'bottom', 'left' or 'right'.
27
+ #
28
+ # @param border_side [::String, Modifier::BorderSide] The unvalidated user input
29
+ #
30
+ # @return [Set<Modifier::BorderSide>]
31
+ def border=(border_side)
32
+ @modifier.border = ::T.cast(
33
+ one_of(:border, border_side, ::CSVPlusPlus::Modifier::BorderSide),
34
+ ::CSVPlusPlus::Modifier::BorderSide
35
+ )
36
+ end
37
+
38
+ sig { params(border_color: ::String).void }
39
+ # Validates that +border_color+ is a hex color.
40
+ #
41
+ # @param border_color [::String] The unvalidated user input
42
+ def bordercolor=(border_color)
43
+ @modifier.bordercolor = color_value(:bordercolor, border_color)
44
+ end
45
+
46
+ sig { params(border_style: ::String).void }
47
+ # Validates that +borderstyle+ is 'dashed', 'dotted', 'double', 'solid', 'solid_medium' or 'solid_thick'.
48
+ #
49
+ # @param border_style [::String] The unvalidated user input
50
+ def borderstyle=(border_style)
51
+ @modifier.borderstyle = ::T.cast(
52
+ one_of(:borderstyle, border_style, ::CSVPlusPlus::Modifier::BorderStyle),
53
+ ::CSVPlusPlus::Modifier::BorderStyle
54
+ )
55
+ end
56
+
57
+ sig { params(color: ::String).void }
58
+ # Validates that +color+ is a hex color.
59
+ #
60
+ # @param color [::String] The unvalidated user input
61
+ def color=(color)
62
+ @modifier.color = color_value(:color, color)
63
+ end
64
+
65
+ sig { params(repetitions: ::String).void }
66
+ # Validates that +repetitions+ is a positive integer.
67
+ #
68
+ # @param repetitions [String] The unvalidated user input
69
+ def expand=(repetitions)
70
+ @modifier.expand = ::CSVPlusPlus::Modifier::Expand.new(repetitions: positive_integer(:expand, repetitions))
71
+ end
72
+
73
+ sig { params(font_color: ::String).void }
74
+ # Validates that +font_color+ is a hex color.
75
+ #
76
+ # @param font_color [::String] The unvalidated user input
77
+ def fontcolor=(font_color)
78
+ @modifier.fontcolor = color_value(:fontcolor, font_color)
79
+ end
80
+
81
+ sig { params(font_family: ::String).void }
82
+ # Validates that +font_family+ is a string that looks like a valid font family. There's only so much validation
83
+ # we can do here
84
+ #
85
+ # @param font_family [::String] The unvalidated user input
86
+ def fontfamily=(font_family)
87
+ @modifier.fontfamily = matches_regexp(
88
+ :fontfamily,
89
+ ::CSVPlusPlus::Lexer.unquote(font_family),
90
+ /^[\w\s]+$/,
91
+ 'It is not a valid font family.'
92
+ )
93
+ end
94
+
95
+ sig { params(font_size: ::String).void }
96
+ # Validates that +font_size+ is a positive integer
97
+ #
98
+ # @param font_size [::String] The unvalidated user input
99
+ def fontsize=(font_size)
100
+ @modifier.fontsize = positive_integer(:fontsize, font_size)
101
+ end
102
+
103
+ sig { params(text_format: ::String).void }
104
+ # Validates that +text_format+ is 'bold', 'italic', 'strikethrough' or 'underline'.
105
+ #
106
+ # @param text_format [::String] The unvalidated user input
107
+ def format=(text_format)
108
+ @modifier.format = ::T.cast(
109
+ one_of(:format, text_format, ::CSVPlusPlus::Modifier::TextFormat),
110
+ ::CSVPlusPlus::Modifier::TextFormat
111
+ )
112
+ end
113
+
114
+ sig { void }
115
+ # Sets the row or cell to be frozen
116
+ def freeze!
117
+ @modifier.freeze!
118
+ end
119
+
120
+ sig { void }
121
+ # Sets an infinite +Expand+ on the +Modifier+.
122
+ def infinite_expand!
123
+ @modifier.infinite_expand!
124
+ end
125
+
126
+ sig { params(halign: ::String).void }
127
+ # Validates that +halign+ is a string representation of +Modifier::HorizontalAlign+
128
+ #
129
+ # @param halign [::String] The unvalidated user input
130
+ def halign=(halign)
131
+ @modifier.halign = ::T.cast(
132
+ one_of(:halign, halign, ::CSVPlusPlus::Modifier::HorizontalAlign),
133
+ ::CSVPlusPlus::Modifier::HorizontalAlign
134
+ )
135
+ end
136
+
137
+ sig { params(note: ::String).void }
138
+ # Validates that +note+ is a quoted string.
139
+ #
140
+ # @param note [::String] The unvalidated user input
141
+ def note=(note)
142
+ @modifier.note = note
143
+ end
144
+
145
+ sig { params(number_format: ::String).void }
146
+ # Validates that +number_format+ is a string version of a +Modifier::NumberFormat+
147
+ #
148
+ # @param number_format [::String] The unvalidated user input
149
+ def numberformat=(number_format)
150
+ @modifier.numberformat = ::T.cast(
151
+ one_of(:numberformat, number_format, ::CSVPlusPlus::Modifier::NumberFormat),
152
+ ::CSVPlusPlus::Modifier::NumberFormat
153
+ )
154
+ end
155
+
156
+ sig { params(valign: ::String).void }
157
+ # Validates that +valign+ is a string representation of +Modifier::VerticalAlign+
158
+ #
159
+ # @param valign [::String] The unvalidated user input
160
+ def valign=(valign)
161
+ @modifier.valign = ::T.cast(
162
+ one_of(:valign, valign, ::CSVPlusPlus::Modifier::VerticalAlign),
163
+ ::CSVPlusPlus::Modifier::VerticalAlign
164
+ )
165
+ end
166
+
167
+ sig { params(rule: ::String).void }
168
+ # Validates that the conditional validating rules are well-formed.
169
+ #
170
+ # Pretty much based off of the Google Sheets API spec here:
171
+ # @see https://developers.google.com/sheets/api/samples/data#apply_data_validation_to_a_range
172
+ #
173
+ # @param rule [::String] The validation rule to apply to this row or cell
174
+ def validate=(rule)
175
+ @modifier.validate = a_data_validation(:validate, rule)
176
+ end
177
+
178
+ sig { params(var: ::String).void }
179
+ # Validates +var+ is a valid variable identifier.
180
+ #
181
+ # @param var [::String] The unvalidated user input
182
+ def var=(var)
183
+ # TODO: I need a shared definition of what a variable can be (I guess the :ID token)
184
+ @modifier.var = matches_regexp(:var, var, /^\w+$/, 'It must be a sequence of letters, numbers and _.').to_sym
185
+ end
186
+
187
+ private
188
+
189
+ sig { params(modifier: ::Symbol, value: ::String).returns(::CSVPlusPlus::Modifier::DataValidation) }
190
+ def a_data_validation(modifier, value)
191
+ data_validation = ::CSVPlusPlus::Modifier::DataValidation.new(value)
192
+ return data_validation if data_validation.valid?
193
+
194
+ raise_error(modifier, value, message: data_validation.invalid_reason)
195
+ end
196
+
197
+ sig { params(modifier: ::Symbol, value: ::String).returns(::CSVPlusPlus::Color) }
198
+ def color_value(modifier, value)
199
+ unless ::CSVPlusPlus::Color.valid_hex_string?(value)
200
+ raise_error(modifier, value, message: 'It must be a 3 or 6 digit hex code.')
201
+ end
202
+
203
+ ::CSVPlusPlus::Color.new(value)
204
+ end
205
+
206
+ sig { params(modifier: ::Symbol, value: ::String, regexp: ::Regexp, message: ::String).returns(::String) }
207
+ def matches_regexp(modifier, value, regexp, message)
208
+ raise_error(modifier, value, message:) unless value =~ regexp
209
+ value
210
+ end
211
+
212
+ sig { params(modifier: ::Symbol, value: ::String, choices: ::T.class_of(::T::Enum)).returns(::T::Enum) }
213
+ def one_of(modifier, value, choices)
214
+ choices.deserialize(value.downcase.gsub('_', ''))
215
+ rescue ::KeyError
216
+ raise_error(modifier, value, choices:)
217
+ end
218
+
219
+ sig { params(modifier: ::Symbol, value: ::String).returns(::Integer) }
220
+ def positive_integer(modifier, value)
221
+ Integer(value.to_s, 10).tap do |i|
222
+ raise_error(modifier, value, message: 'It must be positive and greater than 0.') unless i.positive?
223
+ end
224
+ rescue ::ArgumentError
225
+ raise_error(modifier, value, message: 'It must be a valid (whole) number.')
226
+ end
227
+
228
+ sig do
229
+ type_parameters(:E)
230
+ .params(
231
+ modifier: ::Symbol,
232
+ bad_input: ::String,
233
+ choices: ::T.nilable(::T.all(::T.type_parameter(:E), ::T.class_of(::T::Enum))),
234
+ message: ::T.nilable(::String)
235
+ ).returns(::T.noreturn)
236
+ end
237
+ def raise_error(modifier, bad_input, choices: nil, message: nil)
238
+ raise(::CSVPlusPlus::Error::ModifierValidationError.new(modifier, bad_input:, choices:, message:))
239
+ end
240
+ end
241
+ # rubocop:enable Metrics/ClassLength
242
+ end
243
+ end
@@ -0,0 +1,84 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Modifier
6
+ # Build a RubyXL-decorated Modifier class adds some support for Excel
7
+ class RubyXLModifier < ::CSVPlusPlus::Modifier::Modifier
8
+ extend ::T::Sig
9
+
10
+ # @see https://www.rubydoc.info/gems/rubyXL/RubyXL/NumberFormats
11
+ # @see https://support.microsoft.com/en-us/office/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68
12
+ NUM_FMT_IDS = ::T.let(
13
+ {
14
+ ::CSVPlusPlus::Modifier::NumberFormat::Currency => 5,
15
+ ::CSVPlusPlus::Modifier::NumberFormat::Date => 14,
16
+ ::CSVPlusPlus::Modifier::NumberFormat::DateTime => 22,
17
+ ::CSVPlusPlus::Modifier::NumberFormat::Number => 1,
18
+ ::CSVPlusPlus::Modifier::NumberFormat::Percent => 9,
19
+ ::CSVPlusPlus::Modifier::NumberFormat::Text => 49,
20
+ ::CSVPlusPlus::Modifier::NumberFormat::Time => 21,
21
+ ::CSVPlusPlus::Modifier::NumberFormat::Scientific => 48
22
+ }.freeze,
23
+ ::T::Hash[::CSVPlusPlus::Modifier::NumberFormat, ::Integer]
24
+ )
25
+ private_constant :NUM_FMT_IDS
26
+
27
+ # @see http://www.datypic.com/sc/ooxml/t-ssml_ST_BorderStyle.html
28
+ # ST_BorderStyle = %w{ none thin medium dashed dotted thick double hair mediumDashed dashDot mediumDashDot
29
+ # dashDotDot slantDashDot }
30
+ BORDER_STYLES = ::T.let(
31
+ {
32
+ ::CSVPlusPlus::Modifier::BorderStyle::Dashed => 'dashed',
33
+ ::CSVPlusPlus::Modifier::BorderStyle::Dotted => 'dotted',
34
+ ::CSVPlusPlus::Modifier::BorderStyle::Double => 'double',
35
+ ::CSVPlusPlus::Modifier::BorderStyle::Solid => 'thin',
36
+ ::CSVPlusPlus::Modifier::BorderStyle::SolidMedium => 'medium',
37
+ ::CSVPlusPlus::Modifier::BorderStyle::SolidThick => 'thick'
38
+ }.freeze,
39
+ ::T::Hash[::CSVPlusPlus::Modifier::BorderStyle, ::String]
40
+ )
41
+ private_constant :BORDER_STYLES
42
+
43
+ sig { returns(::T.nilable(::String)) }
44
+ # The excel-specific border weight
45
+ #
46
+ # @return [::String, nil]
47
+ def border_weight
48
+ # rubocop:disable Lint/ConstantResolution
49
+ BORDER_STYLES[borderstyle]
50
+ # rubocop:enable Lint/ConstantResolution
51
+ end
52
+
53
+ sig { returns(::T.nilable(::String)) }
54
+ # The horizontal alignment, formatted for the RubyXL API
55
+ #
56
+ # @return [::String, nil]
57
+ def horizontal_alignment
58
+ @halign&.serialize
59
+ end
60
+
61
+ sig { returns(::T.nilable(::String)) }
62
+ # The excel-specific number format code
63
+ #
64
+ # @return [::String]
65
+ def number_format_code
66
+ return unless @numberformat
67
+
68
+ ::RubyXL::NumberFormats::DEFAULT_NUMBER_FORMATS.find_by_format_id(
69
+ # rubocop:disable Lint/ConstantResolution
70
+ NUM_FMT_IDS[@numberformat]
71
+ # rubocop:enable Lint/ConstantResolution
72
+ ).format_code
73
+ end
74
+
75
+ sig { returns(::T.nilable(::String)) }
76
+ # The vertical alignment, formatted for the RubyXL API
77
+ #
78
+ # @return [::String, nil]
79
+ def vertical_alignment
80
+ @valign&.serialize
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,179 +1,103 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- module CSVPlusPlus
4
- # A container representing the operations that can be applied to a cell or row
5
- #
6
- # @attr bordercolor [Color]
7
- # @attr borders [Array<Symbol>] The borders that will be set
8
- # @attr color [Color] The background color of the cell
9
- # @attr expand [Expand] Whether this row expands into multiple rows
10
- # @attr fontcolor [Color] The font color of the cell
11
- # @attr fontfamily [::String] The font family
12
- # @attr fontsize [Integer] The font size
13
- # @attr halign [:left, :center, :right] Horizontal alignment
14
- # @attr note [::String] A note/comment on the cell
15
- # @attr numberformat [Symbol] A number format to apply to the value in the cell
16
- # @attr row_level [boolean] Is this a row modifier? If so it's values will apply to all cells in the row
17
- # (unless overridden by the cell modifier)
18
- # @attr validation [Object]
19
- # @attr valign [:top, :center, :bottom] Vertical alignment
20
- # @attr var [Symbol] The variable bound to this cell
21
- #
22
- # @attr_writer borderstyle [:hashed, :dotted, :double, :solid, :solid_medium, :solid_thick]
23
- # The style of border on the cell
24
- #
25
- # @attr_reader borders [Array<Symbol>]
26
- # @attr_reader formats [Array<Symbol>] Bold/italics/underline/strikethrough formatting
27
- class Modifier
28
- attr_accessor :bordercolor,
29
- :color,
30
- :expand,
31
- :fontcolor,
32
- :fontfamily,
33
- :fontsize,
34
- :halign,
35
- :valign,
36
- :note,
37
- :numberformat,
38
- :row_level,
39
- :validation,
40
- :var
41
- attr_reader :borders, :formats
42
- attr_writer :borderstyle
43
-
44
- # When instantiating a new object, extend it with our validation functionality.
45
- #
46
- # I'm not sure why I need to do it this way tbh, using +include ValidatedModifier+ at the
47
- # class level didn't seem to have access to the parent methods
48
- # def self.new(*args, **kwargs, &)
49
- # allocate.tap do |i|
50
- # i.__send__(:initialize, *args, **kwargs, &)
51
- # i.extend(::CSVPlusPlus::ValidatedModifier)
52
- # end
53
- # end
54
-
55
- # @param row_level [Boolean] Whether or not this modifier applies to the entire row
56
- def initialize(row_level: false)
57
- @row_level = row_level
58
- @freeze = false
59
- @borders = ::Set.new
60
- @formats = ::Set.new
61
- end
62
-
63
- # Are there any borders set?
64
- #
65
- # @return [Boolean]
66
- def any_border?
67
- !@borders.empty?
68
- end
69
-
70
- # Style of border
71
- #
72
- # @return [Symbol]
73
- def borderstyle
74
- @borderstyle || :solid
75
- end
76
-
77
- # Is this a cell-level modifier?
78
- #
79
- # @return [Boolean]
80
- def cell_level?
81
- !@row_level
82
- end
83
-
84
- # Assign a border
85
- #
86
- # @param side [:top, :left, :bottom, :right, :all]
87
- def border=(side)
88
- @borders << side
89
- end
90
-
91
- # Does this have a border along +side+?
92
- #
93
- # @param side [:top, :left, :bottom, :right, :all]
94
- #
95
- # @return [boolean]
96
- def border_along?(side)
97
- @borders.include?(:all) || @borders.include?(side)
98
- end
99
-
100
- # Does this have a border along all sides?
101
- #
102
- # @return [boolean]
103
- def border_all?
104
- @borders.include?(:all) \
105
- || (border_along?(:top) && border_along?(:bottom) && border_along?(:left) && border_along?(:right))
106
- end
4
+ require_relative './modifier/conditional_formatting'
5
+ require_relative './modifier/data_validation'
6
+ require_relative './modifier/expand'
7
+ require_relative './modifier/modifier'
107
8
 
108
- # Set this modifier to expand infinitely
109
- #
110
- # @return [::Expand]
111
- def expand!
112
- @expand = ::CSVPlusPlus::Expand.new if row_level?
9
+ module CSVPlusPlus
10
+ # All modifier-specific logic is hidden in this module and callers should just call +#new+ on this module.
11
+ module Modifier
12
+ extend ::T::Sig
13
+
14
+ # The sides that a border can be on
15
+ class BorderSide < ::T::Enum
16
+ enums do
17
+ All = new
18
+ Top = new
19
+ Bottom = new
20
+ Left = new
21
+ Right = new
22
+ end
113
23
  end
114
24
 
115
- # Set a text format (bolid, italic, underline or strikethrough)
116
- #
117
- # @param value [:bold, :italic, :underline, :strikethrough]
118
- def format=(value)
119
- @formats << value
25
+ # The various border styles
26
+ class BorderStyle < ::T::Enum
27
+ enums do
28
+ Dashed = new
29
+ Dotted = new
30
+ Double = new
31
+ Solid = new
32
+ SolidMedium = new
33
+ SolidThick = new
34
+ end
120
35
  end
121
36
 
122
- # Is the given format set?
123
- #
124
- # @param type [:bold, :italic, :underline, :strikethrough]
125
- #
126
- # @return [Boolean]
127
- def formatted?(type)
128
- @formats.include?(type)
37
+ # The possible values for a horizontal alignment
38
+ class HorizontalAlign < ::T::Enum
39
+ enums do
40
+ Left = new
41
+ Right = new
42
+ Center = new
43
+ end
129
44
  end
130
45
 
131
- # Freeze the row from edits
132
- #
133
- # @return [true]
134
- def freeze!
135
- @frozen = true
46
+ # The allowed number formats
47
+ class NumberFormat < ::T::Enum
48
+ enums do
49
+ Currency = new
50
+ Date = new
51
+ DateTime = new
52
+ Number = new
53
+ Percent = new
54
+ Text = new
55
+ Time = new
56
+ Scientific = new
57
+ end
136
58
  end
137
59
 
138
- # Is the row frozen?
139
- #
140
- # @return [boolean]
141
- def frozen?
142
- @frozen
60
+ # The types of formats that can be applied to text.
61
+ class TextFormat < ::T::Enum
62
+ enums do
63
+ Bold = new
64
+ Italic = new
65
+ Strikethrough = new
66
+ Underline = new
67
+ end
143
68
  end
144
69
 
145
- # Mark this modifer as row-level
146
- #
147
- # @return [true]
148
- def row_level!
149
- @row_level = true
70
+ # The possible values for a horizontal alignment
71
+ class VerticalAlign < ::T::Enum
72
+ enums do
73
+ Top = new
74
+ Bottom = new
75
+ Center = new
76
+ end
150
77
  end
151
78
 
152
- # Is this a row-level modifier?
79
+ sig { params(options: ::CSVPlusPlus::Options, row_level: ::T::Boolean).returns(::CSVPlusPlus::Modifier::Modifier) }
80
+ # Return a +Modifier+ with the proper validation and helper functions attached for the given output
153
81
  #
154
- # @return [boolean]
155
- def row_level?
156
- @row_level
157
- end
158
-
159
- # @return [::String]
160
- def to_s
161
- # TODO... I dunno, not sure how to manage this
162
- "Modifier(row_level: #{@row_level} halign: #{@halign} valign: #{@valign} format: #{@formats} " \
163
- "font_size: #{@font_size})"
164
- end
165
-
166
- # Create a new modifier instance, with all values defaulted from +other+
82
+ # @param options [boolean] is this a row level modifier? (otherwise cell-level)
83
+ # @param row_level [boolean] is this a row level modifier? (otherwise cell-level)
167
84
  #
168
- # @param other [Modifier]
169
- def take_defaults_from!(other)
170
- other.instance_variables.each do |property|
171
- # don't propagate row-specific values
172
- next if property == :@row_level
173
-
174
- value = other.instance_variable_get(property)
175
- instance_variable_set(property, value.clone)
85
+ # @return [ValidatedModifier]
86
+ def self.new(options, row_level: false)
87
+ output_format = options.output_format
88
+ case output_format
89
+ when ::CSVPlusPlus::Options::OutputFormat::CSV, ::CSVPlusPlus::Options::OutputFormat::OpenDocument
90
+ ::CSVPlusPlus::Modifier::Modifier.new(row_level:)
91
+ when ::CSVPlusPlus::Options::OutputFormat::Excel
92
+ ::CSVPlusPlus::Modifier::RubyXLModifier.new(row_level:)
93
+ when ::CSVPlusPlus::Options::OutputFormat::GoogleSheets
94
+ ::CSVPlusPlus::Modifier::GoogleSheetModifier.new(row_level:)
95
+ else ::T.absurd(output_format)
176
96
  end
177
97
  end
178
98
  end
179
99
  end
100
+
101
+ require_relative './modifier/google_sheet_modifier'
102
+ require_relative './modifier/modifier_validator'
103
+ require_relative './modifier/rubyxl_modifier'