csv_plus_plus 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +23 -13
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +32 -7
  9. data/lib/csv_plus_plus/compiler.rb +82 -60
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
  11. data/lib/csv_plus_plus/entities/boolean.rb +18 -9
  12. data/lib/csv_plus_plus/entities/builtins.rb +23 -9
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
  14. data/lib/csv_plus_plus/entities/date.rb +38 -5
  15. data/lib/csv_plus_plus/entities/entity.rb +27 -61
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +23 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +24 -9
  19. data/lib/csv_plus_plus/entities/number.rb +24 -10
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
  21. data/lib/csv_plus_plus/entities/string.rb +19 -6
  22. data/lib/csv_plus_plus/entities/variable.rb +16 -4
  23. data/lib/csv_plus_plus/entities.rb +20 -13
  24. data/lib/csv_plus_plus/error/error.rb +11 -1
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
  28. data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
  29. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  30. data/lib/csv_plus_plus/error.rb +1 -0
  31. data/lib/csv_plus_plus/google_api_client.rb +7 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
  34. data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
  35. data/lib/csv_plus_plus/lexer.rb +24 -0
  36. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  37. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  38. data/lib/csv_plus_plus/modifier/expand.rb +61 -0
  39. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  40. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  41. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  42. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  43. data/lib/csv_plus_plus/modifier.rb +82 -158
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
  46. data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
  48. data/lib/csv_plus_plus/row.rb +53 -12
  49. data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
  50. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
  51. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  52. data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
  53. data/lib/csv_plus_plus/runtime/references.rb +110 -0
  54. data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
  55. data/lib/csv_plus_plus/runtime.rb +34 -191
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +62 -35
  58. data/lib/csv_plus_plus/version.rb +2 -1
  59. data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
  60. data/lib/csv_plus_plus/writer/csv.rb +11 -9
  61. data/lib/csv_plus_plus/writer/excel.rb +9 -2
  62. data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
  64. data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
  65. data/lib/csv_plus_plus/writer/open_document.rb +6 -1
  66. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +29 -12
  69. metadata +18 -14
  70. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  71. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  72. data/lib/csv_plus_plus/data_validation.rb +0 -138
  73. data/lib/csv_plus_plus/expand.rb +0 -20
  74. data/lib/csv_plus_plus/graph.rb +0 -62
  75. data/lib/csv_plus_plus/references.rb +0 -68
  76. data/lib/csv_plus_plus/scope.rb +0 -196
  77. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  78. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  79. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './file_backer_upper'
@@ -7,13 +8,19 @@ module CSVPlusPlus
7
8
  module Writer
8
9
  # A class that can output a +Template+ to an Excel file
9
10
  class Excel < ::CSVPlusPlus::Writer::BaseWriter
11
+ extend ::T::Sig
12
+
10
13
  include ::CSVPlusPlus::Writer::FileBackerUpper
11
14
 
12
- # write the +template+ to an Excel file
15
+ sig { override.params(template: ::CSVPlusPlus::Template).void }
16
+ # Write the +template+ to an Excel file
17
+ #
18
+ # @param template [Template] The template to write
13
19
  def write(template)
14
20
  ::CSVPlusPlus::Writer::RubyXLBuilder.new(
15
- input_filename: @options.output_filename,
21
+ input_filename: ::T.must(@options.output_filename),
16
22
  rows: template.rows,
23
+ runtime: @runtime,
17
24
  sheet_name: @options.sheet_name
18
25
  ).build_workbook.write(@options.output_filename)
19
26
  end
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -1,21 +1,42 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './google_sheet_modifier'
4
-
5
4
  module CSVPlusPlus
6
5
  module Writer
7
6
  # Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
8
7
  # rubocop:disable Metrics/ClassLength
9
8
  class GoogleSheetBuilder
