csv_plus_plus 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -3
- data/docs/CHANGELOG.md +16 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +3 -3
- data/lib/csv_plus_plus/cell.rb +1 -35
- data/lib/csv_plus_plus/cli.rb +43 -80
- data/lib/csv_plus_plus/cli_flag.rb +71 -70
- data/lib/csv_plus_plus/color.rb +1 -1
- data/lib/csv_plus_plus/compiler.rb +31 -21
- data/lib/csv_plus_plus/entities/ast_builder.rb +11 -4
- data/lib/csv_plus_plus/entities/boolean.rb +16 -9
- data/lib/csv_plus_plus/entities/builtins.rb +68 -40
- data/lib/csv_plus_plus/entities/date.rb +14 -11
- data/lib/csv_plus_plus/entities/entity.rb +11 -29
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +18 -31
- data/lib/csv_plus_plus/entities/function.rb +22 -11
- data/lib/csv_plus_plus/entities/function_call.rb +35 -11
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +15 -10
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +36 -23
- data/lib/csv_plus_plus/entities/string.rb +13 -10
- data/lib/csv_plus_plus/entities.rb +2 -18
- data/lib/csv_plus_plus/error/cli_error.rb +17 -0
- data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
- data/lib/csv_plus_plus/error/error.rb +18 -5
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -13
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +10 -36
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +6 -32
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +1 -1
- data/lib/csv_plus_plus/error.rb +4 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +18 -8
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +53 -17
- data/lib/csv_plus_plus/lexer.rb +40 -1
- data/lib/csv_plus_plus/modifier/data_validation.rb +1 -1
- data/lib/csv_plus_plus/modifier/expand.rb +17 -0
- data/lib/csv_plus_plus/modifier.rb +6 -1
- data/lib/csv_plus_plus/options/file_options.rb +49 -0
- data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
- data/lib/csv_plus_plus/options/options.rb +97 -0
- data/lib/csv_plus_plus/options.rb +22 -110
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +65 -66
- data/lib/csv_plus_plus/parser/code_section.tab.rb +92 -84
- data/lib/csv_plus_plus/parser/modifier.tab.rb +40 -30
- data/lib/csv_plus_plus/reader/csv.rb +50 -0
- data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
- data/lib/csv_plus_plus/reader/reader.rb +27 -0
- data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
- data/lib/csv_plus_plus/reader.rb +14 -0
- data/lib/csv_plus_plus/runtime/graph.rb +6 -6
- data/lib/csv_plus_plus/runtime/{position_tracker.rb → position.rb} +16 -5
- data/lib/csv_plus_plus/runtime/references.rb +32 -27
- data/lib/csv_plus_plus/runtime/runtime.rb +73 -67
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +9 -9
- data/lib/csv_plus_plus/source_code.rb +14 -9
- data/lib/csv_plus_plus/template.rb +17 -12
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/csv.rb +32 -5
- data/lib/csv_plus_plus/writer/excel.rb +19 -6
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -14
- data/lib/csv_plus_plus/writer/google_sheets.rb +23 -129
- data/lib/csv_plus_plus/writer/{google_sheet_builder.rb → google_sheets_builder.rb} +39 -55
- data/lib/csv_plus_plus/writer/merger.rb +31 -0
- data/lib/csv_plus_plus/writer/open_document.rb +16 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +68 -43
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +58 -19
- data/lib/csv_plus_plus.rb +26 -14
- metadata +37 -12
- data/lib/csv_plus_plus/entities/cell_reference.rb +0 -231
- data/lib/csv_plus_plus/entities/variable.rb +0 -37
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -71
- data/lib/csv_plus_plus/google_options.rb +0 -32
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -89
- data/lib/csv_plus_plus/runtime/can_define_references.rb +0 -87
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +0 -209
- data/lib/csv_plus_plus/writer/base_writer.rb +0 -45
@@ -1,170 +1,64 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative '../google_api_client'
|
5
|
-
require_relative 'base_writer'
|
6
|
-
require_relative 'google_sheet_builder'
|
7
|
-
|
8
4
|
module CSVPlusPlus
|
9
5
|
module Writer
|
10
6
|
# A class that can write a +Template+ to Google Sheets (via their API)
|
11
|
-
|
12
|
-
class GoogleSheets < ::CSVPlusPlus::Writer::BaseWriter
|
7
|
+
class GoogleSheets < ::CSVPlusPlus::Writer::Writer
|
13
8
|
extend ::T::Sig
|
9
|
+
include ::CSVPlusPlus::GoogleApiClient
|
14
10
|
|
15
|
-
sig
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
SPREADSHEET_INFINITY = 1000
|
23
|
-
public_constant :SPREADSHEET_INFINITY
|
24
|
-
|
25
|
-
sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
26
|
-
# @param options [Options]
|
27
|
-
# @param runtime [Runtime]
|
28
|
-
def initialize(options, runtime)
|
29
|
-
super(options, runtime)
|
11
|
+
sig do
|
12
|
+
params(options: ::CSVPlusPlus::Options::GoogleSheetsOptions, position: ::CSVPlusPlus::Runtime::Position).void
|
13
|
+
end
|
14
|
+
# @param options [Options::GoogleSheetsOptions]
|
15
|
+
# @param position [Runtime::Position]
|
16
|
+
def initialize(options, position)
|
17
|
+
super(position)
|
30
18
|
|
31
|
-
|
32
|
-
@
|
33
|
-
@sheet_name = ::T.let(options.sheet_name, ::T.nilable(::String))
|
34
|
-
@sheets_client = ::T.let(::CSVPlusPlus::GoogleApiClient.sheets_client, ::Google::Apis::SheetsV4::SheetsService)
|
19
|
+
@options = ::T.let(options, ::CSVPlusPlus::Options::GoogleSheetsOptions)
|
20
|
+
@reader = ::T.let(::CSVPlusPlus::Reader::GoogleSheets.new(options), ::CSVPlusPlus::Reader::GoogleSheets)
|
35
21
|
end
|
36
22
|
|
37
23
|
sig { override.params(template: ::CSVPlusPlus::Template).void }
|
38
|
-
#
|
24
|
+
# Write a +template+ to Google Sheets
|
39
25
|
#
|
40
26
|
# @param template [Template]
|
41
27
|
def write(template)
|
42
|
-
fetch_spreadsheet!
|
43
|
-
fetch_spreadsheet_values!
|
44
|
-
|
45
28
|
create_sheet! if @options.create_if_not_exists
|
46
29
|
|
47
30
|
update_cells!(template)
|
48
31
|
end
|
49
32
|
|
50
33
|
sig { override.void }
|
51
|
-
#
|
34
|
+
# Write a backup of the Google Sheet that is about to be written
|
52
35
|
def write_backup
|
53
|
-
drive_client
|
54
|
-
drive_client.copy_file(@sheet_id)
|
36
|
+
drive_client.copy_file(@options.sheet_id)
|
55
37
|
end
|
56
38
|
|
57
39
|
private
|
58
40
|
|
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)]])
|
64
|
-
end
|
65
|
-
def extract_current_values(formatted_values, formula_values)
|
66
|
-
formatted_values.values.map.each_with_index do |row, x|
|
67
|
-
row.map.each_with_index do |_cell, y|
|
68
|
-
formula_value = formula_values.values[x][y]
|
69
|
-
if formula_value.is_a?(::String) && formula_value.start_with?('=')
|
70
|
-
formula_value
|
71
|
-
else
|
72
|
-
strip_to_nil(formatted_values.values[x][y])
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
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)) }
|
107
|
-
def strip_to_nil(str)
|
108
|
-
str.strip.empty? ? nil : str
|
109
|
-
end
|
110
|
-
|
111
|
-
sig { params(render_option: ::String).returns(::Google::Apis::SheetsV4::ValueRange) }
|
112
|
-
def get_all_spreadsheet_values(render_option)
|
113
|
-
@sheets_client.get_spreadsheet_values(@sheet_id, full_range, value_render_option: render_option)
|
114
|
-
end
|
115
|
-
|
116
|
-
sig { returns(::T.nilable(::Google::Apis::SheetsV4::Sheet)) }
|
117
|
-
def sheet
|
118
|
-
return unless @sheet_name
|
119
|
-
|
120
|
-
spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
|
121
|
-
end
|
122
|
-
|
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
|
131
|
-
|
132
|
-
@spreadsheet
|
133
|
-
end
|
134
|
-
|
135
|
-
sig { void }
|
136
|
-
def fetch_spreadsheet!
|
137
|
-
return unless @sheet_name.nil?
|
138
|
-
|
139
|
-
@sheet_name = spreadsheet.sheets&.first&.properties&.title
|
140
|
-
end
|
141
|
-
|
142
41
|
sig { void }
|
143
42
|
def create_sheet!
|
144
|
-
return if sheet
|
43
|
+
return if @reader.sheet
|
145
44
|
|
146
|
-
|
147
|
-
fetch_spreadsheet!
|
148
|
-
@sheet_name = spreadsheet.sheets.last.properties.title
|
45
|
+
sheets_client.create_spreadsheet(@options.sheet_name)
|
149
46
|
end
|
150
47
|
|
151
48
|
sig { params(template: ::CSVPlusPlus::Template).void }
|
152
49
|
def update_cells!(template)
|
153
|
-
|
50
|
+
sheets_client.batch_update_spreadsheet(@options.sheet_id, builder(template).batch_update_spreadsheet_request)
|
154
51
|
end
|
155
52
|
|
156
|
-
sig { params(template: ::CSVPlusPlus::Template).returns(::CSVPlusPlus::Writer::
|
53
|
+
sig { params(template: ::CSVPlusPlus::Template).returns(::CSVPlusPlus::Writer::GoogleSheetsBuilder) }
|
157
54
|
def builder(template)
|
158
|
-
::CSVPlusPlus::Writer::
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
row_index: @options.offset[0],
|
164
|
-
current_sheet_values: ::T.must(@current_values)
|
55
|
+
::CSVPlusPlus::Writer::GoogleSheetsBuilder.new(
|
56
|
+
options: @options,
|
57
|
+
position: @position,
|
58
|
+
reader: @reader,
|
59
|
+
rows: template.rows
|
165
60
|
)
|
166
61
|
end
|
167
62
|
end
|
168
|
-
# rubocop:enable Metrics/ClassLength
|
169
63
|
end
|
170
64
|
end
|
@@ -5,35 +5,27 @@ module CSVPlusPlus
|
|
5
5
|
module Writer
|
6
6
|
# Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
|
7
7
|
# rubocop:disable Metrics/ClassLength
|
8
|
-
class
|
8
|
+
class GoogleSheetsBuilder
|
9
9
|
extend ::T::Sig
|
10
|
+
include ::CSVPlusPlus::Writer::Merger
|
10
11
|
|
11
12
|
sig do
|
12
13
|
params(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rows: ::T::Array[::CSVPlusPlus::Row]
|
17
|
-
column_index: ::T.nilable(::Integer),
|
18
|
-
row_index: ::T.nilable(::Integer)
|
14
|
+
options: ::CSVPlusPlus::Options::GoogleSheetsOptions,
|
15
|
+
position: ::CSVPlusPlus::Runtime::Position,
|
16
|
+
reader: ::CSVPlusPlus::Reader::GoogleSheets,
|
17
|
+
rows: ::T::Array[::CSVPlusPlus::Row]
|
19
18
|
).void
|
20
19
|
end
|
21
|
-
# @param
|
22
|
-
# @param
|
23
|
-
# @param
|
24
|
-
# @param row_index [Integer] Offset the results by +row_index+
|
20
|
+
# @param options [Options]
|
21
|
+
# @param position [Position] The current position.
|
22
|
+
# @param reader [::CSVPlusPlus::Reader::GoogleSheets]
|
25
23
|
# @param rows [Array<Row>] The rows to render
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# rubocop:enable Metrics/ParameterLists
|
31
|
-
@current_sheet_values = current_sheet_values
|
32
|
-
@sheet_id = sheet_id
|
24
|
+
def initialize(options:, position:, reader:, rows:)
|
25
|
+
@options = options
|
26
|
+
@position = position
|
27
|
+
@reader = reader
|
33
28
|
@rows = rows
|
34
|
-
@column_index = column_index
|
35
|
-
@row_index = row_index
|
36
|
-
@runtime = runtime
|
37
29
|
end
|
38
30
|
|
39
31
|
sig { returns(::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest) }
|
@@ -46,19 +38,23 @@ module CSVPlusPlus
|
|
46
38
|
|
47
39
|
private
|
48
40
|
|
49
|
-
sig { params(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
41
|
+
sig { params(value: ::T.nilable(::String)).returns(::Google::Apis::SheetsV4::ExtendedValue) }
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
43
|
+
def build_extended_value(value)
|
44
|
+
::Google::Apis::SheetsV4::ExtendedValue.new.tap do |xv|
|
45
|
+
v = value || ''
|
46
|
+
if v.start_with?('=')
|
47
|
+
xv.formula_value = value
|
48
|
+
elsif v.match(/^-?[\d.]+$/)
|
49
|
+
xv.number_value = value
|
50
|
+
elsif v.downcase == 'true' || v.downcase == 'false'
|
51
|
+
xv.bool_value = value
|
52
|
+
else
|
53
|
+
xv.string_value = value
|
54
|
+
end
|
60
55
|
end
|
61
56
|
end
|
57
|
+
# rubocop:enable Metrics/MethodLength
|
62
58
|
|
63
59
|
sig { params(mod: ::CSVPlusPlus::Modifier::GoogleSheetModifier).returns(::Google::Apis::SheetsV4::CellFormat) }
|
64
60
|
def build_cell_format(mod)
|
@@ -75,7 +71,6 @@ module CSVPlusPlus
|
|
75
71
|
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::GridRange) }
|
76
72
|
def grid_range_for_cell(cell)
|
77
73
|
::Google::Apis::SheetsV4::GridRange.new(
|
78
|
-
sheet_id: @sheet_id,
|
79
74
|
start_column_index: cell.index,
|
80
75
|
end_column_index: cell.index + 1,
|
81
76
|
start_row_index: cell.row_index,
|
@@ -83,25 +78,15 @@ module CSVPlusPlus
|
|
83
78
|
)
|
84
79
|
end
|
85
80
|
|
86
|
-
sig { params(row_index: ::Integer, cell_index: ::Integer).returns(::T.nilable(::String)) }
|
87
|
-
def current_value(row_index, cell_index)
|
88
|
-
::T.must(@current_sheet_values[row_index])[cell_index]
|
89
|
-
rescue ::StandardError
|
90
|
-
nil
|
91
|
-
end
|
92
|
-
|
93
81
|
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::ExtendedValue) }
|
94
82
|
def build_cell_value(cell)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
set_extended_value_type!(xv, value)
|
104
|
-
end
|
83
|
+
build_extended_value(
|
84
|
+
merge_cell_value(
|
85
|
+
existing_value: @reader.value_at(cell),
|
86
|
+
new_value: (ast = cell.ast) ? ast.evaluate(@position) : cell.value,
|
87
|
+
options: @options
|
88
|
+
)
|
89
|
+
)
|
105
90
|
end
|
106
91
|
|
107
92
|
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::CellData) }
|
@@ -127,9 +112,8 @@ module CSVPlusPlus
|
|
127
112
|
::Google::Apis::SheetsV4::UpdateCellsRequest.new(
|
128
113
|
fields: '*',
|
129
114
|
start: ::Google::Apis::SheetsV4::GridCoordinate.new(
|
130
|
-
|
131
|
-
|
132
|
-
row_index: @row_index
|
115
|
+
column_index: @options.offset[1],
|
116
|
+
row_index: @options.offset[0]
|
133
117
|
),
|
134
118
|
rows:
|
135
119
|
)
|
@@ -159,7 +143,7 @@ module CSVPlusPlus
|
|
159
143
|
def chunked_requests(rows)
|
160
144
|
accum = []
|
161
145
|
[].tap do |chunked|
|
162
|
-
@
|
146
|
+
@position.map_rows(rows) do |row|
|
163
147
|
accum << build_row_data(row)
|
164
148
|
next unless accum.length == 1000
|
165
149
|
|
@@ -181,7 +165,7 @@ module CSVPlusPlus
|
|
181
165
|
::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new.tap do |bu|
|
182
166
|
bu.requests = chunked_requests(rows)
|
183
167
|
|
184
|
-
@
|
168
|
+
@position.map_rows(rows) do |row|
|
185
169
|
row.cells.filter { |c| c.modifier.any_border? }
|
186
170
|
.each do |cell|
|
187
171
|
bu.requests << build_update_borders_request(cell)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Writer
|
6
|
+
# A merging strategy for when we want to write to a cell but it has a value
|
7
|
+
module Merger
|
8
|
+
extend ::T::Sig
|
9
|
+
include ::Kernel
|
10
|
+
|
11
|
+
sig do
|
12
|
+
type_parameters(:V)
|
13
|
+
.params(
|
14
|
+
existing_value: ::T.nilable(::T.all(::T.type_parameter(:V), ::BasicObject)),
|
15
|
+
new_value: ::T.nilable(::T.all(::T.type_parameter(:V), ::BasicObject)),
|
16
|
+
options: ::CSVPlusPlus::Options::Options
|
17
|
+
).returns(::T.nilable(::T.type_parameter(:V)))
|
18
|
+
end
|
19
|
+
# Our strategy for resolving differences between new changes and existing
|
20
|
+
def merge_cell_value(existing_value:, new_value:, options:)
|
21
|
+
# TODO: make an option that specifies if we override (take new data over old)
|
22
|
+
merged_value = new_value || existing_value
|
23
|
+
|
24
|
+
return merged_value if !options.verbose || merged_value == existing_value
|
25
|
+
|
26
|
+
warn("Overwriting existing value: \"#{existing_value}\" with \"#{new_value}\"")
|
27
|
+
merged_value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -4,16 +4,30 @@
|
|
4
4
|
module CSVPlusPlus
|
5
5
|
module Writer
|
6
6
|
# A class that can output a +Template+ to an Excel file
|
7
|
-
class OpenDocument < ::CSVPlusPlus::Writer::
|
7
|
+
class OpenDocument < ::CSVPlusPlus::Writer::Writer
|
8
8
|
extend ::T::Sig
|
9
|
-
|
10
9
|
include ::CSVPlusPlus::Writer::FileBackerUpper
|
11
10
|
|
11
|
+
sig { params(options: ::CSVPlusPlus::Options::FileOptions, position: ::CSVPlusPlus::Runtime::Position).void }
|
12
|
+
# @param options [Options::FileOptions]
|
13
|
+
# @param position [Runtime::Position]
|
14
|
+
def initialize(options, position)
|
15
|
+
super(position)
|
16
|
+
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
12
20
|
sig { override.params(template: ::CSVPlusPlus::Template).void }
|
13
21
|
# write a +template+ to an OpenDocument file
|
14
22
|
def write(template)
|
15
23
|
# TODO
|
16
24
|
end
|
25
|
+
|
26
|
+
sig { override.void }
|
27
|
+
# write a backup of the google sheet
|
28
|
+
def write_backup
|
29
|
+
backup_file(@options)
|
30
|
+
end
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|
@@ -5,39 +5,38 @@ module CSVPlusPlus
|
|
5
5
|
module Writer
|
6
6
|
# Build a RubyXL workbook formatted according to the given +rows+
|
7
7
|
#
|
8
|
-
# @attr_reader
|
8
|
+
# @attr_reader output_filename [Pathname, nil] The filename being written to
|
9
9
|
# @attr_reader rows [Array<Row>] The rows being written
|
10
10
|
# rubocop:disable Metrics/ClassLength
|
11
11
|
class RubyXLBuilder
|
12
12
|
extend ::T::Sig
|
13
|
+
include ::CSVPlusPlus::Writer::Merger
|
13
14
|
|
14
15
|
RubyXLCell = ::T.type_alias { ::T.all(::RubyXL::Cell, ::RubyXL::CellConvenienceMethods) }
|
15
16
|
public_constant :RubyXLCell
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
RubyXLValue = ::T.type_alias { ::T.any(::String, ::Numeric, ::Date) }
|
19
|
+
public_constant :RubyXLValue
|
19
20
|
|
20
21
|
sig { returns(::T::Array[::CSVPlusPlus::Row]) }
|
21
22
|
attr_reader :rows
|
22
23
|
|
23
24
|
sig do
|
24
25
|
params(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
sheet_name: ::T.nilable(::String)
|
26
|
+
options: ::CSVPlusPlus::Options::FileOptions,
|
27
|
+
position: ::CSVPlusPlus::Runtime::Position,
|
28
|
+
rows: ::T::Array[::CSVPlusPlus::Row]
|
29
29
|
).void
|
30
30
|
end
|
31
|
-
# @param
|
31
|
+
# @param options [Options::FileOptions]
|
32
|
+
# @param position [Position] The current position
|
32
33
|
# @param rows [Array<Row>] The rows to write
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
def initialize(options:, position:, rows:)
|
35
|
+
@options = options
|
36
|
+
@position = position
|
36
37
|
@rows = rows
|
37
|
-
@input_filename = input_filename
|
38
|
-
@runtime = runtime
|
39
|
-
@sheet_name = sheet_name
|
40
38
|
@worksheet = ::T.let(open_worksheet, ::RubyXL::Worksheet)
|
39
|
+
@reader = ::T.let(::CSVPlusPlus::Reader::RubyXL.new(@options, @worksheet), ::CSVPlusPlus::Reader::RubyXL)
|
41
40
|
end
|
42
41
|
|
43
42
|
sig { returns(::RubyXL::Workbook) }
|
@@ -52,24 +51,12 @@ module CSVPlusPlus
|
|
52
51
|
private
|
53
52
|
|
54
53
|
sig { void }
|
55
|
-
# rubocop:disable Metrics/MethodLength
|
56
54
|
def build_workbook!
|
57
|
-
@
|
58
|
-
|
59
|
-
|
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)
|
63
|
-
end
|
64
|
-
|
65
|
-
format_cell!(
|
66
|
-
@runtime.row_index,
|
67
|
-
@runtime.cell_index,
|
68
|
-
::T.cast(cell.modifier, ::CSVPlusPlus::Modifier::RubyXLModifier)
|
69
|
-
)
|
55
|
+
@position.map_all_cells(@rows) do |cell|
|
56
|
+
build_cell(cell)
|
57
|
+
format_cell!(cell)
|
70
58
|
end
|
71
59
|
end
|
72
|
-
# rubocop:enable Metrics/MethodLength
|
73
60
|
|
74
61
|
sig do
|
75
62
|
params(
|
@@ -160,30 +147,68 @@ module CSVPlusPlus
|
|
160
147
|
cell.change_contents(cell.value)
|
161
148
|
end
|
162
149
|
|
150
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::T.nilable(::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLValue)) }
|
151
|
+
def value_to_rubyxl(cell)
|
152
|
+
return cell.value unless (ast = cell.ast)
|
153
|
+
|
154
|
+
case ast
|
155
|
+
when ::CSVPlusPlus::Entities::Number, ::CSVPlusPlus::Entities::Date
|
156
|
+
ast.value
|
157
|
+
else
|
158
|
+
ast.evaluate(@position)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
163
162
|
sig do
|
164
|
-
params(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
163
|
+
params(
|
164
|
+
cell: ::CSVPlusPlus::Cell,
|
165
|
+
existing_value: ::T.nilable(::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLValue)
|
166
|
+
).returns(::T.nilable(::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLValue))
|
167
|
+
end
|
168
|
+
def merge_value(cell, existing_value)
|
169
|
+
merge_cell_value(existing_value:, new_value: value_to_rubyxl(cell), options: @options)
|
170
|
+
end
|
171
|
+
|
172
|
+
sig { params(cell: ::CSVPlusPlus::Cell).void }
|
173
|
+
def format_cell!(cell)
|
174
|
+
modifier = ::T.cast(cell.modifier, ::CSVPlusPlus::Modifier::RubyXLModifier)
|
175
|
+
@reader.value_at(cell).tap do |rubyxl_cell|
|
176
|
+
do_alignments!(rubyxl_cell, modifier)
|
177
|
+
do_borders!(rubyxl_cell, modifier)
|
178
|
+
do_fill!(rubyxl_cell, modifier)
|
179
|
+
do_fonts!(rubyxl_cell, modifier)
|
180
|
+
do_formats!(rubyxl_cell, modifier)
|
181
|
+
do_number_formats!(rubyxl_cell, modifier)
|
174
182
|
end
|
175
183
|
end
|
176
184
|
|
177
185
|
sig { returns(::RubyXL::Worksheet) }
|
178
186
|
def open_worksheet
|
179
|
-
if
|
180
|
-
workbook = ::RubyXL::Parser.parse(@
|
181
|
-
workbook[@sheet_name] || workbook.add_worksheet(@sheet_name)
|
187
|
+
if ::File.exist?(@options.output_filename)
|
188
|
+
workbook = ::RubyXL::Parser.parse(@options.output_filename)
|
189
|
+
workbook[@options.sheet_name] || workbook.add_worksheet(@options.sheet_name)
|
182
190
|
else
|
183
191
|
workbook = ::RubyXL::Workbook.new
|
184
|
-
workbook.worksheets[0].tap { |w| w.sheet_name = @sheet_name }
|
192
|
+
workbook.worksheets[0].tap { |w| w.sheet_name = @options.sheet_name }
|
185
193
|
end
|
186
194
|
end
|
195
|
+
|
196
|
+
sig { params(cell: ::CSVPlusPlus::Cell).void }
|
197
|
+
# rubocop:disable Metrics/MethodLength
|
198
|
+
def build_cell(cell)
|
199
|
+
if (existing_cell = @reader.value_at(cell))
|
200
|
+
merged_value = merge_value(cell, existing_cell.value)
|
201
|
+
existing_cell.change_contents(
|
202
|
+
cell.ast ? '' : merged_value,
|
203
|
+
cell.ast ? merged_value : nil
|
204
|
+
)
|
205
|
+
elsif (ast = cell.ast)
|
206
|
+
@worksheet.add_cell(cell.row_index, cell.index, '', ast.evaluate(@position))
|
207
|
+
else
|
208
|
+
@worksheet.add_cell(cell.row_index, cell.index, cell.value)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
# rubocop:enable Metrics/MethodLength
|
187
212
|
end
|
188
213
|
# rubocop:enable Metrics/ClassLength
|
189
214
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Writer
|
6
|
+
# Some shared functionality that all Writers should build on
|
7
|
+
#
|
8
|
+
# @attr_reader position [Position] The current position - needed to resolve variables and display useful error
|
9
|
+
# messages
|
10
|
+
class Writer
|
11
|
+
extend ::T::Sig
|
12
|
+
extend ::T::Helpers
|
13
|
+
|
14
|
+
abstract!
|
15
|
+
|
16
|
+
sig { returns(::CSVPlusPlus::Runtime::Position) }
|
17
|
+
attr_reader :position
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
sig do
|
22
|
+
params(position: ::CSVPlusPlus::Runtime::Position).void
|
23
|
+
end
|
24
|
+
# Open a CSV outputter to the +output_filename+ specified by the +Options+
|
25
|
+
#
|
26
|
+
# @param position [Position] The current position.
|
27
|
+
def initialize(position)
|
28
|
+
@position = position
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { abstract.params(template: ::CSVPlusPlus::Template).void }
|
32
|
+
# Write the given +template+.
|
33
|
+
#
|
34
|
+
# @param template [Template]
|
35
|
+
def write(template); end
|
36
|
+
|
37
|
+
sig { abstract.void }
|
38
|
+
# Write a backup of the current spreadsheet.
|
39
|
+
def write_backup; end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|