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,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
|