10
- # @param current_sheet_values
11
- def initialize(current_sheet_values:, sheet_id:, rows:, column_index: 0, row_index: 0)
9
+ extend ::T::Sig
10
+
11
+ sig do
12
+ params(
13
+ current_sheet_values: ::T::Array[::T::Array[::T.nilable(::String)]],
14
+ runtime: ::CSVPlusPlus::Runtime::Runtime,
15
+ sheet_id: ::T.nilable(::Integer),
16
+ rows: ::T::Array[::CSVPlusPlus::Row],
17
+ column_index: ::T.nilable(::Integer),
18
+ row_index: ::T.nilable(::Integer)
19
+ ).void
20
+ end
21
+ # @param column_index [Integer] Offset the results by +column_index+
22
+ # @param current_sheet_values [Array<Array<::String, nil>>]
23
+ # @param sheet_id [::String] The sheet ID referencing the sheet in Google
24
+ # @param row_index [Integer] Offset the results by +row_index+
25
+ # @param rows [Array<Row>] The rows to render
26
+ # @param runtime [Runtime] The current runtime.
27
+ #
28
+ # rubocop:disable Metrics/ParameterLists
29
+ def initialize(current_sheet_values:, runtime:, sheet_id:, rows:, column_index: 0, row_index: 0)
30
+ # rubocop:enable Metrics/ParameterLists
12
31
  @current_sheet_values = current_sheet_values
13
32
  @sheet_id = sheet_id
14
33
  @rows = rows
15
34
  @column_index = column_index
16
35
  @row_index = row_index
36
+ @runtime = runtime
17
37
  end
18
38
 
39
+ sig { returns(::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest) }
19
40
  # Build a Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest
20
41
  #
21
42
  # @return [Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest]
@@ -25,6 +46,7 @@ module CSVPlusPlus
25
46
 
26
47
  private
27
48
 
49
+ sig { params(extended_value: ::Google::Apis::SheetsV4::ExtendedValue, value: ::T.nilable(::String)).void }
28
50
  def set_extended_value_type!(extended_value, value)
29
51
  v = value || ''
30
52
  if v.start_with?('=')
@@ -32,23 +54,25 @@ module CSVPlusPlus
32
54
  elsif v.match(/^-?[\d.]+$/)
33
55
  extended_value.number_value = value
34
56
  elsif v.downcase == 'true' || v.downcase == 'false'
35
- extended_value.boolean_value = value
57
+ extended_value.bool_value = value
36
58
  else
37
59
  extended_value.string_value = value
38
60
  end
39
61
  end
40
62
 
63
+ sig { params(mod: ::CSVPlusPlus::Modifier::GoogleSheetModifier).returns(::Google::Apis::SheetsV4::CellFormat) }
41
64
  def build_cell_format(mod)
42
65
  ::Google::Apis::SheetsV4::CellFormat.new.tap do |cf|
43
66
  cf.text_format = mod.text_format
44
67
 
45
- cf.horizontal_alignment = mod.halign
46
- cf.vertical_alignment = mod.valign
47
- cf.background_color = mod.color
48
- cf.number_format = mod.numberformat
68
+ cf.horizontal_alignment = mod.horizontal_alignment
69
+ cf.vertical_alignment = mod.vertical_alignment
70
+ cf.background_color = mod.background_color
71
+ cf.number_format = mod.number_format
49
72
  end
50
73
  end
51
74
 
75
+ sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::GridRange) }
52
76
  def grid_range_for_cell(cell)
53
77
  ::Google::Apis::SheetsV4::GridRange.new(
54
78
  sheet_id: @sheet_id,
@@ -59,41 +83,46 @@ module CSVPlusPlus
59
83
  )
60
84
  end
61
85
 
86
+ sig { params(row_index: ::Integer, cell_index: ::Integer).returns(::T.nilable(::String)) }
62
87
  def current_value(row_index, cell_index)
63
- @current_sheet_values[row_index][cell_index]
88
+ ::T.must(@current_sheet_values[row_index])[cell_index]
64
89
  rescue ::StandardError
65
90
  nil
66
91
  end
67
92
 
93
+ sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::ExtendedValue) }
68
94
  def build_cell_value(cell)
69
95
  ::Google::Apis::SheetsV4::ExtendedValue.new.tap do |xv|
70
96
  value =
71
97
  if cell.value.nil?
72
98
  current_value(cell.row_index, cell.index)
73
99
  else
74
- cell.to_csv
100
+ cell.evaluate(@runtime)
75
101
  end
76
102
 
77
103
  set_extended_value_type!(xv, value)
78
104
  end
79
105
  end
80
106
 
107
+ sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::CellData) }
81
108
  def build_cell_data(cell)
82
- mod = ::CSVPlusPlus::Writer::GoogleSheetModifier.new(cell.modifier)
109
+ mod = cell.modifier
83
110
 
