csv_plus_plus 0.1.3 → 0.2.1
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 +13 -3
- data/docs/CHANGELOG.md +18 -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 +77 -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 +102 -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 +56 -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 +43 -18
- 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,56 @@
|
|
|
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
|
+
# Consistently enforce our strategy for resolving differences between new changes and existing. By default we
|
|
20
|
+
# overwrite values that are currently in the spreadsheet but you can override that with the --safe flag
|
|
21
|
+
def merge_cell_value(existing_value:, new_value:, options:)
|
|
22
|
+
merged_value = merge_with_strategy(existing_value:, new_value:, options:)
|
|
23
|
+
|
|
24
|
+
return merged_value unless options.verbose
|
|
25
|
+
|
|
26
|
+
if options.overwrite_values && merged_value != existing_value
|
|
27
|
+
warn("Overwriting existing value: \"#{existing_value}\" with \"#{new_value}\"")
|
|
28
|
+
# rubocop:disable Style/MissingElse
|
|
29
|
+
elsif !options.overwrite_values && new_value != merged_value
|
|
30
|
+
# rubocop:enable Style/MissingElse
|
|
31
|
+
warn("Keeping old value: \"#{existing_value}\" rather than new value: \"#{new_value}\"")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
merged_value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
sig do
|
|
40
|
+
type_parameters(:V)
|
|
41
|
+
.params(
|
|
42
|
+
existing_value: ::T.nilable(::T.all(::T.type_parameter(:V), ::BasicObject)),
|
|
43
|
+
new_value: ::T.nilable(::T.all(::T.type_parameter(:V), ::BasicObject)),
|
|
44
|
+
options: ::CSVPlusPlus::Options::Options
|
|
45
|
+
).returns(::T.nilable(::T.type_parameter(:V)))
|
|
46
|
+
end
|
|
47
|
+
def merge_with_strategy(existing_value:, new_value:, options:)
|
|
48
|
+
if options.overwrite_values
|
|
49
|
+
new_value || existing_value
|
|
50
|
+
else
|
|
51
|
+
existing_value || new_value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
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
|