csv_plus_plus 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/csv_plus_plus/cell.rb +51 -0
- data/lib/csv_plus_plus/code_section.rb +49 -0
- data/lib/csv_plus_plus/color.rb +22 -0
- data/lib/csv_plus_plus/expand.rb +18 -0
- data/lib/csv_plus_plus/google_options.rb +23 -0
- data/lib/csv_plus_plus/graph.rb +68 -0
- data/lib/csv_plus_plus/language/cell_value.tab.rb +333 -0
- data/lib/csv_plus_plus/language/code_section.tab.rb +443 -0
- data/lib/csv_plus_plus/language/compiler.rb +170 -0
- data/lib/csv_plus_plus/language/entities/boolean.rb +32 -0
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +26 -0
- data/lib/csv_plus_plus/language/entities/entity.rb +70 -0
- data/lib/csv_plus_plus/language/entities/function.rb +33 -0
- data/lib/csv_plus_plus/language/entities/function_call.rb +25 -0
- data/lib/csv_plus_plus/language/entities/number.rb +34 -0
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +27 -0
- data/lib/csv_plus_plus/language/entities/string.rb +29 -0
- data/lib/csv_plus_plus/language/entities/variable.rb +25 -0
- data/lib/csv_plus_plus/language/entities.rb +28 -0
- data/lib/csv_plus_plus/language/references.rb +53 -0
- data/lib/csv_plus_plus/language/runtime.rb +147 -0
- data/lib/csv_plus_plus/language/scope.rb +199 -0
- data/lib/csv_plus_plus/language/syntax_error.rb +61 -0
- data/lib/csv_plus_plus/lexer/lexer.rb +64 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +65 -0
- data/lib/csv_plus_plus/lexer.rb +14 -0
- data/lib/csv_plus_plus/modifier.rb +124 -0
- data/lib/csv_plus_plus/modifier.tab.rb +921 -0
- data/lib/csv_plus_plus/options.rb +70 -0
- data/lib/csv_plus_plus/row.rb +42 -0
- data/lib/csv_plus_plus/template.rb +61 -0
- data/lib/csv_plus_plus/version.rb +6 -0
- data/lib/csv_plus_plus/writer/base_writer.rb +21 -0
- data/lib/csv_plus_plus/writer/csv.rb +31 -0
- data/lib/csv_plus_plus/writer/excel.rb +13 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +173 -0
- data/lib/csv_plus_plus/writer/google_sheets.rb +139 -0
- data/lib/csv_plus_plus/writer/open_document.rb +14 -0
- data/lib/csv_plus_plus/writer.rb +25 -0
- data/lib/csv_plus_plus.rb +20 -0
- metadata +83 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './google_options'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
# The options a user can supply
|
7
|
+
class Options
|
8
|
+
attr_accessor :backup, :create_if_not_exists, :key_values, :offset, :output_filename, :sheet_name, :verbose
|
9
|
+
attr_reader :google
|
10
|
+
|
11
|
+
# initialize
|
12
|
+
def initialize
|
13
|
+
@offset = [0, 0]
|
14
|
+
@create_if_not_exists = false
|
15
|
+
@key_values = {}
|
16
|
+
@verbose = false
|
17
|
+
# TODO: switch to true? probably a safer choice
|
18
|
+
@backup = false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set the Google Sheet ID
|
22
|
+
def google_sheet_id=(sheet_id)
|
23
|
+
@google = ::CSVPlusPlus::GoogleOptions.new(sheet_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an error string or nil if there are no validation problems
|
27
|
+
def validate
|
28
|
+
return if @google || @output_filename
|
29
|
+
|
30
|
+
'You must supply either a Google Sheet ID or an output file'
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a string with a verbose description of what we're doing with the options
|
34
|
+
def verbose_summary
|
35
|
+
<<~SUMMARY
|
36
|
+
#{summary_divider}
|
37
|
+
|
38
|
+
# csv++ Command Options
|
39
|
+
|
40
|
+
> Input filename | #{@filename}
|
41
|
+
> Sheet name | #{@sheet_name}
|
42
|
+
> Create sheet if it does not exist? | #{@create_if_not_exists}
|
43
|
+
> Spreadsheet row-offset | #{@offset[0]}
|
44
|
+
> Spreadsheet cell-offset | #{@offset[1]}
|
45
|
+
> User-supplied key-values | #{@key_values}
|
46
|
+
> Verbose | #{@verbose}
|
47
|
+
|
48
|
+
## Output Options
|
49
|
+
|
50
|
+
> Backup | #{@backup}
|
51
|
+
> Output filename | #{@output_filename}
|
52
|
+
|
53
|
+
#{@google&.verbose_summary || ''}
|
54
|
+
#{summary_divider}
|
55
|
+
SUMMARY
|
56
|
+
end
|
57
|
+
|
58
|
+
# to_s
|
59
|
+
def to_s
|
60
|
+
"Options(create_if_not_exists: #{@create_if_not_exists}, google: #{@google}, key_values: #{@key_values}, " \
|
61
|
+
"offset: #{@offset}, sheet_name: #{@sheet_name}, verbose: #{@verbose})"
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def summary_divider
|
67
|
+
'========================================================================='
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'cell'
|
4
|
+
require_relative 'modifier.tab'
|
5
|
+
|
6
|
+
module CSVPlusPlus
|
7
|
+
##
|
8
|
+
# A row of a template
|
9
|
+
class Row
|
10
|
+
attr_reader :cells, :index, :modifier
|
11
|
+
|
12
|
+
# initialize
|
13
|
+
def initialize(index, cells, modifier)
|
14
|
+
@cells = cells
|
15
|
+
@modifier = modifier
|
16
|
+
@index = index
|
17
|
+
end
|
18
|
+
|
19
|
+
# Set the row index. And update the index of all affected cells
|
20
|
+
def index=(index)
|
21
|
+
@index = index
|
22
|
+
@cells.each { |cell| cell.row_index = index }
|
23
|
+
end
|
24
|
+
|
25
|
+
# How much this row will expand itself, if at all (0)
|
26
|
+
def expand_amount
|
27
|
+
return 0 unless @modifier.expand
|
28
|
+
|
29
|
+
@modifier.expand.repetitions || (1000 - @index)
|
30
|
+
end
|
31
|
+
|
32
|
+
# to_s
|
33
|
+
def to_s
|
34
|
+
"Row(index: #{index}, modifier: #{modifier}, cells: #{cells})"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return a deep copy of this row
|
38
|
+
def deep_clone
|
39
|
+
::Marshal.load(::Marshal.dump(self))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
# Contains the flow and data from a code section and CSV section
|
5
|
+
class Template
|
6
|
+
attr_reader :rows, :scope
|
7
|
+
|
8
|
+
# initialize
|
9
|
+
def initialize(rows:, scope:)
|
10
|
+
@rows = rows
|
11
|
+
@scope = scope
|
12
|
+
end
|
13
|
+
|
14
|
+
# to_s
|
15
|
+
def to_s
|
16
|
+
"Template(rows: #{@rows}, scope: #{@scope})"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Apply any expand= modifiers to the parsed template
|
20
|
+
def expand_rows!
|
21
|
+
expanded_rows = []
|
22
|
+
row_index = 0
|
23
|
+
expand_rows(
|
24
|
+
lambda do |new_row|
|
25
|
+
new_row.index = row_index
|
26
|
+
expanded_rows << new_row
|
27
|
+
row_index += 1
|
28
|
+
end
|
29
|
+
)
|
30
|
+
|
31
|
+
@rows = expanded_rows
|
32
|
+
end
|
33
|
+
|
34
|
+
# Make sure that the template has a valid amount of infinite expand modifiers
|
35
|
+
def validate_infinite_expands(runtime)
|
36
|
+
infinite_expand_rows = @rows.filter { |r| r.modifier.expand&.infinite? }
|
37
|
+
return unless infinite_expand_rows.length > 1
|
38
|
+
|
39
|
+
runtime.raise_syntax_error(
|
40
|
+
'You can only have one infinite expand= (on all others you must specify an amount)',
|
41
|
+
infinite_expand_rows[1]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def expand_rows(push_row_fn)
|
48
|
+
# TODO: make it so that an infinite expand will not overwrite the rows below it, but
|
49
|
+
# instead merge with them
|
50
|
+
rows.each do |row|
|
51
|
+
if row.modifier.expand
|
52
|
+
row.expand_amount.times do
|
53
|
+
push_row_fn.call(row.deep_clone)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
push_row_fn.call(row)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Writer
|
5
|
+
##
|
6
|
+
# Some shared functionality that all Writers should build on
|
7
|
+
class BaseWriter
|
8
|
+
attr_accessor :options
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
# Open a CSV outputter to +filename+
|
13
|
+
def initialize(options)
|
14
|
+
@options = options
|
15
|
+
load_requires
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_requires; end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Writer
|
5
|
+
##
|
6
|
+
# A class that can output a +Template+ to CSV
|
7
|
+
class CSV < ::CSVPlusPlus::Writer::BaseWriter
|
8
|
+
# write a +template+ to CSV
|
9
|
+
def write(template)
|
10
|
+
# TODO: also read it and merge the results
|
11
|
+
::CSV.open(@options.output_filename, 'wb') do |csv|
|
12
|
+
template.rows.each do |row|
|
13
|
+
csv << build_row(row)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def load_requires
|
21
|
+
require('csv')
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_row(row)
|
27
|
+
row.cells.map(&:to_csv)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Writer
|
5
|
+
# A class that can output a +Template+ to an Excel file
|
6
|
+
class Excel < ::CSVPlusPlus::Writer::BaseWriter
|
7
|
+
# write a +template+ to an Excel file
|
8
|
+
def write(template)
|
9
|
+
# TODO
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Writer
|
5
|
+
##
|
6
|
+
# Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
8
|
+
class GoogleSheetBuilder
|
9
|
+
# initialize
|
10
|
+
def initialize(current_sheet_values:, sheet_id:, rows:, column_index: 0, row_index: 0)
|
11
|
+
@current_sheet_values = current_sheet_values
|
12
|
+
@sheet_id = sheet_id
|
13
|
+
@rows = rows
|
14
|
+
@column_index = column_index
|
15
|
+
@row_index = row_index
|
16
|
+
end
|
17
|
+
|
18
|
+
# Build a Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest
|
19
|
+
def batch_update_spreadsheet_request
|
20
|
+
build_batch_request(@rows)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def sheets_ns
|
26
|
+
::Google::Apis::SheetsV4
|
27
|
+
end
|
28
|
+
|
29
|
+
def sheets_color(color)
|
30
|
+
sheets_ns::Color.new(red: color.red, green: color.green, blue: color.blue)
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_extended_value_type!(extended_value, value)
|
34
|
+
v = value || ''
|
35
|
+
if v.start_with?('=')
|
36
|
+
extended_value.formula_value = value
|
37
|
+
elsif v.match(/^-?[\d.]+$/)
|
38
|
+
extended_value.number_value = value
|
39
|
+
elsif v.downcase == 'true' || v.downcase == 'false'
|
40
|
+
extended_value.boolean_value = value
|
41
|
+
else
|
42
|
+
extended_value.string_value = value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_text_format(mod)
|
47
|
+
sheets_ns::TextFormat.new(
|
48
|
+
bold: mod.formatted?('bold') || nil,
|
49
|
+
italic: mod.formatted?('italic') || nil,
|
50
|
+
strikethrough: mod.formatted?('strikethrough') || nil,
|
51
|
+
underline: mod.formatted?('underline') || nil,
|
52
|
+
|
53
|
+
font_family: mod.fontfamily,
|
54
|
+
font_size: mod.fontsize,
|
55
|
+
|
56
|
+
foreground_color: mod.fontcolor ? sheets_color(mod.fontcolor) : nil
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
61
|
+
def build_cell_format(mod)
|
62
|
+
sheets_ns::CellFormat.new.tap do |cf|
|
63
|
+
cf.text_format = build_text_format(mod)
|
64
|
+
|
65
|
+
# TODO: are these not overwriting each other?
|
66
|
+
cf.horizontal_alignment = 'LEFT' if mod.aligned?('left')
|
67
|
+
cf.horizontal_alignment = 'RIGHT' if mod.aligned?('right')
|
68
|
+
cf.horizontal_alignment = 'CENTER' if mod.aligned?('center')
|
69
|
+
cf.vertical_alignment = 'TOP' if mod.aligned?('top')
|
70
|
+
cf.vertical_alignment = 'BOTTOM' if mod.aligned?('bottom')
|
71
|
+
|
72
|
+
cf.background_color = sheets_color(mod.color) if mod.color
|
73
|
+
|
74
|
+
cf.number_format = sheets_ns::NumberFormat.new(type: mod.numberformat) if mod.numberformat
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
78
|
+
|
79
|
+
def grid_range_for_cell(cell)
|
80
|
+
sheets_ns::GridRange.new(
|
81
|
+
sheet_id: @sheet_id,
|
82
|
+
start_column_index: cell.index,
|
83
|
+
end_column_index: cell.index + 1,
|
84
|
+
start_row_index: cell.row_index,
|
85
|
+
end_row_index: cell.row_index + 1
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def current_value(row_index, cell_index)
|
90
|
+
@current_values[row_index][cell_index]
|
91
|
+
rescue ::StandardError
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_cell_value(cell)
|
96
|
+
sheets_ns::ExtendedValue.new.tap do |xv|
|
97
|
+
value =
|
98
|
+
if cell.value.nil?
|
99
|
+
current_value(cell.row_index, cell.index)
|
100
|
+
else
|
101
|
+
cell.to_csv
|
102
|
+
end
|
103
|
+
|
104
|
+
set_extended_value_type!(xv, value)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_cell_data(cell)
|
109
|
+
mod = cell.modifier
|
110
|
+
|
111
|
+
sheets_ns::CellData.new.tap do |cd|
|
112
|
+
cd.user_entered_format = build_cell_format(cell.modifier)
|
113
|
+
cd.note = mod.note if mod.note
|
114
|
+
|
115
|
+
# XXX apply data validation
|
116
|
+
cd.user_entered_value = build_cell_value(cell)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_row_data(row)
|
121
|
+
sheets_ns::RowData.new(values: row.cells.map { |cell| build_cell_data(cell) })
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_update_cells_request(rows)
|
125
|
+
sheets_ns::UpdateCellsRequest.new(
|
126
|
+
fields: '*',
|
127
|
+
start: sheets_ns::GridCoordinate.new(
|
128
|
+
sheet_id: @sheet_id,
|
129
|
+
column_index: @column_index,
|
130
|
+
row_index: @row_index
|
131
|
+
),
|
132
|
+
rows: rows.map { |row| build_row_data(row) }
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_border(cell)
|
137
|
+
mod = cell.modifier
|
138
|
+
# TODO: allow different border styles per side
|
139
|
+
border = sheets_ns::Border.new(color: mod.bordercolor || '#000000', style: mod.borderstyle || 'solid')
|
140
|
+
sheets_ns::UpdateBordersRequest.new(
|
141
|
+
top: mod.border_along?('top') ? border : nil,
|
142
|
+
right: mod.border_along?('right') ? border : nil,
|
143
|
+
left: mod.border_along?('left') ? border : nil,
|
144
|
+
bottom: mod.border_along?('bottom') ? border : nil,
|
145
|
+
range: grid_range_for_cell(cell)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def build_update_borders_request(cell)
|
150
|
+
sheets_ns::Request.new(update_borders: build_border(cell))
|
151
|
+
end
|
152
|
+
|
153
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
154
|
+
def build_batch_request(rows)
|
155
|
+
sheets_ns::BatchUpdateSpreadsheetRequest.new.tap do |bu|
|
156
|
+
bu.requests =
|
157
|
+
rows.each_slice(1000).to_a.map do |chunked_rows|
|
158
|
+
sheets_ns::Request.new(update_cells: build_update_cells_request(chunked_rows))
|
159
|
+
end
|
160
|
+
|
161
|
+
rows.each do |row|
|
162
|
+
row.cells.filter { |c| c.modifier.any_border? }
|
163
|
+
.each do |cell|
|
164
|
+
bu.requests << build_update_borders_request(cell)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
170
|
+
end
|
171
|
+
# rubocop:enable Metrics/ClassLength
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_writer'
|
4
|
+
require_relative 'google_sheet_builder'
|
5
|
+
|
6
|
+
AUTH_SCOPES = ['https://www.googleapis.com/auth/spreadsheets'].freeze
|
7
|
+
FULL_RANGE = 'A1:Z1000'
|
8
|
+
|
9
|
+
module CSVPlusPlus
|
10
|
+
module Writer
|
11
|
+
# A class that can output a +Template+ to Google Sheets (via their API)
|
12
|
+
class GoogleSheets < ::CSVPlusPlus::Writer::BaseWriter
|
13
|
+
# XXX it would be nice to raise this but we shouldn't expand out more than necessary for our data
|
14
|
+
SPREADSHEET_INFINITY = 1000
|
15
|
+
public_constant :SPREADSHEET_INFINITY
|
16
|
+
|
17
|
+
# initialize
|
18
|
+
def initialize(options)
|
19
|
+
super(options)
|
20
|
+
|
21
|
+
@sheet_id = options.google.sheet_id
|
22
|
+
@sheet_name = options.sheet_name
|
23
|
+
end
|
24
|
+
|
25
|
+
# write a +template+ to Google Sheets
|
26
|
+
def write(template)
|
27
|
+
auth!
|
28
|
+
|
29
|
+
save_spreadsheet!
|
30
|
+
save_spreadsheet_values!
|
31
|
+
|
32
|
+
create_sheet! if @options.create_if_not_exists
|
33
|
+
|
34
|
+
update_cells!(template)
|
35
|
+
rescue ::Google::Apis::ClientError => e
|
36
|
+
handle_google_error(e)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def load_requires
|
42
|
+
require('google/apis/sheets_v4')
|
43
|
+
require('googleauth')
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def format_range(range)
|
49
|
+
@sheet_name ? "'#{@sheet_name}'!#{range}" : range
|
50
|
+
end
|
51
|
+
|
52
|
+
def full_range
|
53
|
+
format_range(::FULL_RANGE)
|
54
|
+
end
|
55
|
+
|
56
|
+
def auth!
|
57
|
+
@gs ||= sheets_ns::SheetsService.new
|
58
|
+
@gs.authorization = ::Google::Auth.get_application_default(::AUTH_SCOPES)
|
59
|
+
end
|
60
|
+
|
61
|
+
def save_spreadsheet_values!
|
62
|
+
formatted_values = get_all_spreadsheet_values('FORMATTED_VALUE')
|
63
|
+
formula_values = get_all_spreadsheet_values('FORMULA')
|
64
|
+
|
65
|
+
return if formula_values.values.nil? || formatted_values.values.nil?
|
66
|
+
|
67
|
+
@current_values = extract_current_values(formatted_values, formula_values)
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_current_values(formatted_values, formula_values)
|
71
|
+
formatted_values.values.map.each_with_index do |row, x|
|
72
|
+
row.map.each_with_index do |_cell, y|
|
73
|
+
formula_value = formula_values.values[x][y]
|
74
|
+
if formula_value.is_a?(::String) && formula_value.start_with?('=')
|
75
|
+
formula_value
|
76
|
+
else
|
77
|
+
strip_to_nil(formatted_values.values[x][y])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def strip_to_nil(str)
|
84
|
+
str.strip.empty? ? nil : str
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_all_spreadsheet_values(render_option)
|
88
|
+
@gs.get_spreadsheet_values(@sheet_id, full_range, value_render_option: render_option)
|
89
|
+
end
|
90
|
+
|
91
|
+
def sheet
|
92
|
+
return unless @sheet_name
|
93
|
+
|
94
|
+
@spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
|
95
|
+
end
|
96
|
+
|
97
|
+
def save_spreadsheet!
|
98
|
+
@spreadsheet = @gs.get_spreadsheet(@sheet_id)
|
99
|
+
|
100
|
+
return unless @sheet_name.nil?
|
101
|
+
|
102
|
+
@sheet_name = @spreadsheet.sheets&.first&.properties&.title
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_sheet!
|
106
|
+
return if sheet
|
107
|
+
|
108
|
+
@gs.create_spreadsheet(@sheet_name)
|
109
|
+
get_spreadsheet!
|
110
|
+
@sheet_name = @spreadsheet.sheets.last.properties.title
|
111
|
+
end
|
112
|
+
|
113
|
+
def update_cells!(template)
|
114
|
+
builder = ::CSVPlusPlus::Writer::GoogleSheetBuilder.new(
|
115
|
+
rows: template.rows,
|
116
|
+
sheet_id: sheet&.properties&.sheet_id,
|
117
|
+
column_index: @options.offset[1],
|
118
|
+
row_index: @options.offset[0],
|
119
|
+
current_sheet_values: @current_sheet_values
|
120
|
+
)
|
121
|
+
@gs.batch_update_spreadsheet(@sheet_id, builder.batch_update_spreadsheet_request)
|
122
|
+
rescue ::Google::Apis::ClientError => e
|
123
|
+
handle_google_error(e)
|
124
|
+
end
|
125
|
+
|
126
|
+
def sheets_ns
|
127
|
+
::Google::Apis::SheetsV4
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_google_error(error)
|
131
|
+
if @options.verbose
|
132
|
+
warn("#{error.status_code} Error making Google Sheets API request [#{error.message}]: #{error.body}")
|
133
|
+
else
|
134
|
+
warn("Error making Google Sheets API request: #{error.message}")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Writer
|
5
|
+
##
|
6
|
+
# A class that can output a +Template+ to an Excel file
|
7
|
+
class OpenDocument < ::CSVPlusPlus::Writer::BaseWriter
|
8
|
+
# write a +template+ to an OpenDocument file
|
9
|
+
def write(template)
|
10
|
+
# TODO
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './writer/base_writer'
|
4
|
+
require_relative './writer/csv'
|
5
|
+
require_relative './writer/excel'
|
6
|
+
require_relative './writer/google_sheets'
|
7
|
+
require_relative './writer/open_document'
|
8
|
+
|
9
|
+
module CSVPlusPlus
|
10
|
+
##
|
11
|
+
# Various strategies for writing to various formats (excel, google sheets, CSV, OpenDocument)
|
12
|
+
module Writer
|
13
|
+
# Return an instance of a writer depending on the given +options+
|
14
|
+
def self.writer(options)
|
15
|
+
return ::CSVPlusPlus::Writer::GoogleSheets.new(options) if options.google
|
16
|
+
|
17
|
+
case options.output_filename
|
18
|
+
when /\.csv$/ then ::CSVPlusPlus::Writer::CSV.new(options)
|
19
|
+
when /\.ods$/ then ::CSVPlusPlus::Writer::OpenDocument.new(options)
|
20
|
+
when /\.xls$/ then ::CSVPlusPlus::Writer::Excel.new(options)
|
21
|
+
else raise(::StandardError, "Unsupported extension: #{options.output_filename}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'csv_plus_plus/language/compiler'
|
4
|
+
require_relative 'csv_plus_plus/options'
|
5
|
+
require_relative 'csv_plus_plus/writer'
|
6
|
+
|
7
|
+
# A language for writing rich CSV data
|
8
|
+
module CSVPlusPlus
|
9
|
+
# Parse the input into a +Template+ and write it to the desired format
|
10
|
+
def self.apply_template_to_sheet!(input, filename, options)
|
11
|
+
warn(options.verbose_summary) if options.verbose
|
12
|
+
|
13
|
+
::CSVPlusPlus::Language::Compiler.with_compiler(input:, filename:, options:) do |c|
|
14
|
+
template = c.parse_template
|
15
|
+
|
16
|
+
output = ::CSVPlusPlus::Writer.writer(options)
|
17
|
+
c.outputting! { output.write(template) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|