84
111
  ::Google::Apis::SheetsV4::CellData.new.tap do |cd|
85
- cd.user_entered_format = build_cell_format(mod)
112
+ cd.user_entered_format = build_cell_format(::T.cast(mod, ::CSVPlusPlus::Modifier::GoogleSheetModifier))
86
113
  cd.note = mod.note if mod.note
87
114
 
88
- # XXX apply data validation
115
+ # TODO: apply data validation
89
116
  cd.user_entered_value = build_cell_value(cell)
90
117
  end
91
118
  end
92
119
 
120
+ sig { params(row: ::CSVPlusPlus::Row).returns(::Google::Apis::SheetsV4::RowData) }
93
121
  def build_row_data(row)
94
122
  ::Google::Apis::SheetsV4::RowData.new(values: row.cells.map { |cell| build_cell_data(cell) })
95
123
  end
96
124
 
125
+ sig { params(rows: ::T::Array[::CSVPlusPlus::Row]).returns(::Google::Apis::SheetsV4::UpdateCellsRequest) }
97
126
  def build_update_cells_request(rows)
98
127
  ::Google::Apis::SheetsV4::UpdateCellsRequest.new(
99
128
  fields: '*',
@@ -102,38 +131,57 @@ module CSVPlusPlus
102
131
  column_index: @column_index,
103
132
  row_index: @row_index
104
133
  ),
105
- rows: rows.map { |row| build_row_data(row) }
134
+ rows:
106
135
  )
107
136
  end
108
137
 
138
+ sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::UpdateBordersRequest) }
109
139
  def build_border(cell)
110
- mod = ::CSVPlusPlus::Writer::GoogleSheetModifier.new(cell.modifier)
140
+ mod = ::T.cast(cell.modifier, ::CSVPlusPlus::Modifier::GoogleSheetModifier)
111
141
  border = mod.border
112
142
 
113
143
  ::Google::Apis::SheetsV4::UpdateBordersRequest.new(
114
- top: mod.border_along?(:top) ? border : nil,
115
- right: mod.border_along?(:right) ? border : nil,
116
- left: mod.border_along?(:left) ? border : nil,
117
- bottom: mod.border_along?(:bottom) ? border : nil,
144
+ top: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Top) ? border : nil,
145
+ right: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Right) ? border : nil,
146
+ left: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Left) ? border : nil,
147
+ bottom: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Bottom) ? border : nil,
118
148
  range: grid_range_for_cell(cell)
119
149
  )
120
150
  end
121
151
 
152
+ sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::Request) }
122
153
  def build_update_borders_request(cell)
123
154
  ::Google::Apis::SheetsV4::Request.new(update_borders: build_border(cell))
124
155
  end
125
156
 
157
+ sig { params(rows: ::T::Array[::CSVPlusPlus::Row]).returns(::T::Array[::Google::Apis::SheetsV4::Request]) }
158
+ # rubocop:disable Metrics/MethodLength
126
159
  def chunked_requests(rows)
127
- rows.each_slice(1000).to_a.map do |chunked_rows|
128
- ::Google::Apis::SheetsV4::Request.new(update_cells: build_update_cells_request(chunked_rows))
160
+ accum = []
161
+ [].tap do |chunked|
162
+ @runtime.map_rows(rows) do |row|
163
+ accum << build_row_data(row)
164
+ next unless accum.length == 1000
165
+
166
+ chunked << ::Google::Apis::SheetsV4::Request.new(update_cells: build_update_cells_request(accum))
167
+ accum = []
168
+ end
169
+
170
+ unless accum.empty?
171
+ chunked << ::Google::Apis::SheetsV4::Request.new(update_cells: build_update_cells_request(accum))
172
+ end
129
173
  end
130
174
  end
175
+ # rubocop:enable Metrics/MethodLength
131
176
 
177
+ sig do
178
+ params(rows: ::T::Array[::CSVPlusPlus::Row]).returns(::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest)
179
+ end
132
180
  def build_batch_request(rows)
133
181
  ::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new.tap do |bu|
134
182
  bu.requests = chunked_requests(rows)
135
183
 
136
- rows.each do |row|
184
+ @runtime.map_rows(rows) do |row|
137
185
  row.cells.filter { |c| c.modifier.any_border? }
138
186
  .each do |cell|
