csv_plus_plus 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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'