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