139
187
  bu.requests << build_update_borders_request(cell)
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative '../google_api_client'
@@ -7,25 +8,37 @@ require_relative 'google_sheet_builder'
7
8
  module CSVPlusPlus
8
9
  module Writer
9
10
  # A class that can write a +Template+ to Google Sheets (via their API)
11
+ # rubocop:disable Metrics/ClassLength
10
12
  class GoogleSheets < ::CSVPlusPlus::Writer::BaseWriter
13
+ extend ::T::Sig
14
+
15
+ sig { returns(::String) }
16
+ attr_reader :sheet_id
17
+
18
+ sig { returns(::T.nilable(::String)) }
19
+ attr_reader :sheet_name
20
+
11
21
  # TODO: it would be nice to raise this but we shouldn't expand out more than necessary for our data
12
22
  SPREADSHEET_INFINITY = 1000
13
23
  public_constant :SPREADSHEET_INFINITY
14
24
 
25
+ sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
15
26
  # @param options [Options]
16
- def initialize(options)
17
- super(options)
18
-
19
- @sheet_id = options.google.sheet_id
20
- @sheet_name = options.sheet_name
27
+ # @param runtime [Runtime]
28
+ def initialize(options, runtime)
29
+ super(options, runtime)
30
+
31
+ # @current_values = ::T.let(nil, ::T.nilable(::T::Array
32
+ @sheet_id = ::T.let(::T.must(options.google).sheet_id, ::String)
33
+ @sheet_name = ::T.let(options.sheet_name, ::T.nilable(::String))
34
+ @sheets_client = ::T.let(::CSVPlusPlus::GoogleApiClient.sheets_client, ::Google::Apis::SheetsV4::SheetsService)
21
35
  end
22
36
 
37
+ sig { override.params(template: ::CSVPlusPlus::Template).void }
23
38
  # write a +template+ to Google Sheets
24
39
  #
25
40
  # @param template [Template]
26
41
  def write(template)
27
- @sheets_client = ::CSVPlusPlus::GoogleApiClient.sheets_client
28
-
29
42
  fetch_spreadsheet!
30
43
  fetch_spreadsheet_values!
31
44
 
@@ -34,6 +47,7 @@ module CSVPlusPlus
34
47
  update_cells!(template)
35
48
  end
36
49
 
50
+ sig { override.void }
37
51
  # write a backup of the google sheet
38
52
  def write_backup
39
53
  drive_client = ::CSVPlusPlus::GoogleApiClient.drive_client
@@ -42,23 +56,12 @@ module CSVPlusPlus
42
56
 
43
57
  private
44
58
 
45
- def format_range(range)
46
- @sheet_name ? "'#{@sheet_name}'!#{range}" : range
47
- end
48
-
49
- def full_range
50
- format_range('A1:Z1000')
51
- end
52
-
53
- def fetch_spreadsheet_values!
54
- formatted_values = get_all_spreadsheet_values('FORMATTED_VALUE')
55
- formula_values = get_all_spreadsheet_values('FORMULA')
56
-
57
- return if formula_values.values.nil? || formatted_values.values.nil?
58
-
59
- @current_values = extract_current_values(formatted_values, formula_values)
59
+ sig do
60
+ params(
61
+ formatted_values: ::Google::Apis::SheetsV4::ValueRange,
62
+ formula_values: ::Google::Apis::SheetsV4::ValueRange
63
+ ).returns(::T::Array[::T::Array[::T.nilable(::String)]])
60
64
  end
61
-
62
65
  def extract_current_values(formatted_values, formula_values)
63
66
  formatted_values.values.map.each_with_index do |row, x|
64
67
  row.map.each_with_index do |_cell, y|
@@ -72,49 +75,96 @@ module CSVPlusPlus
72
75
  end
73
76
  end
74
77
 
