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,138 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Modifier
|
6
|
+
# A validation on a cell value. Used to support the `validate=` modifier directive. This is mostly based on the
|
7
|
+
# Google Sheets API spec:
|
8
|
+
#
|
9
|
+
# @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionType
|
10
|
+
#
|
11
|
+
# @attr_reader arguments [Array<::String>] The parsed arguments as required by the condition.
|
12
|
+
# @attr_reader condition [Symbol] The condition (:blank, :text_eq, :date_before, etc.)
|
13
|
+
# @attr_reader invalid_reason [::String, nil] If set, the reason why this modifier is not valid.
|
14
|
+
class DataValidation
|
15
|
+
attr_reader :arguments, :condition, :invalid_reason
|
16
|
+
|
17
|
+
# @param value [::String] The value to parse as a data validation
|
18
|
+
def initialize(value)
|
19
|
+
condition, args = value.split(/\s*:\s*/)
|
20
|
+
@arguments = ::CSVPlusPlus::Lexer.unquote(args || '').split(/\s+/)
|
21
|
+
@condition = condition.to_sym
|
22
|
+
|
23
|
+
validate!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Each data validation (represented by +@condition+) has their own requirements for which arguments are valid.
|
27
|
+
# If this object is invalid, you can see the reason in +@invalid_reason+.
|
28
|
+
#
|
29
|
+
# @return [boolean]
|
30
|
+
def valid?
|
31
|
+
@invalid_reason.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def invalid!(reason)
|
37
|
+
@invalid_reason = reason
|
38
|
+
end
|
39
|
+
|
40
|
+
def a_number(arg)
|
41
|
+
Float(arg)
|
42
|
+
rescue ::ArgumentError
|
43
|
+
invalid!("Requires a number but given: #{arg}")
|
44
|
+
end
|
45
|
+
|
46
|
+
def a1_notation(arg)
|
47
|
+
return arg if ::CSVPlusPlus::Entities::CellReference.valid_cell_reference?(arg)
|
48
|
+
end
|
49
|
+
|
50
|
+
def a_date(arg, allow_relative_date: false)
|
51
|
+
return arg if ::CSVPlusPlus::Entities::Date.valid_date?(arg)
|
52
|
+
|
53
|
+
if allow_relative_date
|
54
|
+
a_relative_date(arg)
|
55
|
+
else
|
56
|
+
invalid!("Requires a date but given: #{arg}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def a_relative_date(arg)
|
61
|
+
return arg if %w[past_month past_week past_year yesterday today tomorrow].include?(arg.downcase)
|
62
|
+
|
63
|
+
invalid!('Requires a relative date: past_month, past_week, past_year, yesterday, today or tomorrow')
|
64
|
+
end
|
65
|
+
|
66
|
+
def no_args
|
67
|
+
return if @arguments.empty?
|
68
|
+
|
69
|
+
invalid!("Requires no arguments but #{@arguments.length} given: #{@arguments}")
|
70
|
+
end
|
71
|
+
|
72
|
+
def one_arg
|
73
|
+
return @arguments[0] if @arguments.length == 1
|
74
|
+
|
75
|
+
invalid!("Requires only one argument but #{@arguments.length} given: #{@arguments}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def one_arg_or_more
|
79
|
+
return @arguments if @arguments.length.positive?
|
80
|
+
|
81
|
+
invalid!("Requires at least one argument but #{@arguments.length} given: #{@arguments}")
|
82
|
+
end
|
83
|
+
|
84
|
+
def two_dates
|
85
|
+
return @arguments if @arguments.length == 2 && a_date(@arguments[0]) && a_date(@arguments[1])
|
86
|
+
|
87
|
+
invalid!("Requires exactly two dates but given: #{@arguments}")
|
88
|
+
end
|
89
|
+
|
90
|
+
def two_numbers
|
91
|
+
return @arguments if @arguments.length == 2 && a_number(@arguments[0]) && a_number(@arguments[1])
|
92
|
+
|
93
|
+
invalid!("Requires exactly two numbers but given: #{@arguments}")
|
94
|
+
end
|
95
|
+
|
96
|
+
# validate_boolean is a weird one because it can have 0, 1 or 2 @arguments - all of them must be (true | false)
|
97
|
+
def validate_boolean
|
98
|
+
return @arguments if @arguments.empty?
|
99
|
+
|
100
|
+
converted_args = @arguments.map(&:strip).map(&:downcase)
|
101
|
+
return @arguments if [1, 2].include?(@arguments.length) && converted_args.all? do |arg|
|
102
|
+
%w[true false].include?(arg)
|
103
|
+
end
|
104
|
+
|
105
|
+
invalid!("Requires 0, 1 or 2 arguments and they all must be either 'true' or 'false'. Received: #{arguments}")
|
106
|
+
end
|
107
|
+
|
108
|
+
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
109
|
+
def validate!
|
110
|
+
case condition.to_sym
|
111
|
+
when :blank, :date_is_valid, :not_blank, :text_is_email, :text_is_url
|
112
|
+
no_args
|
113
|
+
when :text_contains, :text_ends_with, :text_eq, :text_not_contains, :text_starts_with
|
114
|
+
one_arg
|
115
|
+
when :date_after, :date_before, :date_on_or_after, :date_on_or_before
|
116
|
+
a_date(one_arg, allow_relative_date: true)
|
117
|
+
when :date_eq, :date_not_eq
|
118
|
+
a_date(one_arg)
|
119
|
+
when :date_between, :date_not_between
|
120
|
+
two_dates
|
121
|
+
when :one_of_range
|
122
|
+
a1_notation(one_arg)
|
123
|
+
when :custom_formula, :one_of_list, :text_not_eq
|
124
|
+
one_arg_or_more
|
125
|
+
when :number_eq, :number_greater, :number_greater_than_eq, :number_less, :number_less_than_eq, :number_not_eq
|
126
|
+
a_number(one_arg)
|
127
|
+
when :number_between, :number_not_between
|
128
|
+
two_numbers
|
129
|
+
when :boolean
|
130
|
+
validate_boolean
|
131
|
+
else
|
132
|
+
invalid!('Not a recognized data validation directive')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Modifier
|
6
|
+
# The logic for how a row can expand
|
7
|
+
#
|
8
|
+
# @attr_reader ends_at [Integer, nil] Once the row has been expanded, where it ends at.
|
9
|
+
# @attr_reader repetitions [Integer, nil] How many times the row repeats/expands.
|
10
|
+
# @attr_reader starts_at [Integer, nil] Once the row has been expanded, where it starts at.
|
11
|
+
class Expand
|
12
|
+
extend ::T::Sig
|
13
|
+
|
14
|
+
sig { returns(::T.nilable(::Integer)) }
|
15
|
+
attr_reader :ends_at
|
16
|
+
|
17
|
+
sig { returns(::T.nilable(::Integer)) }
|
18
|
+
attr_reader :repetitions
|
19
|
+
|
20
|
+
sig { returns(::T.nilable(::Integer)) }
|
21
|
+
attr_reader :starts_at
|
22
|
+
|
23
|
+
sig { params(repetitions: ::T.nilable(::Integer), starts_at: ::T.nilable(::Integer)).void }
|
24
|
+
# @param repetitions [Integer, nil] How many times this expand repeats. If it's +nil+ it will expand infinitely
|
25
|
+
# (for the rest of the worksheet.)
|
26
|
+
# @param starts_at [Integer, nil] The final location where the +Expand+ will start. It's important to note that
|
27
|
+
# this can't be derived until all rows are expanded, because each expand modifier will push down the ones below
|
28
|
+
# it. So typically this param will not be passed in the initializer but instead set later.
|
29
|
+
def initialize(repetitions: nil, starts_at: nil)
|
30
|
+
@repetitions = ::T.let(repetitions, ::T.nilable(::Integer))
|
31
|
+
@starts_at = ::T.let(starts_at, ::T.nilable(::Integer)) unless starts_at.nil?
|
32
|
+
@ends_at = ::T.let(nil, ::T.nilable(::Integer))
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { returns(::T::Boolean) }
|
36
|
+
# Has the row been expanded?
|
37
|
+
#
|
38
|
+
# @return [boolean]
|
39
|
+
def expanded?
|
40
|
+
!@starts_at.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { returns(::T::Boolean) }
|
44
|
+
# Does this infinitely expand?
|
45
|
+
#
|
46
|
+
# @return [boolean]
|
47
|
+
def infinite?
|
48
|
+
repetitions.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { params(row_index: ::Integer).void }
|
52
|
+
# Mark the start of the row once it's been expanded, as well as where it +ends_at+. When expanding rows each one
|
53
|
+
# adds rows to the worksheet and if there are multiple `expand` modifiers in the worksheet, we don't know the
|
54
|
+
# final +row_index+ until we're in the phase of expanding all the rows out.
|
55
|
+
def starts_at=(row_index)
|
56
|
+
@starts_at = row_index
|
57
|
+
@ends_at = row_index + @repetitions unless @repetitions.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Modifier
|
6
|
+
# Decorate a +Modifier+ so it is more compatible with the Google Sheets API
|
7
|
+
class GoogleSheetModifier < ::CSVPlusPlus::Modifier::Modifier
|
8
|
+
extend ::T::Sig
|
9
|
+
|
10
|
+
sig { returns(::T.nilable(::Google::Apis::SheetsV4::Color)) }
|
11
|
+
# Format the color for Google Sheets
|
12
|
+
#
|
13
|
+
# @return [Google::Apis::SheetsV4::Color]
|
14
|
+
def background_color
|
15
|
+
google_sheets_color(@color) if @color
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { returns(::T.nilable(::Google::Apis::SheetsV4::Border)) }
|
19
|
+
# Format the border for Google Sheets
|
20
|
+
#
|
21
|
+
# @return [Google::Apis::SheetsV4::Border]
|
22
|
+
def border
|
23
|
+
return unless any_border?
|
24
|
+
|
25
|
+
# TODO: allow different border styles per side?
|
26
|
+
::Google::Apis::SheetsV4::Border.new(
|
27
|
+
color: google_sheets_color(bordercolor || ::CSVPlusPlus::Color.new('#000000')),
|
28
|
+
style: border_style
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { returns(::T.nilable(::Google::Apis::SheetsV4::Color)) }
|
33
|
+
# Format the fontcolor for Google Sheets
|
34
|
+
#
|
35
|
+
# @return [Google::Apis::SheetsV4::Color]
|
36
|
+
def font_color
|
37
|
+
google_sheets_color(@fontcolor) if @fontcolor
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { returns(::T.nilable(::String)) }
|
41
|
+
# Format the halign for Google Sheets
|
42
|
+
#
|
43
|
+
# @return [::String]
|
44
|
+
def horizontal_alignment
|
45
|
+
halign&.serialize&.upcase
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { returns(::T.nilable(::Google::Apis::SheetsV4::NumberFormat)) }
|
49
|
+
# Format the numberformat for Google Sheets
|
50
|
+
#
|
51
|
+
#
|
52
|
+
# @return [Google::Apis::SheetsV4::NumberFormat]
|
53
|
+
def number_format
|
54
|
+
::Google::Apis::SheetsV4::NumberFormat.new(type: number_format_type(@numberformat)) if @numberformat
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(::Google::Apis::SheetsV4::TextFormat) }
|
58
|
+
# Builds a SheetsV4::TextFormat with the underlying Modifier
|
59
|
+
#
|
60
|
+
# @return [Google::Apis::SheetsV4::TextFormat]
|
61
|
+
def text_format
|
62
|
+
::Google::Apis::SheetsV4::TextFormat.new(
|
63
|
+
bold: formatted?(::CSVPlusPlus::Modifier::TextFormat::Bold) || nil,
|
64
|
+
italic: formatted?(::CSVPlusPlus::Modifier::TextFormat::Italic) || nil,
|
65
|
+
strikethrough: formatted?(::CSVPlusPlus::Modifier::TextFormat::Strikethrough) || nil,
|
66
|
+
underline: formatted?(::CSVPlusPlus::Modifier::TextFormat::Underline) || nil,
|
67
|
+
font_family: fontfamily,
|
68
|
+
font_size: fontsize,
|
69
|
+
foreground_color: font_color
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { returns(::T.nilable(::String)) }
|
74
|
+
# Format the valign for Google Sheets
|
75
|
+
def vertical_alignment
|
76
|
+
valign&.serialize&.upcase
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
sig { returns(::T.nilable(::String)) }
|
82
|
+
# Format the border style for Google Sheets
|
83
|
+
#
|
84
|
+
# @see https://developers.google.com/apps-script/reference/spreadsheet/border-style
|
85
|
+
#
|
86
|
+
# @return [::String, nil]
|
87
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
88
|
+
def border_style
|
89
|
+
return 'SOLID' unless @borderstyle
|
90
|
+
|
91
|
+
case @borderstyle
|
92
|
+
when ::CSVPlusPlus::Modifier::BorderStyle::Dashed then 'DASHED'
|
93
|
+
when ::CSVPlusPlus::Modifier::BorderStyle::Dotted then 'DOTTED'
|
94
|
+
when ::CSVPlusPlus::Modifier::BorderStyle::Double then 'DOUBLE'
|
95
|
+
when ::CSVPlusPlus::Modifier::BorderStyle::Solid then 'SOLID'
|
96
|
+
when ::CSVPlusPlus::Modifier::BorderStyle::SolidMedium then 'SOLID_MEDIUM'
|
97
|
+
when ::CSVPlusPlus::Modifier::BorderStyle::SolidThick then 'SOLID_THICK'
|
98
|
+
else ::T.absurd(@borderstyle)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
102
|
+
|
103
|
+
sig { params(color: ::CSVPlusPlus::Color).returns(::Google::Apis::SheetsV4::Color) }
|
104
|
+
def google_sheets_color(color)
|
105
|
+
::Google::Apis::SheetsV4::Color.new(
|
106
|
+
red: color.red_percent,
|
107
|
+
green: color.green_percent,
|
108
|
+
blue: color.blue_percent
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
sig { params(numberformat: ::CSVPlusPlus::Modifier::NumberFormat).returns(::String) }
|
113
|
+
# @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#NumberFormat
|
114
|
+
#
|
115
|
+
# @return [::String]
|
116
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
117
|
+
def number_format_type(numberformat)
|
118
|
+
case numberformat
|
119
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Currency then 'CURRENCY'
|
120
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Date then 'DATE'
|
121
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::DateTime then 'DATE_TIME'
|
122
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Number then 'NUMBER'
|
123
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Percent then 'PERCENT'
|
124
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Text then 'TEXT'
|
125
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Time then 'TIME'
|
126
|
+
when ::CSVPlusPlus::Modifier::NumberFormat::Scientific then 'SCIENTIFIC'
|
127
|
+
else ::T.absurd(numberformat)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Modifier
|
6
|
+
# A container representing the operations that can be applied to a cell or row
|
7
|
+
#
|
8
|
+
# @attr bordercolor [Color, nil]
|
9
|
+
# @attr color [Color, nil] The background color of the cell
|
10
|
+
# @attr expand [Modifier::Expand, nil] Whether this row expands into multiple rows
|
11
|
+
# @attr fontcolor [Color, nil] The font color of the cell
|
12
|
+
# @attr fontfamily [::String, nil] The font family
|
13
|
+
# @attr fontsize [Numeric, nil] The font size
|
14
|
+
# @attr halign [Modifier::HorizontalAlign, nil] Horizontal alignment
|
15
|
+
# @attr note [::String, nil] A note/comment on the cell
|
16
|
+
# @attr numberformat [Modifier::NumberFormat, nil] A number format to apply to the value in the cell
|
17
|
+
# @attr row_level [T::Boolean] Is this a row modifier? If so it's values will apply to all cells in the row
|
18
|
+
# (unless overridden by the cell modifier)
|
19
|
+
# @attr validate [Modifier::DataValidation, nil]
|
20
|
+
# @attr valign [Modifier::VerticalAlign, nil] Vertical alignment
|
21
|
+
# @attr var [Symbol, nil] The variable bound to this cell
|
22
|
+
#
|
23
|
+
# @attr_writer borderstyle [Modifier::BorderStyle] The style of border on the cell
|
24
|
+
#
|
25
|
+
# @attr_reader borders [Set<Modifier::BorderSide>] The sides of the cell where a border will be applied.
|
26
|
+
# @attr_reader formats [Set<Modifier::TextFormat>] Bold/italics/underline and strikethrough formatting.
|
27
|
+
#
|
28
|
+
# rubocop:disable Metrics/ClassLength
|
29
|
+
class Modifier
|
30
|
+
extend ::T::Sig
|
31
|
+
|
32
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Color)) }
|
33
|
+
attr_accessor :bordercolor
|
34
|
+
|
35
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Color)) }
|
36
|
+
attr_accessor :color
|
37
|
+
|
38
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::Expand)) }
|
39
|
+
attr_accessor :expand
|
40
|
+
|
41
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Color)) }
|
42
|
+
attr_accessor :fontcolor
|
43
|
+
|
44
|
+
sig { returns(::T.nilable(::String)) }
|
45
|
+
attr_accessor :fontfamily
|
46
|
+
|
47
|
+
sig { returns(::T.nilable(::Numeric)) }
|
48
|
+
attr_accessor :fontsize
|
49
|
+
|
50
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::HorizontalAlign)) }
|
51
|
+
attr_accessor :halign
|
52
|
+
|
53
|
+
sig { returns(::T.nilable(::String)) }
|
54
|
+
attr_accessor :note
|
55
|
+
|
56
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::NumberFormat)) }
|
57
|
+
attr_accessor :numberformat
|
58
|
+
|
59
|
+
sig { returns(::T::Boolean) }
|
60
|
+
attr_accessor :row_level
|
61
|
+
|
62
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::DataValidation)) }
|
63
|
+
attr_accessor :validate
|
64
|
+
|
65
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::VerticalAlign)) }
|
66
|
+
attr_accessor :valign
|
67
|
+
|
68
|
+
sig { returns(::T.nilable(::Symbol)) }
|
69
|
+
attr_accessor :var
|
70
|
+
|
71
|
+
sig { returns(::T::Set[::CSVPlusPlus::Modifier::BorderSide]) }
|
72
|
+
attr_reader :borders
|
73
|
+
|
74
|
+
sig { returns(::T::Set[::CSVPlusPlus::Modifier::TextFormat]) }
|
75
|
+
attr_reader :formats
|
76
|
+
|
77
|
+
sig do
|
78
|
+
params(borderstyle: ::CSVPlusPlus::Modifier::BorderStyle)
|
79
|
+
.returns(::T.nilable(::CSVPlusPlus::Modifier::BorderStyle))
|
80
|
+
end
|
81
|
+
attr_writer :borderstyle
|
82
|
+
|
83
|
+
sig { params(row_level: ::T::Boolean).void }
|
84
|
+
# @param row_level [Boolean] Whether or not this modifier applies to the entire row
|
85
|
+
def initialize(row_level: false)
|
86
|
+
@row_level = row_level
|
87
|
+
@frozen = ::T.let(false, ::T::Boolean)
|
88
|
+
@borders = ::T.let(::Set.new, ::T::Set[::CSVPlusPlus::Modifier::BorderSide])
|
89
|
+
@formats = ::T.let(::Set.new, ::T::Set[::CSVPlusPlus::Modifier::TextFormat])
|
90
|
+
end
|
91
|
+
|
92
|
+
sig { returns(::T::Boolean) }
|
93
|
+
# Are there any borders set?
|
94
|
+
#
|
95
|
+
# @return [::T::Boolean]
|
96
|
+
def any_border?
|
97
|
+
!@borders.empty?
|
98
|
+
end
|
99
|
+
|
100
|
+
sig { returns(::CSVPlusPlus::Modifier::BorderStyle) }
|
101
|
+
# Style of the border
|
102
|
+
#
|
103
|
+
# @return [::CSVPlusPlus::Modifier::BorderStyle]
|
104
|
+
def borderstyle
|
105
|
+
@borderstyle || ::CSVPlusPlus::Modifier::BorderStyle::Solid
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { returns(::T::Boolean) }
|
109
|
+
# Is this a cell-level modifier?
|
110
|
+
#
|
111
|
+
# @return [T::Boolean]
|
112
|
+
def cell_level?
|
113
|
+
!@row_level
|
114
|
+
end
|
115
|
+
|
116
|
+
sig { params(side: ::CSVPlusPlus::Modifier::BorderSide).returns(::T::Set[::CSVPlusPlus::Modifier::BorderSide]) }
|
117
|
+
# Put a border on the given +side+
|
118
|
+
#
|
119
|
+
# @param side [Modifier::BorderSide]
|
120
|
+
def border=(side)
|
121
|
+
@borders << side
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { params(side: ::CSVPlusPlus::Modifier::BorderSide).returns(::T::Boolean) }
|
125
|
+
# Does this have a border along +side+?
|
126
|
+
#
|
127
|
+
# @param side [Modifier::BorderSide]
|
128
|
+
#
|
129
|
+
# @return [T::Boolean]
|
130
|
+
def border_along?(side)
|
131
|
+
@borders.include?(::CSVPlusPlus::Modifier::BorderSide::All) || @borders.include?(side)
|
132
|
+
end
|
133
|
+
|
134
|
+
sig { returns(::T::Boolean) }
|
135
|
+
# Does this have a border along all sides?
|
136
|
+
#
|
137
|
+
# @return [T::Boolean]
|
138
|
+
def border_all?
|
139
|
+
@borders.include?(::CSVPlusPlus::Modifier::BorderSide::All) \
|
140
|
+
|| (border_along?(::CSVPlusPlus::Modifier::BorderSide::Top) \
|
141
|
+
&& border_along?(::CSVPlusPlus::Modifier::BorderSide::Bottom) \
|
142
|
+
&& border_along?(::CSVPlusPlus::Modifier::BorderSide::Left) \
|
143
|
+
&& border_along?(::CSVPlusPlus::Modifier::BorderSide::Right))
|
144
|
+
end
|
145
|
+
|
146
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::Expand)) }
|
147
|
+
# Set this modifier to expand infinitely
|
148
|
+
#
|
149
|
+
# @return [Expand, nil]
|
150
|
+
def infinite_expand!
|
151
|
+
@expand = ::CSVPlusPlus::Modifier::Expand.new if row_level?
|
152
|
+
end
|
153
|
+
|
154
|
+
sig { params(format: ::CSVPlusPlus::Modifier::TextFormat).returns(::T::Set[::CSVPlusPlus::Modifier::TextFormat]) }
|
155
|
+
# Set a text format (bolid, italic, underline or strikethrough)
|
156
|
+
#
|
157
|
+
# @param format [TextFormat]
|
158
|
+
def format=(format)
|
159
|
+
@formats << format
|
160
|
+
end
|
161
|
+
|
162
|
+
sig { params(format: ::CSVPlusPlus::Modifier::TextFormat).returns(::T::Boolean) }
|
163
|
+
# Is the given format set?
|
164
|
+
#
|
165
|
+
# @param format [TextFormat]
|
166
|
+
#
|
167
|
+
# @return [T::Boolean]
|
168
|
+
def formatted?(format)
|
169
|
+
@formats.include?(format)
|
170
|
+
end
|
171
|
+
|
172
|
+
sig { returns(::T::Boolean) }
|
173
|
+
# Freeze the row or cell from edits
|
174
|
+
#
|
175
|
+
# @return [true]
|
176
|
+
def freeze!
|
177
|
+
@frozen = true
|
178
|
+
end
|
179
|
+
|
180
|
+
sig { returns(::T::Boolean) }
|
181
|
+
# Is the cell or row frozen from edits?
|
182
|
+
#
|
183
|
+
# @return [T::Boolean]
|
184
|
+
def frozen?
|
185
|
+
@frozen
|
186
|
+
end
|
187
|
+
|
188
|
+
sig { returns(::T::Boolean) }
|
189
|
+
# Mark this modifer as row-level
|
190
|
+
#
|
191
|
+
# @return [true]
|
192
|
+
def row_level!
|
193
|
+
@row_level = true
|
194
|
+
end
|
195
|
+
|
196
|
+
sig { returns(::T::Boolean) }
|
197
|
+
# Is this a row-level modifier?
|
198
|
+
#
|
199
|
+
# @return [T::Boolean]
|
200
|
+
def row_level?
|
201
|
+
@row_level
|
202
|
+
end
|
203
|
+
|
204
|
+
sig { params(other: ::CSVPlusPlus::Modifier::Modifier).void }
|
205
|
+
# Create a new modifier instance, with all values defaulted from +other+
|
206
|
+
#
|
207
|
+
# @param other [Modifier]
|
208
|
+
#
|
209
|
+
# @return [Modifier]
|
210
|
+
def take_defaults_from!(other)
|
211
|
+
other.instance_variables.each do |property|
|
212
|
+
# don't propagate row-specific values
|
213
|
+
next if property == :@row_level
|
214
|
+
|
215
|
+
value = other.instance_variable_get(property)
|
216
|
+
instance_variable_set(property, value.clone)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
# rubocop:enable Metrics/ClassLength
|
221
|
+
end
|
222
|
+
end
|