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.
- 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'
|