78
+ sig { void }
79
+ # rubocop:disable Metrics/MethodLength
80
+ def fetch_spreadsheet_values!
81
+ formatted_values = get_all_spreadsheet_values('FORMATTED_VALUE')
82
+ formula_values = get_all_spreadsheet_values('FORMULA')
83
+
84
+ @current_values =
85
+ ::T.let(
86
+ if formula_values.values.nil? || formatted_values.values.nil?
87
+ []
88
+ else
89
+ extract_current_values(formatted_values, formula_values)
90
+ end,
91
+ ::T.nilable(::T::Array[::T::Array[::T.nilable(::String)]])
92
+ )
93
+ end
94
+ # rubocop:enable Metrics/MethodLength
95
+
96
+ sig { params(range: ::String).returns(::String) }
97
+ def format_range(range)
98
+ @sheet_name ? "'#{@sheet_name}'!#{range}" : range
99
+ end
100
+
101
+ sig { returns(::String) }
102
+ def full_range
103
+ format_range('A1:Z1000')
104
+ end
105
+
106
+ sig { params(str: ::String).returns(::T.nilable(::String)) }
75
107
  def strip_to_nil(str)
76
108
  str.strip.empty? ? nil : str
77
109
  end
78
110
 
111
+ sig { params(render_option: ::String).returns(::Google::Apis::SheetsV4::ValueRange) }
79
112
  def get_all_spreadsheet_values(render_option)
80
113
  @sheets_client.get_spreadsheet_values(@sheet_id, full_range, value_render_option: render_option)
81
114
  end
82
115
 
116
+ sig { returns(::T.nilable(::Google::Apis::SheetsV4::Sheet)) }
83
117
  def sheet
84
118
  return unless @sheet_name
85
119
 
86
- @spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
120
+ spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
87
121
  end
88
122
 
89
- def fetch_spreadsheet!
90
- @spreadsheet = @sheets_client.get_spreadsheet(@sheet_id)
123
+ sig { returns(::Google::Apis::SheetsV4::Spreadsheet) }
124
+ def spreadsheet
125
+ @spreadsheet ||= ::T.let(
126
+ @sheets_client.get_spreadsheet(@sheet_id),
127
+ ::T.nilable(::Google::Apis::SheetsV4::Spreadsheet)
128
+ )
129
+
130
+ raise(::CSVPlusPlus::Error::WriterError, 'Unable to connect to google spreadsheet') unless @spreadsheet
91
131
 
132
+ @spreadsheet
133
+ end
134
+
135
+ sig { void }
136
+ def fetch_spreadsheet!
92
137
  return unless @sheet_name.nil?
93
138
 
94
- @sheet_name = @spreadsheet.sheets&.first&.properties&.title
139
+ @sheet_name = spreadsheet.sheets&.first&.properties&.title
95
140
  end
96
141
 
142
+ sig { void }
97
143
  def create_sheet!
98
144
  return if sheet
99
145
 
100
146
  @sheets_client.create_spreadsheet(@sheet_name)
101
147
  fetch_spreadsheet!
102
- @sheet_name = @spreadsheet.sheets.last.properties.title
148
+ @sheet_name = spreadsheet.sheets.last.properties.title
103
149
  end
104
150
 
151
+ sig { params(template: ::CSVPlusPlus::Template).void }
105
152
  def update_cells!(template)
106
153
  @sheets_client.batch_update_spreadsheet(@sheet_id, builder(template).batch_update_spreadsheet_request)
107
154
  end
108
155
 
156
+ sig { params(template: ::CSVPlusPlus::Template).returns(::CSVPlusPlus::Writer::GoogleSheetBuilder) }
109
157
  def builder(template)
110
158
  ::CSVPlusPlus::Writer::GoogleSheetBuilder.new(
159
+ runtime: @runtime,
111
160
  rows: template.rows,
112
161
  sheet_id: sheet&.properties&.sheet_id,
113
162
  column_index: @options.offset[1],
114
163
  row_index: @options.offset[0],
115
- current_sheet_values: @current_values
164
+ current_sheet_values: ::T.must(@current_values)
116
165
  )
117
166
  end
118
167
  end
168
+ # rubocop:enable Metrics/ClassLength
119
169
  end
120
170
  end
@@ -1,10 +1,15 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Writer
5
- ##
6
6
  # A class that can output a +Template+ to an Excel file
7
7
  class OpenDocument < ::CSVPlusPlus::Writer::BaseWriter
8
+ extend ::T::Sig
9
+
10
+ include ::CSVPlusPlus::Writer::FileBackerUpper
11
+
12
+ sig { override.params(template: ::CSVPlusPlus::Template).void }
8
13
  # write a +template+ to an OpenDocument file
9
14
  def write(template)
10
15
  # TODO
