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.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +23 -13
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +82 -60
- data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
- data/lib/csv_plus_plus/entities/boolean.rb +18 -9
- data/lib/csv_plus_plus/entities/builtins.rb +23 -9
- data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
- data/lib/csv_plus_plus/entities/date.rb +38 -5
- data/lib/csv_plus_plus/entities/entity.rb +27 -61
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +23 -11
- data/lib/csv_plus_plus/entities/function_call.rb +24 -9
- data/lib/csv_plus_plus/entities/number.rb +24 -10
- data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
- data/lib/csv_plus_plus/entities/string.rb +19 -6
- data/lib/csv_plus_plus/entities/variable.rb +16 -4
- data/lib/csv_plus_plus/entities.rb +20 -13
- data/lib/csv_plus_plus/error/error.rb +11 -1
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
- data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +1 -0
- data/lib/csv_plus_plus/google_api_client.rb +7 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
- data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
- data/lib/csv_plus_plus/lexer.rb +24 -0
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
- data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
- data/lib/csv_plus_plus/modifier/expand.rb +61 -0
- data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
- data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
- data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
- data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
- data/lib/csv_plus_plus/modifier.rb +82 -158
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
- data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
- data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
- data/lib/csv_plus_plus/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
- data/lib/csv_plus_plus/runtime/references.rb +110 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +62 -35
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
- data/lib/csv_plus_plus/writer/csv.rb +11 -9
- data/lib/csv_plus_plus/writer/excel.rb +9 -2
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
- data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
- data/lib/csv_plus_plus/writer/open_document.rb +6 -1
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +29 -12
- metadata +18 -14
- data/lib/csv_plus_plus/can_define_references.rb +0 -88
- data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
- data/lib/csv_plus_plus/data_validation.rb +0 -138
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/references.rb +0 -68
- data/lib/csv_plus_plus/scope.rb +0 -196
- data/lib/csv_plus_plus/validated_modifier.rb +0 -164
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- 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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
#
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
-
# @
|
155
|
-
|
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
|
-
# @
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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'
|