csv_plus_plus 0.1.2 → 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 +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
@@ -1,62 +1,96 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
4
5
|
# Contains the data from a parsed csvpp template.
|
5
6
|
#
|
6
7
|
# @attr_reader rows [Array<Row>] The +Row+s that comprise this +Template+
|
7
|
-
# @attr_reader
|
8
|
+
# @attr_reader runtime [Runtime] The +Runtime+ containing all function and variable references
|
8
9
|
class Template
|
9
|
-
|
10
|
+
extend ::T::Sig
|
10
11
|
|
12
|
+
sig { returns(::T::Array[::CSVPlusPlus::Row]) }
|
13
|
+
attr_reader :rows
|
14
|
+
|
15
|
+
sig { returns(::CSVPlusPlus::Runtime::Runtime) }
|
16
|
+
attr_reader :runtime
|
17
|
+
|
18
|
+
sig { params(rows: ::T::Array[::CSVPlusPlus::Row], runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
11
19
|
# @param rows [Array<Row>] The +Row+s that comprise this +Template+
|
12
|
-
# @param
|
13
|
-
def initialize(rows:,
|
14
|
-
@scope = scope
|
20
|
+
# @param runtime [Runtime] The +Runtime+ containing all function and variable references
|
21
|
+
def initialize(rows:, runtime:)
|
15
22
|
@rows = rows
|
23
|
+
@runtime = runtime
|
16
24
|
end
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
27
|
+
# Only run after expanding all rows, now we can bind all [[var=]] modifiers to a variable. There are two distinct
|
28
|
+
# types of variable bindings here:
|
29
|
+
#
|
30
|
+
# * Binding to a cell: for this we just make an +A1Reference+ to the cell itself (A1, B4, etc)
|
31
|
+
# * Binding to a cell within an expand: the variable can only be resolved within that expand and needs to be
|
32
|
+
# relative to it's row (it can't be an absolute cell reference like above)
|
33
|
+
#
|
34
|
+
# @param runtime [Runtime] The current runtime
|
35
|
+
# rubocop:disable Metrics/MethodLength
|
36
|
+
def bind_all_vars!(runtime)
|
37
|
+
runtime.position.map_rows(@rows) do |row|
|
38
|
+
# rubocop:disable Style/MissingElse
|
39
|
+
if row.unexpanded?
|
40
|
+
# rubocop:enable Style/MissingElse
|
41
|
+
raise(
|
42
|
+
::CSVPlusPlus::Error::CompilerError,
|
43
|
+
'Template#expand_rows! must be called before Template#bind_all_vars!'
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
runtime.position.map_row(row.cells) do |cell|
|
48
|
+
bind_vars(cell, row.modifier.expand)
|
49
|
+
end
|
50
|
+
end
|
21
51
|
end
|
52
|
+
# rubocop:enable Metrics/MethodLength
|
22
53
|
|
23
|
-
|
54
|
+
sig { returns(::T::Array[::CSVPlusPlus::Row]) }
|
55
|
+
# Apply expand= (adding rows to the results) modifiers to the parsed template. This happens in towards the end of
|
56
|
+
# compilation because expanding rows will change the relative rownums as rows are added, and variables can't be
|
57
|
+
# bound until the rows have been assigned their final rownums.
|
24
58
|
#
|
25
59
|
# @return [Array<Row>]
|
26
60
|
def expand_rows!
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
61
|
+
# TODO: make it so that an infinite expand will not overwrite the rows below it, but instead merge with them
|
62
|
+
@rows =
|
63
|
+
rows.reduce([]) do |expanded_rows, row|
|
64
|
+
if row.modifier.expand
|
65
|
+
row.expand_rows(starts_at: expanded_rows.length, into: expanded_rows)
|
66
|
+
else
|
67
|
+
expanded_rows << row.tap { |r| r.index = expanded_rows.length }
|
68
|
+
end
|
34
69
|
end
|
35
|
-
)
|
36
|
-
|
37
|
-
@rows = expanded_rows
|
38
70
|
end
|
39
71
|
|
72
|
+
sig { void }
|
40
73
|
# Make sure that the template has a valid amount of infinite expand modifiers
|
41
|
-
|
42
|
-
# @param runtime [Runtime] The compiler's current runtime
|
43
|
-
def validate_infinite_expands(runtime)
|
74
|
+
def validate_infinite_expands
|
44
75
|
infinite_expand_rows = @rows.filter { |r| r.modifier.expand&.infinite? }
|
45
76
|
return unless infinite_expand_rows.length > 1
|
46
77
|
|
47
|
-
|
48
|
-
|
49
|
-
|
78
|
+
raise(
|
79
|
+
::CSVPlusPlus::Error::ModifierSyntaxError.new(
|
80
|
+
'You can only have one infinite expand= (on all others you must specify an amount)',
|
81
|
+
bad_input: infinite_expand_rows[1].to_s
|
82
|
+
)
|
50
83
|
)
|
51
84
|
end
|
52
85
|
|
53
|
-
|
86
|
+
sig { returns(::String) }
|
87
|
+
# Provide a summary of the state of the template (and it's +@runtime+)
|
54
88
|
#
|
55
|
-
# @return [String]
|
89
|
+
# @return [::String]
|
56
90
|
def verbose_summary
|
57
91
|
# TODO: we can probably include way more stats in here
|
58
92
|
<<~SUMMARY
|
59
|
-
#{@scope.verbose_summary}
|
93
|
+
#{@runtime.scope.verbose_summary}
|
60
94
|
|
61
95
|
> #{@rows.length} rows to be written
|
62
96
|
SUMMARY
|
@@ -64,17 +98,15 @@ module CSVPlusPlus
|
|
64
98
|
|
65
99
|
private
|
66
100
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
push_row_fn.call(row)
|
77
|
-
end
|
101
|
+
sig { params(cell: ::CSVPlusPlus::Cell, expand: ::T.nilable(::CSVPlusPlus::Modifier::Expand)).void }
|
102
|
+
def bind_vars(cell, expand)
|
103
|
+
var = cell.modifier.var
|
104
|
+
return unless var
|
105
|
+
|
106
|
+
if expand
|
107
|
+
@runtime.bind_variable_in_expand(var, expand)
|
108
|
+
else
|
109
|
+
@runtime.bind_variable_to_cell(var)
|
78
110
|
end
|
79
111
|
end
|
80
112
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative './file_backer_upper'
|
@@ -5,29 +6,57 @@ require_relative './file_backer_upper'
|
|
5
6
|
module CSVPlusPlus
|
6
7
|
module Writer
|
7
8
|
# A class that can output a +Template+ to CSV
|
8
|
-
class CSV < ::CSVPlusPlus::Writer::
|
9
|
+
class CSV < ::CSVPlusPlus::Writer::Writer
|
10
|
+
extend ::T::Sig
|
9
11
|
include ::CSVPlusPlus::Writer::FileBackerUpper
|
12
|
+
include ::CSVPlusPlus::Writer::Merger
|
10
13
|
|
11
|
-
|
14
|
+
sig { params(options: ::CSVPlusPlus::Options::FileOptions, position: ::CSVPlusPlus::Runtime::Position).void }
|
15
|
+
# @param options [Options::FileOptions]
|
16
|
+
# @param position [Runtime::Position]
|
17
|
+
def initialize(options, position)
|
18
|
+
super(position)
|
19
|
+
|
20
|
+
@reader = ::T.let(::CSVPlusPlus::Reader::CSV.new(options), ::CSVPlusPlus::Reader::CSV)
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { override.params(template: ::CSVPlusPlus::Template).void }
|
25
|
+
# Write a +template+ to CSV
|
26
|
+
#
|
27
|
+
# @param template [Template] The template to use as input to be written. It should have been compiled by calling
|
28
|
+
# Compiler#compile_template
|
12
29
|
def write(template)
|
13
|
-
# TODO: also read it and merge the results
|
14
30
|
::CSV.open(@options.output_filename, 'wb') do |csv|
|
15
|
-
template.rows
|
31
|
+
@position.map_rows(template.rows) do |row|
|
16
32
|
csv << build_row(row)
|
17
33
|
end
|
18
34
|
end
|
19
35
|
end
|
20
36
|
|
21
|
-
|
37
|
+
sig { override.void }
|
38
|
+
# Write a backup of the current spreadsheet.
|
39
|
+
def write_backup
|
40
|
+
backup_file(@options)
|
41
|
+
end
|
22
42
|
|
23
|
-
|
24
|
-
|
43
|
+
sig { params(cell: ::CSVPlusPlus::Cell).returns(::T.nilable(::String)) }
|
44
|
+
# Turn the cell into a CSV-
|
45
|
+
def evaluate_cell(cell)
|
46
|
+
if (ast = cell.ast)
|
47
|
+
"=#{ast.evaluate(@position)}"
|
48
|
+
else
|
49
|
+
cell.value
|
50
|
+
end
|
25
51
|
end
|
26
52
|
|
27
53
|
private
|
28
54
|
|
55
|
+
sig { params(row: ::CSVPlusPlus::Row).returns(::T::Array[::T.nilable(::String)]) }
|
29
56
|
def build_row(row)
|
30
|
-
row.cells
|
57
|
+
@position.map_row(row.cells) do |cell, _i|
|
58
|
+
merge_cell_value(existing_value: @reader.value_at(cell), new_value: evaluate_cell(cell), options: @options)
|
59
|
+
end
|
31
60
|
end
|
32
61
|
end
|
33
62
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative './file_backer_upper'
|
@@ -6,17 +7,36 @@ require_relative './rubyxl_builder'
|
|
6
7
|
module CSVPlusPlus
|
7
8
|
module Writer
|
8
9
|
# A class that can output a +Template+ to an Excel file
|
9
|
-
class Excel < ::CSVPlusPlus::Writer::
|
10
|
+
class Excel < ::CSVPlusPlus::Writer::Writer
|
11
|
+
extend ::T::Sig
|
10
12
|
include ::CSVPlusPlus::Writer::FileBackerUpper
|
11
13
|
|
12
|
-
|
14
|
+
sig { params(options: ::CSVPlusPlus::Options::FileOptions, position: ::CSVPlusPlus::Runtime::Position).void }
|
15
|
+
# @param options [Options::FileOptions]
|
16
|
+
# @param position [Runtime::Position]
|
17
|
+
def initialize(options, position)
|
18
|
+
super(position)
|
19
|
+
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.params(template: ::CSVPlusPlus::Template).void }
|
24
|
+
# Write the +template+ to an Excel file
|
25
|
+
#
|
26
|
+
# @param template [Template] The template to write
|
13
27
|
def write(template)
|
14
28
|
::CSVPlusPlus::Writer::RubyXLBuilder.new(
|
15
|
-
|
16
|
-
|
17
|
-
|
29
|
+
options: @options,
|
30
|
+
position: @position,
|
31
|
+
rows: template.rows
|
18
32
|
).build_workbook.write(@options.output_filename)
|
19
33
|
end
|
34
|
+
|
35
|
+
sig { override.void }
|
36
|
+
# Write a backup of the current spreadsheet.
|
37
|
+
def write_backup
|
38
|
+
backup_file(@options)
|
39
|
+
end
|
20
40
|
end
|
21
41
|
end
|
22
42
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -5,32 +6,44 @@ module CSVPlusPlus
|
|
5
6
|
# A module that can be mixed into any Writer that needs to back up it's @output_filename (all of them except Google
|
6
7
|
# Sheets)
|
7
8
|
module FileBackerUpper
|
9
|
+
include ::Kernel
|
10
|
+
extend ::T::Sig
|
11
|
+
|
8
12
|
# I don't want to include a bunch of second/millisecond stuff in the filename unless we
|
9
13
|
# really need to. so try a less specifically formatted filename then get more specific
|
10
|
-
DESIRED_BACKUP_FORMATS =
|
14
|
+
DESIRED_BACKUP_FORMATS = ::T.let(
|
15
|
+
[
|
16
|
+
%(%Y_%m_%d-%I_%M%p),
|
17
|
+
%(%Y_%m_%d-%I_%M_%S%p),
|
18
|
+
%(%Y_%m_%d-%I_%M_%S_%L%p)
|
19
|
+
].freeze,
|
20
|
+
::T::Array[::String]
|
21
|
+
)
|
11
22
|
private_constant :DESIRED_BACKUP_FORMATS
|
12
23
|
|
24
|
+
sig { params(options: ::CSVPlusPlus::Options::FileOptions).returns(::T.nilable(::Pathname)) }
|
13
25
|
# Assuming the underlying spreadsheet is file-based, create a backup of it
|
14
|
-
def
|
15
|
-
return unless ::File.exist?(
|
26
|
+
def backup_file(options)
|
27
|
+
return unless ::File.exist?(options.output_filename)
|
16
28
|
|
17
29
|
# TODO: also don't do anything if the current backups contents haven't changed (do a md5sum or something)
|
18
30
|
|
19
|
-
attempt_backups.tap do |backed_up_to|
|
20
|
-
|
31
|
+
attempt_backups(options).tap do |backed_up_to|
|
32
|
+
puts("Backed up #{options.output_filename} to #{backed_up_to}") if options.verbose
|
21
33
|
end
|
22
34
|
end
|
23
35
|
|
24
36
|
private
|
25
37
|
|
38
|
+
sig { params(options: ::CSVPlusPlus::Options::FileOptions).returns(::Pathname) }
|
26
39
|
# rubocop:disable Metrics/MethodLength
|
27
|
-
def attempt_backups
|
40
|
+
def attempt_backups(options)
|
28
41
|
attempted =
|
29
42
|
# rubocop:disable Lint/ConstantResolution
|
30
43
|
DESIRED_BACKUP_FORMATS.map do |file_format|
|
31
44
|
# rubocop:enable Lint/ConstantResolution
|
32
|
-
filename = format_backup_filename(file_format)
|
33
|
-
backed_up_to = backup(filename)
|
45
|
+
filename = format_backup_filename(file_format, options.output_filename)
|
46
|
+
backed_up_to = backup(filename, options.output_filename)
|
34
47
|
|
35
48
|
next filename unless backed_up_to
|
36
49
|
|
@@ -44,16 +57,17 @@ module CSVPlusPlus
|
|
44
57
|
end
|
45
58
|
# rubocop:enable Metrics/MethodLength
|
46
59
|
|
47
|
-
|
60
|
+
sig { params(filename: ::Pathname, output_filename: ::Pathname).returns(::T.nilable(::Pathname)) }
|
61
|
+
def backup(filename, output_filename)
|
48
62
|
return if ::File.exist?(filename)
|
49
63
|
|
50
|
-
::FileUtils.cp(
|
64
|
+
::FileUtils.cp(output_filename, filename)
|
51
65
|
filename
|
52
66
|
end
|
53
67
|
|
54
|
-
|
55
|
-
|
56
|
-
|
68
|
+
sig { params(file_format: ::String, output_filename: ::Pathname).returns(::Pathname) }
|
69
|
+
def format_backup_filename(file_format, output_filename)
|
70
|
+
output_filename.sub_ext("-#{::Time.now.strftime(file_format)}" + output_filename.extname)
|
57
71
|
end
|
58
72
|
end
|
59
73
|
end
|
@@ -1,118 +1,62 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require_relative '../google_api_client'
|
4
|
-
require_relative 'base_writer'
|
5
|
-
require_relative 'google_sheet_builder'
|
6
|
-
|
7
4
|
module CSVPlusPlus
|
8
5
|
module Writer
|
9
6
|
# A class that can write a +Template+ to Google Sheets (via their API)
|
10
|
-
class GoogleSheets < ::CSVPlusPlus::Writer::
|
11
|
-
|
12
|
-
|
13
|
-
public_constant :SPREADSHEET_INFINITY
|
7
|
+
class GoogleSheets < ::CSVPlusPlus::Writer::Writer
|
8
|
+
extend ::T::Sig
|
9
|
+
include ::CSVPlusPlus::GoogleApiClient
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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)
|
18
18
|
|
19
|
-
@
|
20
|
-
@
|
19
|
+
@options = ::T.let(options, ::CSVPlusPlus::Options::GoogleSheetsOptions)
|
20
|
+
@reader = ::T.let(::CSVPlusPlus::Reader::GoogleSheets.new(options), ::CSVPlusPlus::Reader::GoogleSheets)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
sig { override.params(template: ::CSVPlusPlus::Template).void }
|
24
|
+
# Write a +template+ to Google Sheets
|
24
25
|
#
|
25
26
|
# @param template [Template]
|
26
27
|
def write(template)
|
27
|
-
@sheets_client = ::CSVPlusPlus::GoogleApiClient.sheets_client
|
28
|
-
|
29
|
-
fetch_spreadsheet!
|
30
|
-
fetch_spreadsheet_values!
|
31
|
-
|
32
28
|
create_sheet! if @options.create_if_not_exists
|
33
29
|
|
34
30
|
update_cells!(template)
|
35
31
|
end
|
36
32
|
|
37
|
-
|
33
|
+
sig { override.void }
|
34
|
+
# Write a backup of the Google Sheet that is about to be written
|
38
35
|
def write_backup
|
39
|
-
drive_client
|
40
|
-
drive_client.copy_file(@sheet_id)
|
36
|
+
drive_client.copy_file(@options.sheet_id)
|
41
37
|
end
|
42
38
|
|
43
39
|
private
|
44
40
|
|
45
|
-
|
46
|
-
@sheet_name ? "'#{@sheet_name}'!#{range}" : range
|
47
|
-
end
|
48
|
-
|
49
|
-
def full_range
|
50
|
-
format_range('A1:Z1000')
|
51
|
-
end
|
52
|
-
|
53
|
-
def fetch_spreadsheet_values!
|
54
|
-
formatted_values = get_all_spreadsheet_values('FORMATTED_VALUE')
|
55
|
-
formula_values = get_all_spreadsheet_values('FORMULA')
|
56
|
-
|
57
|
-
return if formula_values.values.nil? || formatted_values.values.nil?
|
58
|
-
|
59
|
-
@current_values = extract_current_values(formatted_values, formula_values)
|
60
|
-
end
|
61
|
-
|
62
|
-
def extract_current_values(formatted_values, formula_values)
|
63
|
-
formatted_values.values.map.each_with_index do |row, x|
|
64
|
-
row.map.each_with_index do |_cell, y|
|
65
|
-
formula_value = formula_values.values[x][y]
|
66
|
-
if formula_value.is_a?(::String) && formula_value.start_with?('=')
|
67
|
-
formula_value
|
68
|
-
else
|
69
|
-
strip_to_nil(formatted_values.values[x][y])
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def strip_to_nil(str)
|
76
|
-
str.strip.empty? ? nil : str
|
77
|
-
end
|
78
|
-
|
79
|
-
def get_all_spreadsheet_values(render_option)
|
80
|
-
@sheets_client.get_spreadsheet_values(@sheet_id, full_range, value_render_option: render_option)
|
81
|
-
end
|
82
|
-
|
83
|
-
def sheet
|
84
|
-
return unless @sheet_name
|
85
|
-
|
86
|
-
@spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
|
87
|
-
end
|
88
|
-
|
89
|
-
def fetch_spreadsheet!
|
90
|
-
@spreadsheet = @sheets_client.get_spreadsheet(@sheet_id)
|
91
|
-
|
92
|
-
return unless @sheet_name.nil?
|
93
|
-
|
94
|
-
@sheet_name = @spreadsheet.sheets&.first&.properties&.title
|
95
|
-
end
|
96
|
-
|
41
|
+
sig { void }
|
97
42
|
def create_sheet!
|
98
|
-
return if sheet
|
43
|
+
return if @reader.sheet
|
99
44
|
|
100
|
-
|
101
|
-
fetch_spreadsheet!
|
102
|
-
@sheet_name = @spreadsheet.sheets.last.properties.title
|
45
|
+
sheets_client.create_spreadsheet(@options.sheet_name)
|
103
46
|
end
|
104
47
|
|
48
|
+
sig { params(template: ::CSVPlusPlus::Template).void }
|
105
49
|
def update_cells!(template)
|
106
|
-
|
50
|
+
sheets_client.batch_update_spreadsheet(@options.sheet_id, builder(template).batch_update_spreadsheet_request)
|
107
51
|
end
|
108
52
|
|
53
|
+
sig { params(template: ::CSVPlusPlus::Template).returns(::CSVPlusPlus::Writer::GoogleSheetsBuilder) }
|
109
54
|
def builder(template)
|
110
|
-
::CSVPlusPlus::Writer::
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
current_sheet_values: @current_values
|
55
|
+
::CSVPlusPlus::Writer::GoogleSheetsBuilder.new(
|
56
|
+
options: @options,
|
57
|
+
position: @position,
|
58
|
+
reader: @reader,
|
59
|
+
rows: template.rows
|
116
60
|
)
|
117
61
|
end
|
118
62
|
end
|