@@ -1,50 +1,93 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './rubyxl_modifier'
4
-
5
4
  module CSVPlusPlus
6
5
  module Writer
7
6
  # Build a RubyXL workbook formatted according to the given +rows+
8
7
  #
9
8
  # @attr_reader input_filename [String] The filename being written to
10
9
  # @attr_reader rows [Array<Row>] The rows being written
10
+ # rubocop:disable Metrics/ClassLength
11
11
  class RubyXLBuilder
12
- attr_reader :input_filename, :rows
12
+ extend ::T::Sig
13
+
14
+ RubyXLCell = ::T.type_alias { ::T.all(::RubyXL::Cell, ::RubyXL::CellConvenienceMethods) }
15
+ public_constant :RubyXLCell
16
+
17
+ sig { returns(::T.nilable(::String)) }
18
+ attr_reader :input_filename
13
19
 
14
- # @param input_filename [String] The file to write to
20
+ sig { returns(::T::Array[::CSVPlusPlus::Row]) }
21
+ attr_reader :rows
22
+
23
+ sig do
24
+ params(
25
+ input_filename: ::T.nilable(::String),
26
+ rows: ::T::Array[::CSVPlusPlus::Row],
27
+ runtime: ::CSVPlusPlus::Runtime::Runtime,
28
+ sheet_name: ::T.nilable(::String)
29
+ ).void
30
+ end
31
+ # @param input_filename [::String] The file to write to
15
32
  # @param rows [Array<Row>] The rows to write
16
- # @param sheet_name [String] The name of the sheet within the workbook to write to
17
- def initialize(input_filename:, rows:, sheet_name:)
33
+ # @param runtime [Runtime] The current runtime
34
+ # @param sheet_name [::String] The name of the sheet within the workbook to write to
35
+ def initialize(input_filename:, rows:, runtime:, sheet_name: nil)
18
36
  @rows = rows
19
37
  @input_filename = input_filename
38
+ @runtime = runtime
20
39
  @sheet_name = sheet_name
40
+ @worksheet = ::T.let(open_worksheet, ::RubyXL::Worksheet)
21
41
  end
22
42
 
43
+ sig { returns(::RubyXL::Workbook) }
23
44
  # Build a +RubyXL::Workbook+ with the given +@rows+ in +sheet_name+
24
45
  #
25
46
  # @return [RubyXL::Workbook]
26
47
  def build_workbook
27
- open_workbook.tap { build_workbook! }
48
+ build_workbook!
49
+ @worksheet.workbook
28
50
  end
29
51
 
30
52
  private
31
53
 
54
+ sig { void }
55
+ # rubocop:disable Metrics/MethodLength
32
56
  def build_workbook!
33
- @rows.each_with_index do |row, x|
34
- row.cells.each_with_index do |cell, y|
35
- modifier = ::CSVPlusPlus::Writer::RubyXLModifier.new(cell.modifier)
36
-
37
- @worksheet.add_cell(x, y, cell.to_csv)
38
- format_cell!(x, y, modifier)
57
+ @runtime.map_all_cells(@rows) do |cell|
58
+ value = cell.evaluate(@runtime)
59
+ if value&.start_with?('=')
60
+ @worksheet.add_cell(@runtime.row_index, @runtime.cell_index, '', value.gsub(/^=/, ''))
61
+ else
62
+ @worksheet.add_cell(@runtime.row_index, @runtime.cell_index, value)
39
63
  end
64
+
65
+ format_cell!(
66
+ @runtime.row_index,
67
+ @runtime.cell_index,
68
+ ::T.cast(cell.modifier, ::CSVPlusPlus::Modifier::RubyXLModifier)
69
+ )
40
70
  end
41
71
  end
72
+ # rubocop:enable Metrics/MethodLength
42
73
 
74
+ sig do
75
+ params(
76
+ cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
77
+ modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
78
+ ).void
79
+ end
43
80
  def do_alignments!(cell, modifier)
44
- cell.change_horizontal_alignment(modifier.halign) if modifier.halign
45
- cell.change_vertical_alignment(modifier.valign) if modifier.valign
81
+ cell.change_horizontal_alignment(modifier.horizontal_alignment) if modifier.halign
82
+ cell.change_vertical_alignment(modifier.vertical_alignment) if modifier.valign
46
83
  end
47
84
 
