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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -5
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  6. data/lib/csv_plus_plus/cell.rb +29 -41
  7. data/lib/csv_plus_plus/cli.rb +53 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -71
  9. data/lib/csv_plus_plus/color.rb +32 -7
  10. data/lib/csv_plus_plus/compiler.rb +98 -66
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
  12. data/lib/csv_plus_plus/entities/boolean.rb +26 -10
  13. data/lib/csv_plus_plus/entities/builtins.rb +66 -24
  14. data/lib/csv_plus_plus/entities/date.rb +42 -6
  15. data/lib/csv_plus_plus/entities/entity.rb +17 -69
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
  17. data/lib/csv_plus_plus/entities/function.rb +34 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +49 -10
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +30 -11
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
  23. data/lib/csv_plus_plus/entities/string.rb +23 -7
  24. data/lib/csv_plus_plus/entities.rb +7 -16
  25. data/lib/csv_plus_plus/error/cli_error.rb +17 -0
  26. data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
  27. data/lib/csv_plus_plus/error/error.rb +25 -2
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  33. data/lib/csv_plus_plus/error.rb +5 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +25 -10
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
  38. data/lib/csv_plus_plus/lexer.rb +64 -1
  39. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  40. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  41. data/lib/csv_plus_plus/modifier/expand.rb +78 -0
  42. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  43. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  44. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  45. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  46. data/lib/csv_plus_plus/modifier.rb +89 -160
  47. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  48. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  49. data/lib/csv_plus_plus/options/options.rb +97 -0
  50. data/lib/csv_plus_plus/options.rb +34 -77
  51. data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
  52. data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
  53. data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
  54. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  55. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  56. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  57. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  58. data/lib/csv_plus_plus/reader.rb +14 -0
  59. data/lib/csv_plus_plus/row.rb +53 -12
  60. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  61. data/lib/csv_plus_plus/runtime/position.rb +242 -0
  62. data/lib/csv_plus_plus/runtime/references.rb +115 -0
  63. data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
  64. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  65. data/lib/csv_plus_plus/runtime.rb +34 -191
  66. data/lib/csv_plus_plus/source_code.rb +71 -0
  67. data/lib/csv_plus_plus/template.rb +71 -39
  68. data/lib/csv_plus_plus/version.rb +2 -1
  69. data/lib/csv_plus_plus/writer/csv.rb +37 -8
  70. data/lib/csv_plus_plus/writer/excel.rb +25 -5
  71. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
  72. data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
  73. data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
  74. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  75. data/lib/csv_plus_plus/writer/open_document.rb +21 -2
  76. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
  77. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  78. data/lib/csv_plus_plus/writer.rb +79 -10
  79. data/lib/csv_plus_plus.rb +47 -18
  80. metadata +50 -21
  81. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  82. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  83. data/lib/csv_plus_plus/data_validation.rb +0 -138
  84. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -60
  85. data/lib/csv_plus_plus/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
  87. data/lib/csv_plus_plus/expand.rb +0 -20
  88. data/lib/csv_plus_plus/google_options.rb +0 -27
  89. data/lib/csv_plus_plus/graph.rb +0 -62
  90. data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
  91. data/lib/csv_plus_plus/references.rb +0 -68
  92. data/lib/csv_plus_plus/scope.rb +0 -196
  93. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  94. data/lib/csv_plus_plus/writer/base_writer.rb +0 -20
  95. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
  96. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  97. 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 scope [Scope] The +Scope+ containing all function and variable references
8
+ # @attr_reader runtime [Runtime] The +Runtime+ containing all function and variable references
8
9
  class Template
9
- attr_reader :rows, :scope
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 scope [Scope] The +Scope+ containing all function and variable references
13
- def initialize(rows:, scope:)
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
- # @return [String]
19
- def to_s
20
- "Template(rows: #{@rows}, scope: #{@scope})"
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
- # Apply any expand= modifiers to the parsed template
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
- expanded_rows = []
28
- row_index = 0
29
- expand_rows(
30
- lambda do |new_row|
31
- new_row.index = row_index
32
- expanded_rows << new_row
33
- row_index += 1
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
- runtime.raise_formula_syntax_error(
48
- 'You can only have one infinite expand= (on all others you must specify an amount)',
49
- infinite_expand_rows[1]
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
- # Provide a summary of the state of the template (and it's +@scope+)
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
- def expand_rows(push_row_fn)
68
- # TODO: make it so that an infinite expand will not overwrite the rows below it, but
69
- # instead merge with them
70
- rows.each do |row|
71
- if row.modifier.expand
72
- row.expand_amount.times do
73
- push_row_fn.call(row.deep_clone)
74
- end
75
- else
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,6 +1,7 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
- VERSION = '0.1.2'
5
+ VERSION = '0.2.0'
5
6
  public_constant :VERSION
6
7
  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::BaseWriter
9
+ class CSV < ::CSVPlusPlus::Writer::Writer
10
+ extend ::T::Sig
9
11
  include ::CSVPlusPlus::Writer::FileBackerUpper
12
+ include ::CSVPlusPlus::Writer::Merger
10
13
 
11
- # write a +template+ to CSV
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.each do |row|
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
- protected
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
- def load_requires
24
- require('csv')
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.map(&:to_csv)
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::BaseWriter
10
+ class Excel < ::CSVPlusPlus::Writer::Writer
11
+ extend ::T::Sig
10
12
  include ::CSVPlusPlus::Writer::FileBackerUpper
11
13
 
12
- # write the +template+ to an Excel file
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
- input_filename: @options.output_filename,
16
- rows: template.rows,
17
- sheet_name: @options.sheet_name
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 = [%(%Y_%m_%d-%I_%M%p), %(%Y_%m_%d-%I_%M_%S%p), %(%Y_%m_%d-%I_%M_%S_%L%p)].freeze
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 write_backup
15
- return unless ::File.exist?(@options.output_filename)
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
- warn("Backed up #{@options.output_filename} to #{backed_up_to}") if @options.verbose
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
- def backup(filename)
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(@options.output_filename, filename)
64
+ ::FileUtils.cp(output_filename, filename)
51
65
  filename
52
66
  end
53
67
 
54
- def format_backup_filename(file_format)
55
- pn = ::Pathname.new(@options.output_filename)
56
- pn.sub_ext("-#{::Time.now.strftime(file_format)}" + pn.extname)
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::BaseWriter
11
- # TODO: it would be nice to raise this but we shouldn't expand out more than necessary for our data
12
- SPREADSHEET_INFINITY = 1000
13
- public_constant :SPREADSHEET_INFINITY
7
+ class GoogleSheets < ::CSVPlusPlus::Writer::Writer
8
+ extend ::T::Sig
9
+ include ::CSVPlusPlus::GoogleApiClient
14
10
 
15
- # @param options [Options]
16
- def initialize(options)
17
- super(options)
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
- @sheet_id = options.google.sheet_id
20
- @sheet_name = options.sheet_name
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
- # write a +template+ to Google Sheets
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
- # write a backup of the google sheet
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 = ::CSVPlusPlus::GoogleApiClient.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
- def format_range(range)
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
- @sheets_client.create_spreadsheet(@sheet_name)
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
- @sheets_client.batch_update_spreadsheet(@sheet_id, builder(template).batch_update_spreadsheet_request)
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::GoogleSheetBuilder.new(
111
- rows: template.rows,
112
- sheet_id: sheet&.properties&.sheet_id,
113
- column_index: @options.offset[1],
114
- row_index: @options.offset[0],
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