csv_plus_plus 0.1.2 → 0.2.0
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 +9 -5
- data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +29 -41
- data/lib/csv_plus_plus/cli.rb +53 -80
- data/lib/csv_plus_plus/cli_flag.rb +71 -71
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +98 -66
- data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
- data/lib/csv_plus_plus/entities/boolean.rb +26 -10
- data/lib/csv_plus_plus/entities/builtins.rb +66 -24
- data/lib/csv_plus_plus/entities/date.rb +42 -6
- data/lib/csv_plus_plus/entities/entity.rb +17 -69
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
- data/lib/csv_plus_plus/entities/function.rb +34 -11
- data/lib/csv_plus_plus/entities/function_call.rb +49 -10
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +30 -11
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
- data/lib/csv_plus_plus/entities/string.rb +23 -7
- data/lib/csv_plus_plus/entities.rb +7 -16
- 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 +25 -2
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +5 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +25 -10
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
- data/lib/csv_plus_plus/lexer.rb +64 -1
- 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 +78 -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 +89 -160
- 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 +34 -77
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
- data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
- data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
- 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/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position.rb +242 -0
- data/lib/csv_plus_plus/runtime/references.rb +115 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +71 -0
- data/lib/csv_plus_plus/template.rb +71 -39
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/csv.rb +37 -8
- data/lib/csv_plus_plus/writer/excel.rb +25 -5
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
- data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
- data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
- data/lib/csv_plus_plus/writer/merger.rb +31 -0
- data/lib/csv_plus_plus/writer/open_document.rb +21 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +79 -10
- data/lib/csv_plus_plus.rb +47 -18
- metadata +50 -21
- 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/entities/cell_reference.rb +0 -60
- data/lib/csv_plus_plus/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/google_options.rb +0 -27
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
- 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/base_writer.rb +0 -20
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module CSVPlusPlus
|
|
5
|
+
module Writer
|
|
6
|
+
# Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
|
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
|
8
|
+
class GoogleSheetsBuilder
|
|
9
|
+
extend ::T::Sig
|
|
10
|
+
include ::CSVPlusPlus::Writer::Merger
|
|
11
|
+
|
|
12
|
+
sig do
|
|
13
|
+
params(
|
|
14
|
+
options: ::CSVPlusPlus::Options::GoogleSheetsOptions,
|
|
15
|
+
position: ::CSVPlusPlus::Runtime::Position,
|
|
16
|
+
reader: ::CSVPlusPlus::Reader::GoogleSheets,
|
|
17
|
+
rows: ::T::Array[::CSVPlusPlus::Row]
|
|
18
|
+
).void
|
|
19
|
+
end
|
|
20
|
+
# @param options [Options]
|
|
21
|
+
# @param position [Position] The current position.
|
|
22
|
+
# @param reader [::CSVPlusPlus::Reader::GoogleSheets]
|
|
23
|
+
# @param rows [Array<Row>] The rows to render
|
|
24
|
+
def initialize(options:, position:, reader:, rows:)
|
|
25
|
+
@options = options
|
|
26
|
+
@position = position
|
|
27
|
+
@reader = reader
|
|
28
|
+
@rows = rows
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { returns(::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest) }
|
|
32
|
+
# Build a Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest
|
|
33
|
+
#
|
|
34
|
+
# @return [Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest]
|
|
35
|
+
def batch_update_spreadsheet_request
|
|
36
|
+
build_batch_request(@rows)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
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
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
# rubocop:enable Metrics/MethodLength
|
|
58
|
+
|
|
59
|
+
sig { params(mod: ::CSVPlusPlus::Modifier::GoogleSheetModifier).returns(::Google::Apis::SheetsV4::CellFormat) }
|
|
60
|
+
def build_cell_format(mod)
|
|
61
|
+
::Google::Apis::SheetsV4::CellFormat.new.tap do |cf|
|
|
62
|
+
cf.text_format = mod.text_format
|
|
63
|
+
|
|
64
|
+
cf.horizontal_alignment = mod.horizontal_alignment
|
|
65
|
+
cf.vertical_alignment = mod.vertical_alignment
|
|
66
|
+
cf.background_color = mod.background_color
|
|
67
|
+
cf.number_format = mod.number_format
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::GridRange) }
|
|
72
|
+
def grid_range_for_cell(cell)
|
|
73
|
+
::Google::Apis::SheetsV4::GridRange.new(
|
|
74
|
+
start_column_index: cell.index,
|
|
75
|
+
end_column_index: cell.index + 1,
|
|
76
|
+
start_row_index: cell.row_index,
|
|
77
|
+
end_row_index: cell.row_index + 1
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::ExtendedValue) }
|
|
82
|
+
def build_cell_value(cell)
|
|
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
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::CellData) }
|
|
93
|
+
def build_cell_data(cell)
|
|
94
|
+
mod = cell.modifier
|
|
95
|
+
|
|
96
|
+
::Google::Apis::SheetsV4::CellData.new.tap do |cd|
|
|
97
|
+
cd.user_entered_format = build_cell_format(::T.cast(mod, ::CSVPlusPlus::Modifier::GoogleSheetModifier))
|
|
98
|
+
cd.note = mod.note if mod.note
|
|
99
|
+
|
|
100
|
+
# TODO: apply data validation
|
|
101
|
+
cd.user_entered_value = build_cell_value(cell)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
sig { params(row: ::CSVPlusPlus::Row).returns(::Google::Apis::SheetsV4::RowData) }
|
|
106
|
+
def build_row_data(row)
|
|
107
|
+
::Google::Apis::SheetsV4::RowData.new(values: row.cells.map { |cell| build_cell_data(cell) })
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sig { params(rows: ::T::Array[::CSVPlusPlus::Row]).returns(::Google::Apis::SheetsV4::UpdateCellsRequest) }
|
|
111
|
+
def build_update_cells_request(rows)
|
|
112
|
+
::Google::Apis::SheetsV4::UpdateCellsRequest.new(
|
|
113
|
+
fields: '*',
|
|
114
|
+
start: ::Google::Apis::SheetsV4::GridCoordinate.new(
|
|
115
|
+
column_index: @options.offset[1],
|
|
116
|
+
row_index: @options.offset[0]
|
|
117
|
+
),
|
|
118
|
+
rows:
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::UpdateBordersRequest) }
|
|
123
|
+
def build_border(cell)
|
|
124
|
+
mod = ::T.cast(cell.modifier, ::CSVPlusPlus::Modifier::GoogleSheetModifier)
|
|
125
|
+
border = mod.border
|
|
126
|
+
|
|
127
|
+
::Google::Apis::SheetsV4::UpdateBordersRequest.new(
|
|
128
|
+
top: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Top) ? border : nil,
|
|
129
|
+
right: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Right) ? border : nil,
|
|
130
|
+
left: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Left) ? border : nil,
|
|
131
|
+
bottom: mod.border_along?(::CSVPlusPlus::Modifier::BorderSide::Bottom) ? border : nil,
|
|
132
|
+
range: grid_range_for_cell(cell)
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::Google::Apis::SheetsV4::Request) }
|
|
137
|
+
def build_update_borders_request(cell)
|
|
138
|
+
::Google::Apis::SheetsV4::Request.new(update_borders: build_border(cell))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
sig { params(rows: ::T::Array[::CSVPlusPlus::Row]).returns(::T::Array[::Google::Apis::SheetsV4::Request]) }
|
|
142
|
+
# rubocop:disable Metrics/MethodLength
|
|
143
|
+
def chunked_requests(rows)
|
|
144
|
+
accum = []
|
|
145
|
+
[].tap do |chunked|
|
|
146
|
+
@position.map_rows(rows) do |row|
|
|
147
|
+
accum << build_row_data(row)
|
|
148
|
+
next unless accum.length == 1000
|
|
149
|
+
|
|
150
|
+
chunked << ::Google::Apis::SheetsV4::Request.new(update_cells: build_update_cells_request(accum))
|
|
151
|
+
accum = []
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
unless accum.empty?
|
|
155
|
+
chunked << ::Google::Apis::SheetsV4::Request.new(update_cells: build_update_cells_request(accum))
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
# rubocop:enable Metrics/MethodLength
|
|
160
|
+
|
|
161
|
+
sig do
|
|
162
|
+
params(rows: ::T::Array[::CSVPlusPlus::Row]).returns(::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest)
|
|
163
|
+
end
|
|
164
|
+
def build_batch_request(rows)
|
|
165
|
+
::Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new.tap do |bu|
|
|
166
|
+
bu.requests = chunked_requests(rows)
|
|
167
|
+
|
|
168
|
+
@position.map_rows(rows) do |row|
|
|
169
|
+
row.cells.filter { |c| c.modifier.any_border? }
|
|
170
|
+
.each do |cell|
|
|
171
|
+
bu.requests << build_update_borders_request(cell)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
# rubocop:enable Metrics/ClassLength
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -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
|
|
@@ -1,14 +1,33 @@
|
|
|
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
|
-
class OpenDocument < ::CSVPlusPlus::Writer::
|
|
7
|
+
class OpenDocument < ::CSVPlusPlus::Writer::Writer
|
|
8
|
+
extend ::T::Sig
|
|
9
|
+
include ::CSVPlusPlus::Writer::FileBackerUpper
|
|
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
|
+
|
|
20
|
+
sig { override.params(template: ::CSVPlusPlus::Template).void }
|
|
8
21
|
# write a +template+ to an OpenDocument file
|
|
9
22
|
def write(template)
|
|
10
23
|
# TODO
|
|
11
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
|
|
12
31
|
end
|
|
13
32
|
end
|
|
14
33
|
end
|
|
@@ -1,50 +1,80 @@
|
|
|
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
|
-
# @attr_reader
|
|
8
|
+
# @attr_reader output_filename [Pathname, nil] 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
|
+
include ::CSVPlusPlus::Writer::Merger
|
|
14
|
+
|
|
15
|
+
RubyXLCell = ::T.type_alias { ::T.all(::RubyXL::Cell, ::RubyXL::CellConvenienceMethods) }
|
|
16
|
+
public_constant :RubyXLCell
|
|
17
|
+
|
|
18
|
+
RubyXLValue = ::T.type_alias { ::T.any(::String, ::Numeric, ::Date) }
|
|
19
|
+
public_constant :RubyXLValue
|
|
20
|
+
|
|
21
|
+
sig { returns(::T::Array[::CSVPlusPlus::Row]) }
|
|
22
|
+
attr_reader :rows
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
sig do
|
|
25
|
+
params(
|
|
26
|
+
options: ::CSVPlusPlus::Options::FileOptions,
|
|
27
|
+
position: ::CSVPlusPlus::Runtime::Position,
|
|
28
|
+
rows: ::T::Array[::CSVPlusPlus::Row]
|
|
29
|
+
).void
|
|
30
|
+
end
|
|
31
|
+
# @param options [Options::FileOptions]
|
|
32
|
+
# @param position [Position] The current position
|
|
15
33
|
# @param rows [Array<Row>] The rows to write
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
def initialize(options:, position:, rows:)
|
|
35
|
+
@options = options
|
|
36
|
+
@position = position
|
|
18
37
|
@rows = rows
|
|
19
|
-
@
|
|
20
|
-
@
|
|
38
|
+
@worksheet = ::T.let(open_worksheet, ::RubyXL::Worksheet)
|
|
39
|
+
@reader = ::T.let(::CSVPlusPlus::Reader::RubyXL.new(@options, @worksheet), ::CSVPlusPlus::Reader::RubyXL)
|
|
21
40
|
end
|
|
22
41
|
|
|
42
|
+
sig { returns(::RubyXL::Workbook) }
|
|
23
43
|
# Build a +RubyXL::Workbook+ with the given +@rows+ in +sheet_name+
|
|
24
44
|
#
|
|
25
45
|
# @return [RubyXL::Workbook]
|
|
26
46
|
def build_workbook
|
|
27
|
-
|
|
47
|
+
build_workbook!
|
|
48
|
+
@worksheet.workbook
|
|
28
49
|
end
|
|
29
50
|
|
|
30
51
|
private
|
|
31
52
|
|
|
53
|
+
sig { void }
|
|
32
54
|
def build_workbook!
|
|
33
|
-
@rows
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@worksheet.add_cell(x, y, cell.to_csv)
|
|
38
|
-
format_cell!(x, y, modifier)
|
|
39
|
-
end
|
|
55
|
+
@position.map_all_cells(@rows) do |cell|
|
|
56
|
+
build_cell(cell)
|
|
57
|
+
format_cell!(cell)
|
|
40
58
|
end
|
|
41
59
|
end
|
|
42
60
|
|
|
61
|
+
sig do
|
|
62
|
+
params(
|
|
63
|
+
cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
|
|
64
|
+
modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
|
|
65
|
+
).void
|
|
66
|
+
end
|
|
43
67
|
def do_alignments!(cell, modifier)
|
|
44
|
-
cell.change_horizontal_alignment(modifier.
|
|
45
|
-
cell.change_vertical_alignment(modifier.
|
|
68
|
+
cell.change_horizontal_alignment(modifier.horizontal_alignment) if modifier.halign
|
|
69
|
+
cell.change_vertical_alignment(modifier.vertical_alignment) if modifier.valign
|
|
46
70
|
end
|
|
47
71
|
|
|
72
|
+
sig do
|
|
73
|
+
params(
|
|
74
|
+
cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
|
|
75
|
+
modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
|
|
76
|
+
).void
|
|
77
|
+
end
|
|
48
78
|
# rubocop:disable Metrics/MethodLength
|
|
49
79
|
def do_borders!(cell, modifier)
|
|
50
80
|
return unless modifier.any_border?
|
|
@@ -59,29 +89,56 @@ module CSVPlusPlus
|
|
|
59
89
|
end
|
|
60
90
|
else
|
|
61
91
|
modifier.borders.each do |direction|
|
|
62
|
-
|
|
92
|
+
# TODO: move direction.serialize into the RubyXLModifier
|
|
93
|
+
cell.change_border(direction.serialize, color || weight)
|
|
63
94
|
end
|
|
64
95
|
end
|
|
65
96
|
end
|
|
66
97
|
# rubocop:enable Metrics/MethodLength
|
|
67
98
|
|
|
99
|
+
sig do
|
|
100
|
+
params(
|
|
101
|
+
cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
|
|
102
|
+
modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
|
|
103
|
+
).void
|
|
104
|
+
end
|
|
68
105
|
def do_fill!(cell, modifier)
|
|
69
|
-
|
|
106
|
+
return unless modifier.color
|
|
107
|
+
|
|
108
|
+
cell.change_fill(modifier.color&.to_hex)
|
|
70
109
|
end
|
|
71
110
|
|
|
111
|
+
sig do
|
|
112
|
+
params(
|
|
113
|
+
cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
|
|
114
|
+
modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
|
|
115
|
+
).void
|
|
116
|
+
end
|
|
72
117
|
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?(
|
|
118
|
+
cell.change_font_bold(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Bold)
|
|
119
|
+
cell.change_font_italics(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Italic)
|
|
120
|
+
cell.change_font_underline(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Underline)
|
|
121
|
+
cell.change_font_strikethrough(true) if modifier.formatted?(::CSVPlusPlus::Modifier::TextFormat::Strikethrough)
|
|
77
122
|
end
|
|
78
123
|
|
|
124
|
+
sig do
|
|
125
|
+
params(
|
|
126
|
+
cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
|
|
127
|
+
modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
|
|
128
|
+
).void
|
|
129
|
+
end
|
|
79
130
|
def do_fonts!(cell, modifier)
|
|
80
|
-
cell.change_font_color(modifier.fontcolor.to_hex) if modifier.fontcolor
|
|
131
|
+
cell.change_font_color(::T.must(modifier.fontcolor).to_hex) if modifier.fontcolor
|
|
81
132
|
cell.change_font_name(modifier.fontfamily) if modifier.fontfamily
|
|
82
133
|
cell.change_font_size(modifier.fontsize) if modifier.fontsize
|
|
83
134
|
end
|
|
84
135
|
|
|
136
|
+
sig do
|
|
137
|
+
params(
|
|
138
|
+
cell: ::CSVPlusPlus::Writer::RubyXLBuilder::RubyXLCell,
|
|
139
|
+
modifier: ::CSVPlusPlus::Modifier::RubyXLModifier
|
|
140
|
+
).void
|
|
141
|
+
end
|
|
85
142
|
def do_number_formats!(cell, modifier)
|
|
86
143
|
return unless modifier.numberformat
|
|
87
144
|
|
|
@@ -90,28 +147,69 @@ module CSVPlusPlus
|
|
|
90
147
|
cell.change_contents(cell.value)
|
|
91
148
|
end
|
|
92
149
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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)
|
|
101
159
|
end
|
|
102
160
|
end
|
|
103
161
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
::
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
sig do
|
|
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)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
sig { returns(::RubyXL::Worksheet) }
|
|
186
|
+
def open_worksheet
|
|
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)
|
|
109
190
|
else
|
|
110
|
-
::RubyXL::Workbook.new
|
|
111
|
-
|
|
112
|
-
end
|
|
191
|
+
workbook = ::RubyXL::Workbook.new
|
|
192
|
+
workbook.worksheets[0].tap { |w| w.sheet_name = @options.sheet_name }
|
|
113
193
|
end
|
|
114
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
|
|
115
212
|
end
|
|
213
|
+
# rubocop:enable Metrics/ClassLength
|
|
116
214
|
end
|
|
117
215
|
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
|
data/lib/csv_plus_plus/writer.rb
CHANGED
|
@@ -1,24 +1,93 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require_relative './writer/
|
|
4
|
+
require_relative './writer/merger'
|
|
5
|
+
require_relative './writer/writer'
|
|
6
|
+
|
|
4
7
|
require_relative './writer/csv'
|
|
5
8
|
require_relative './writer/excel'
|
|
6
9
|
require_relative './writer/google_sheets'
|
|
10
|
+
require_relative './writer/google_sheets_builder'
|
|
7
11
|
require_relative './writer/open_document'
|
|
12
|
+
require_relative './writer/rubyxl_builder'
|
|
8
13
|
|
|
9
14
|
module CSVPlusPlus
|
|
10
|
-
# Various strategies for writing to various formats (excel, google sheets, CSV
|
|
15
|
+
# Various strategies for writing to various formats (excel, google sheets, CSV & OpenDocument (not yet implemented))
|
|
11
16
|
module Writer
|
|
17
|
+
extend ::T::Sig
|
|
18
|
+
|
|
19
|
+
sig do
|
|
20
|
+
params(
|
|
21
|
+
options: ::CSVPlusPlus::Options::Options,
|
|
22
|
+
position: ::CSVPlusPlus::Runtime::Position
|
|
23
|
+
).returns(
|
|
24
|
+
::T.any(
|
|
25
|
+
::CSVPlusPlus::Writer::CSV,
|
|
26
|
+
::CSVPlusPlus::Writer::Excel,
|
|
27
|
+
::CSVPlusPlus::Writer::GoogleSheets,
|
|
28
|
+
::CSVPlusPlus::Writer::OpenDocument
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
12
32
|
# Return an instance of a writer depending on the given +options+
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
#
|
|
34
|
+
# @param options [Options] The supplied options.
|
|
35
|
+
# @param position [Position] The current position.
|
|
36
|
+
#
|
|
37
|
+
# @return [Writer::CSV | Writer::Excel | Writer::GoogleSheets | Writer::OpenDocument]
|
|
38
|
+
def self.writer(options, position)
|
|
39
|
+
output_format = options.output_format
|
|
40
|
+
case output_format
|
|
41
|
+
when ::CSVPlusPlus::Options::OutputFormat::CSV then csv(options, position)
|
|
42
|
+
when ::CSVPlusPlus::Options::OutputFormat::Excel then excel(options, position)
|
|
43
|
+
when ::CSVPlusPlus::Options::OutputFormat::GoogleSheets then google_sheets(options, position)
|
|
44
|
+
when ::CSVPlusPlus::Options::OutputFormat::OpenDocument then open_document(options, position)
|
|
45
|
+
else ::T.absurd(output_format)
|
|
21
46
|
end
|
|
22
47
|
end
|
|
48
|
+
|
|
49
|
+
sig do
|
|
50
|
+
params(
|
|
51
|
+
options: ::CSVPlusPlus::Options::Options,
|
|
52
|
+
position: ::CSVPlusPlus::Runtime::Position
|
|
53
|
+
).returns(::CSVPlusPlus::Writer::CSV)
|
|
54
|
+
end
|
|
55
|
+
# Instantiate a +CSV+ writer
|
|
56
|
+
def self.csv(options, position)
|
|
57
|
+
::CSVPlusPlus::Writer::CSV.new(::T.cast(options, ::CSVPlusPlus::Options::FileOptions), position)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
sig do
|
|
61
|
+
params(
|
|
62
|
+
options: ::CSVPlusPlus::Options::Options,
|
|
63
|
+
position: ::CSVPlusPlus::Runtime::Position
|
|
64
|
+
).returns(::CSVPlusPlus::Writer::Excel)
|
|
65
|
+
end
|
|
66
|
+
# Instantiate a +Excel+ writer
|
|
67
|
+
def self.excel(options, position)
|
|
68
|
+
::CSVPlusPlus::Writer::Excel.new(::T.cast(options, ::CSVPlusPlus::Options::FileOptions), position)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sig do
|
|
72
|
+
params(
|
|
73
|
+
options: ::CSVPlusPlus::Options::Options,
|
|
74
|
+
position: ::CSVPlusPlus::Runtime::Position
|
|
75
|
+
).returns(::CSVPlusPlus::Writer::GoogleSheets)
|
|
76
|
+
end
|
|
77
|
+
# Instantiate a +GoogleSheets+ writer
|
|
78
|
+
def self.google_sheets(options, position)
|
|
79
|
+
::CSVPlusPlus::Writer::GoogleSheets.new(::T.cast(options, ::CSVPlusPlus::Options::GoogleSheetsOptions), position)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sig do
|
|
83
|
+
params(
|
|
84
|
+
options: ::CSVPlusPlus::Options::Options,
|
|
85
|
+
position: ::CSVPlusPlus::Runtime::Position
|
|
86
|
+
).returns(::CSVPlusPlus::Writer::OpenDocument)
|
|
87
|
+
end
|
|
88
|
+
# Instantiate an +OpenDocument+ writer
|
|
89
|
+
def self.open_document(options, position)
|
|
90
|
+
::CSVPlusPlus::Writer::OpenDocument.new(::T.cast(options, ::CSVPlusPlus::Options::FileOptions), position)
|
|
91
|
+
end
|
|
23
92
|
end
|
|
24
93
|
end
|