85
+ sig do
86
+ params(
87
+ cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
88
+ modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
89
+ ).void
90
+ end
48
91
  # rubocop:disable Metrics/MethodLength
49
92
  def do_borders!(cell, modifier)
50
93
  return unless modifier.any_border?
@@ -59,29 +102,56 @@ module CSVPlusPlus
59
102
  end
60
103
  else
61
104
  modifier.borders.each do |direction|
62
- cell.change_border(direction, color || weight)
105
+ # TODO: move direction.serialize into the RubyXLModifier
106
+ cell.change_border(direction.serialize, color || weight)
63
107
  end
64
108
  end
65
109
  end
66
110
  # rubocop:enable Metrics/MethodLength
67
111
 
112
+ sig do
113
+ params(
114
+ cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
115
+ modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
116
+ ).void
117
+ end
68
118
  def do_fill!(cell, modifier)
69
- cell.change_fill(modifier.color.to_hex) if modifier.color
119
+ return unless modifier.color
120
+
121
+ cell.change_fill(modifier.color&.to_hex)
70
122
  end
71
123
 
124
+ sig do
125
+ params(
126
+ cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
127
+ modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
128
+ ).void
129
+ end
72
130
  def do_formats!(cell, modifier)
73
- cell.change_font_bold(true) if modifier.formatted?('bold')
74
- cell.change_font_italics(true) if modifier.formatted?('italic')
75
- cell.change_font_underline(true) if modifier.formatted?('underline')
76
- cell.change_font_strikethrough(true) if modifier.formatted?('strikethrough')
131
+ cell.change_font_bold(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Bold)
132
+ cell.change_font_italics(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Italic)
133
+ cell.change_font_underline(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Underline)
134
+ cell.change_font_strikethrough(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Strikethrough)
77
135
  end
78
136
 
137
+ sig do
138
+ params(
139
+ cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
140
+ modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
141
+ ).void
142
+ end
79
143
  def do_fonts!(cell, modifier)
80
- cell.change_font_color(modifier.fontcolor.to_hex) if modifier.fontcolor
144
+ cell.change_font_color(::T.must(modifier.fontcolor).to_hex) if modifier.fontcolor
81
145
  cell.change_font_name(modifier.fontfamily) if modifier.fontfamily
82
146
  cell.change_font_size(modifier.fontsize) if modifier.fontsize
83
147
  end
84
148
 
149
+ sig do
150
+ params(
151
+ cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
152
+ modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
153
+ ).void
154
+ end
85
155
  def do_number_formats!(cell, modifier)
86
156
  return unless modifier.numberformat
87
157
 
@@ -90,6 +160,9 @@ module CSVPlusPlus
90
160
  cell.change_contents(cell.value)
91
161
  end
92
162
 
163
+ sig do
164
+ params(row_index: ::Integer, cell_index: ::Integer, modifier: ::CSVPlusPlus::Modifier::RubyXLModifier).void
165
+ end
93
166
  def format_cell!(row_index, cell_index, modifier)
94
167
  @worksheet.sheet_data[row_index][cell_index].tap do |cell|
95
168
  do_alignments!(cell, modifier)
@@ -101,17 +174,17 @@ module CSVPlusPlus
101
174
  end
102
175
  end
103
176
 
104
- def open_workbook
105
- if ::File.exist?(@input_filename)
106
- ::RubyXL::Parser.parse(@input_filename).tap do |workbook|
107
- @worksheet = workbook[@sheet_name] || workbook.add_worksheet(@sheet_name)
108
- end
177
+ sig { returns(::RubyXL::Worksheet) }
178
+ def open_worksheet
179
+ if @input_filename && ::File.exist?(@input_filename)
180
+ workbook = ::RubyXL::Parser.parse(@input_filename)
181
+ workbook[@sheet_name] || workbook.add_worksheet(@sheet_name)
109
182
  else
110
- ::RubyXL::Workbook.new.tap do |workbook|
111
- @worksheet = workbook.worksheets[0].tap { |w| w.sheet_name = @sheet_name }
112
- end
183
+ workbook = ::RubyXL::Workbook.new
184
+ workbook.worksheets[0].tap { |w| w.sheet_name = @sheet_name }
113
185
  end
114
186
  end
115
187
  end
188
+ # rubocop:enable Metrics/ClassLength
116
189
  end
117
190
  end