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
@@ -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
|
-
|
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,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
|
-
|
11
|
-
|
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.
|
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.
|
46
|
-
cf.vertical_alignment = mod.
|
47
|
-
cf.background_color = mod.
|
48
|
-
cf.number_format = mod.
|
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.
|
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 =
|
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
|
-
#
|
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:
|
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 = ::
|
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?(
|
115
|
-
right: mod.border_along?(
|
116
|
-
left: mod.border_along?(
|
117
|
-
bottom: mod.border_along?(
|
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
|
-
|
128
|
-
|
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
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
120
|
+
spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
|
87
121
|
end
|
88
122
|
|
89
|
-
|
90
|
-
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
17
|
-
|
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
|
-
|
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
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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.
|
45
|
-
cell.change_vertical_alignment(modifier.
|
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
|
-
|
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
|
-
|
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?(
|
74
|
-
cell.change_font_italics(true) if modifier.formatted?(
|
75
|
-
cell.change_font_underline(true) if modifier.formatted?(
|
76
|
-
cell.change_font_strikethrough(true) if modifier.formatted?(
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
111
|
-
|
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
|