csv_plus_plus 0.1.0 → 0.1.2

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -1
  3. data/README.md +18 -62
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +62 -0
  5. data/lib/csv_plus_plus/can_define_references.rb +88 -0
  6. data/lib/csv_plus_plus/can_resolve_references.rb +8 -0
  7. data/lib/csv_plus_plus/cell.rb +3 -3
  8. data/lib/csv_plus_plus/cli.rb +24 -7
  9. data/lib/csv_plus_plus/color.rb +12 -6
  10. data/lib/csv_plus_plus/compiler.rb +156 -0
  11. data/lib/csv_plus_plus/data_validation.rb +138 -0
  12. data/lib/csv_plus_plus/{language → entities}/ast_builder.rb +5 -7
  13. data/lib/csv_plus_plus/entities/boolean.rb +31 -0
  14. data/lib/csv_plus_plus/{language → entities}/builtins.rb +2 -4
  15. data/lib/csv_plus_plus/entities/cell_reference.rb +60 -0
  16. data/lib/csv_plus_plus/entities/date.rb +30 -0
  17. data/lib/csv_plus_plus/entities/entity.rb +84 -0
  18. data/lib/csv_plus_plus/entities/function.rb +33 -0
  19. data/lib/csv_plus_plus/entities/function_call.rb +35 -0
  20. data/lib/csv_plus_plus/entities/number.rb +34 -0
  21. data/lib/csv_plus_plus/entities/runtime_value.rb +26 -0
  22. data/lib/csv_plus_plus/entities/string.rb +29 -0
  23. data/lib/csv_plus_plus/entities/variable.rb +25 -0
  24. data/lib/csv_plus_plus/entities.rb +33 -0
  25. data/lib/csv_plus_plus/error/error.rb +10 -0
  26. data/lib/csv_plus_plus/error/formula_syntax_error.rb +36 -0
  27. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +27 -0
  28. data/lib/csv_plus_plus/error/modifier_validation_error.rb +49 -0
  29. data/lib/csv_plus_plus/{language → error}/syntax_error.rb +6 -14
  30. data/lib/csv_plus_plus/error/writer_error.rb +9 -0
  31. data/lib/csv_plus_plus/error.rb +9 -2
  32. data/lib/csv_plus_plus/expand.rb +3 -1
  33. data/lib/csv_plus_plus/google_api_client.rb +4 -0
  34. data/lib/csv_plus_plus/lexer/lexer.rb +19 -11
  35. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +17 -0
  36. data/lib/csv_plus_plus/modifier.rb +73 -70
  37. data/lib/csv_plus_plus/options.rb +3 -0
  38. data/lib/csv_plus_plus/parser/cell_value.tab.rb +305 -0
  39. data/lib/csv_plus_plus/parser/code_section.tab.rb +410 -0
  40. data/lib/csv_plus_plus/parser/modifier.tab.rb +484 -0
  41. data/lib/csv_plus_plus/references.rb +68 -0
  42. data/lib/csv_plus_plus/row.rb +0 -3
  43. data/lib/csv_plus_plus/runtime.rb +199 -0
  44. data/lib/csv_plus_plus/scope.rb +196 -0
  45. data/lib/csv_plus_plus/template.rb +21 -5
  46. data/lib/csv_plus_plus/validated_modifier.rb +164 -0
  47. data/lib/csv_plus_plus/version.rb +1 -1
  48. data/lib/csv_plus_plus/writer/file_backer_upper.rb +6 -4
  49. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +24 -29
  50. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +33 -12
  51. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +3 -6
  52. data/lib/csv_plus_plus.rb +41 -16
  53. metadata +34 -24
  54. data/lib/csv_plus_plus/code_section.rb +0 -68
  55. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
  56. data/lib/csv_plus_plus/language/cell_value.tab.rb +0 -332
  57. data/lib/csv_plus_plus/language/code_section.tab.rb +0 -442
  58. data/lib/csv_plus_plus/language/compiler.rb +0 -157
  59. data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
  60. data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
  61. data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
  62. data/lib/csv_plus_plus/language/entities/function.rb +0 -35
  63. data/lib/csv_plus_plus/language/entities/function_call.rb +0 -26
  64. data/lib/csv_plus_plus/language/entities/number.rb +0 -36
  65. data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
  66. data/lib/csv_plus_plus/language/entities/string.rb +0 -31
  67. data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
  68. data/lib/csv_plus_plus/language/entities.rb +0 -28
  69. data/lib/csv_plus_plus/language/references.rb +0 -70
  70. data/lib/csv_plus_plus/language/runtime.rb +0 -205
  71. data/lib/csv_plus_plus/language/scope.rb +0 -188
  72. data/lib/csv_plus_plus/modifier.tab.rb +0 -907
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './syntax_error'
4
+
5
+ module CSVPlusPlus
6
+ module Error
7
+ # An Error that wraps a +ModifierValidationError+ with a +Runtime+.
8
+ class ModifierSyntaxError < ::CSVPlusPlus::Error::SyntaxError
9
+ # You must supply either a +choices+ or +message+
10
+ #
11
+ # @param runtime [Runtime] The current runtime
12
+ # @param wrapped_error [ModifierValidationError] The validtion error that this is wrapping
13
+ def initialize(runtime, wrapped_error:)
14
+ @wrapped_error = wrapped_error
15
+
16
+ super(runtime, wrapped_error:)
17
+ end
18
+
19
+ # Calls +wrapped_error.error_message+.
20
+ #
21
+ # @return [::String]
22
+ def error_message
23
+ @wrapped_error.error_message
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './syntax_error'
4
+
5
+ module CSVPlusPlus
6
+ module Error
7
+ # An error that can be thrown when a modifier doesn't pass our validation.
8
+ #
9
+ # @attr_reader modifier [Symbol] The modifier being parsed when the bad input was encountered
10
+ # @attr_reader bad_input [String] The offending input that caused the error to be thrown
11
+ # @attr_reader choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
12
+ # @attr_reader message [String, nil] A relevant message to show
13
+ class ModifierValidationError < ::CSVPlusPlus::Error::Error
14
+ attr_reader :bad_input, :choices, :message, :modifier
15
+
16
+ # You must supply either a +choices+ or +message+
17
+ #
18
+ # @param modifier [Symbol] The modifier being parsed when the bad input was encountered
19
+ # @param bad_input [String] The offending input that caused the error to be thrown
20
+ # @param choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
21
+ # @param message [String, nil] A relevant message to show
22
+ def initialize(modifier, bad_input, choices: nil, message: nil)
23
+ @bad_input = bad_input
24
+ @choices = choices
25
+ @modifier = modifier
26
+
27
+ @message =
28
+ if @choices
29
+ "must be one of (#{@choices.map(&:to_s).join(', ')})"
30
+ else
31
+ message
32
+ end
33
+
34
+ super(@message)
35
+ end
36
+
37
+ # Create a relevant error message given +@choices+ or +@message+ (one of them must be supplied).
38
+ #
39
+ # @return [::String]
40
+ def error_message
41
+ <<~ERROR_MESSAGE
42
+ Error parsing modifier: [[#{@modifier}=...]]
43
+ Bad input: #{@bad_input}
44
+ Reason: #{@message}
45
+ ERROR_MESSAGE
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,21 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CSVPlusPlus
4
- module Language
4
+ module Error
5
5
  # An error that can be thrown for various syntax errors
6
- class SyntaxError < ::CSVPlusPlus::Error
7
- # @param message [String] The primary message to be shown to the user
8
- # @param bad_input [String] The offending input that caused the error to be thrown
6
+ class SyntaxError < ::CSVPlusPlus::Error::Error
9
7
  # @param runtime [Runtime] The current runtime
10
8
  # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
11
9
  # Racc::ParseError that was thrown
12
- def initialize(message, bad_input, runtime, wrapped_error: nil)
13
- @bad_input = bad_input.to_s
10
+ def initialize(runtime, wrapped_error: nil)
14
11
  @runtime = runtime
15
12
  @wrapped_error = wrapped_error
16
- @message = message
17
13
 
18
- super(message)
14
+ super()
19
15
  end
20
16
 
21
17
  # @return [String]
@@ -34,7 +30,7 @@ module CSVPlusPlus
34
30
  #
35
31
  # @return [String]
36
32
  def to_trace
37
- "#{message_prefix}#{cell_index} #{message_postfix}"
33
+ "#{message_prefix}#{cell_index} #{error_message}"
38
34
  end
39
35
 
40
36
  private
@@ -55,11 +51,7 @@ module CSVPlusPlus
55
51
  filename = @runtime.filename
56
52
 
57
53
  line_str = line_number ? ":#{line_number}" : ''
58
- "csv++ #{filename}#{line_str}"
59
- end
60
-
61
- def message_postfix
62
- "#{@message}: \"#{@bad_input}\""
54
+ "#{filename}#{line_str}"
63
55
  end
64
56
  end
65
57
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Error
5
+ # An error that can be thrown when writing a spreadsheet
6
+ class WriterError < ::CSVPlusPlus::Error::Error
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './error/error'
4
+ require_relative './error/formula_syntax_error'
5
+ require_relative './error/modifier_syntax_error'
6
+ require_relative './error/modifier_validation_error'
7
+ require_relative './error/syntax_error'
8
+ require_relative './error/writer_error'
9
+
3
10
  module CSVPlusPlus
4
- # An error thrown by our code (generally to be handled at the top level bin/ command)
5
- class Error < StandardError
11
+ # A module containing errors to be raised
12
+ module Error
6
13
  end
7
14
  end
@@ -4,11 +4,13 @@ module CSVPlusPlus
4
4
  Expand =
5
5
  ::Struct.new(:repetitions) do
6
6
  # Does this infinitely expand?
7
+ #
8
+ # @return [boolean]
7
9
  def infinite?
8
10
  repetitions.nil?
9
11
  end
10
12
 
11
- # to_s
13
+ # @return [::String]
12
14
  def to_s
13
15
  "Expand #{repetitions || 'infinity'}"
14
16
  end
@@ -4,6 +4,8 @@ module CSVPlusPlus
4
4
  # A convenience wrapper around Google's REST API client
5
5
  module GoogleApiClient
6
6
  # Get a +::Google::Apis::SheetsV4::SheetsService+ instance connected to the sheets API
7
+ #
8
+ # @return [Google::Apis::SheetsV4::SheetsService]
7
9
  def self.sheets_client
8
10
  ::Google::Apis::SheetsV4::SheetsService.new.tap do |s|
9
11
  s.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/spreadsheets'].freeze)
@@ -11,6 +13,8 @@ module CSVPlusPlus
11
13
  end
12
14
 
13
15
  # Get a +::Google::Apis::DriveV3::DriveService+ instance connected to the drive API
16
+ #
17
+ # @return [Google::Apis::DriveV3::DriveService]
14
18
  def self.drive_client
15
19
  ::Google::Apis::DriveV3::DriveService.new.tap do |d|
16
20
  d.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/drive.file'].freeze)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../color'
4
+
3
5
  module CSVPlusPlus
4
6
  # Common methods to be mixed into the Racc parsers
5
7
  #
@@ -32,18 +34,23 @@ module CSVPlusPlus
32
34
  do_parse
33
35
  return_value
34
36
  rescue ::Racc::ParseError => e
35
- runtime.raise_syntax_error("Error parsing #{parse_subject}", e.message, wrapped_error: e)
37
+ runtime.raise_formula_syntax_error("Error parsing #{parse_subject}", e.message, wrapped_error: e)
38
+ rescue ::CSVPlusPlus::Error::ModifierValidationError => e
39
+ raise(::CSVPlusPlus::Error::ModifierSyntaxError.new(runtime, wrapped_error: e))
36
40
  end
37
41
 
38
- protected
39
-
40
- # Given a +type+, instantiate the proper instance with the given +entity_args+
41
- #
42
- # @param type [Symbol]
43
- # @param entity_args
44
- def e(type, *entity_args)
45
- ::CSVPlusPlus::Language::TYPES[type].new(*entity_args)
46
- end
42
+ TOKEN_LIBRARY = {
43
+ A1_NOTATION: [::CSVPlusPlus::Entities::CellReference::A1_NOTATION_REGEXP, :A1_NOTATION],
44
+ FALSE: [/false/i, :FALSE],
45
+ HEX_COLOR: [::CSVPlusPlus::Color::HEX_STRING_REGEXP, :HEX_COLOR],
46
+ ID: [/[$!\w:]+/, :ID],
47
+ INFIX_OP: [%r{\^|\+|-|\*|/|&|<|>|<=|>=|<>}, :INFIX_OP],
48
+ NUMBER: [/-?[\d.]+/, :NUMBER],
49
+ STRING: [%r{"(?:[^"\\]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"}, :STRING],
50
+ TRUE: [/true/i, :TRUE],
51
+ VAR_REF: [/\$\$/, :VAR_REF]
52
+ }.freeze
53
+ public_constant :TOKEN_LIBRARY
47
54
 
48
55
  private
49
56
 
@@ -70,7 +77,8 @@ module CSVPlusPlus
70
77
  elsif tokenizer.scan_catchall
71
78
  @tokens << [tokenizer.last_match, tokenizer.last_match]
72
79
  else
73
- runtime.raise_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
80
+ # TODO: this should raise a modifier_syntax_error if we're on the modifier parser
81
+ runtime.raise_formula_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
74
82
  end
75
83
  end
76
84
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Modifier
5
+ # A class that handles the rules for modifiers to support conditional formatting.
6
+ class ConditionalFormatting
7
+ attr_reader :arguments, :condition, :invalid_reason
8
+
9
+ # @param value [::String] The unparsed conditional formatting rule
10
+ def initialize(value)
11
+ condition, args = value.split(/\si:\s*/)
12
+ @condition = condition.to_sym
13
+ @arguments = args.split(/\s+/)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,37 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require_relative './color'
5
- require_relative './expand'
6
- require_relative './language/syntax_error'
7
-
8
3
  module CSVPlusPlus
9
4
  # A container representing the operations that can be applied to a cell or row
10
5
  #
11
- # @attr borders [Array<String>] The borders that will be set
6
+ # @attr bordercolor [Color]
7
+ # @attr borders [Array<Symbol>] The borders that will be set
8
+ # @attr color [Color] The background color of the cell
12
9
  # @attr expand [Expand] Whether this row expands into multiple rows
13
- # @attr fontfamily [String] The font family
14
- # @attr fontsize [Number] The font size
15
- # @attr halign ['left', 'center', 'right'] Horizontal alignment
16
- # @attr note [String] A note/comment on the cell
17
- # @attr numberformat [String] A number format to apply to the value in the cell
18
- # @attr row_level [Boolean] Is this a row modifier? If so it's values will apply to all cells in the row
10
+ # @attr fontcolor [Color] The font color of the cell
11
+ # @attr fontfamily [::String] The font family
12
+ # @attr fontsize [Integer] The font size
13
+ # @attr halign [:left, :center, :right] Horizontal alignment
14
+ # @attr note [::String] A note/comment on the cell
15
+ # @attr numberformat [Symbol] A number format to apply to the value in the cell
16
+ # @attr row_level [boolean] Is this a row modifier? If so it's values will apply to all cells in the row
19
17
  # (unless overridden by the cell modifier)
20
18
  # @attr validation [Object]
21
- # @attr valign ['top', 'center', 'bottom'] Vertical alignment
19
+ # @attr valign [:top, :center, :bottom] Vertical alignment
20
+ # @attr var [Symbol] The variable bound to this cell
22
21
  #
23
- # @attr_writer borderstyle ['dashed', 'dotted', 'double', 'solid', 'solid_medium', 'solid_thick']
22
+ # @attr_writer borderstyle [:hashed, :dotted, :double, :solid, :solid_medium, :solid_thick]
24
23
  # The style of border on the cell
25
24
  #
26
- # @attr_reader bordercolor [String]
27
- # @attr_reader borders [Array<String>]
28
- # @attr_reader color [Color] The background color of the cell
29
- # @attr_reader fontcolor [Color] The font color of the cell
30
- # @attr_reader formats [Array<String>] Bold/italics/underline/strikethrough formatting
25
+ # @attr_reader borders [Array<Symbol>]
26
+ # @attr_reader formats [Array<Symbol>] Bold/italics/underline/strikethrough formatting
31
27
  class Modifier
32
- attr_reader :bordercolor, :borders, :color, :fontcolor, :formats
28
+ attr_accessor :bordercolor,
29
+ :color,
30
+ :expand,
31
+ :fontcolor,
32
+ :fontfamily,
33
+ :fontsize,
34
+ :halign,
35
+ :valign,
36
+ :note,
37
+ :numberformat,
38
+ :row_level,
39
+ :validation,
40
+ :var
41
+ attr_reader :borders, :formats
33
42
  attr_writer :borderstyle
34
- attr_accessor :expand, :fontfamily, :fontsize, :halign, :valign, :note, :numberformat, :row_level, :validation
43
+
44
+ # When instantiating a new object, extend it with our validation functionality.
45
+ #
46
+ # I'm not sure why I need to do it this way tbh, using +include ValidatedModifier+ at the
47
+ # class level didn't seem to have access to the parent methods
48
+ # def self.new(*args, **kwargs, &)
49
+ # allocate.tap do |i|
50
+ # i.__send__(:initialize, *args, **kwargs, &)
51
+ # i.extend(::CSVPlusPlus::ValidatedModifier)
52
+ # end
53
+ # end
35
54
 
36
55
  # @param row_level [Boolean] Whether or not this modifier applies to the entire row
37
56
  def initialize(row_level: false)
@@ -41,70 +60,68 @@ module CSVPlusPlus
41
60
  @formats = ::Set.new
42
61
  end
43
62
 
44
- # Set the color
63
+ # Are there any borders set?
64
+ #
65
+ # @return [Boolean]
66
+ def any_border?
67
+ !@borders.empty?
68
+ end
69
+
70
+ # Style of border
45
71
  #
46
- # @param hex_value [String]
72
+ # @return [Symbol]
73
+ def borderstyle
74
+ @borderstyle || :solid
75
+ end
76
+
77
+ # Is this a cell-level modifier?
47
78
  #
48
- # @return [Color]
49
- def color=(hex_value)
50
- @color = ::CSVPlusPlus::Color.new(hex_value)
79
+ # @return [Boolean]
80
+ def cell_level?
81
+ !@row_level
51
82
  end
52
83
 
53
84
  # Assign a border
54
85
  #
55
- # @param side ['top', 'left', 'bottom', 'right', 'all']
86
+ # @param side [:top, :left, :bottom, :right, :all]
56
87
  def border=(side)
57
88
  @borders << side
58
89
  end
59
90
 
60
91
  # Does this have a border along +side+?
61
92
  #
62
- # @param side ['top', 'left', 'bottom', 'right', 'all']
93
+ # @param side [:top, :left, :bottom, :right, :all]
63
94
  #
64
- # @return [Boolean]
95
+ # @return [boolean]
65
96
  def border_along?(side)
66
- @borders.include?('all') || @borders.include?(side)
97
+ @borders.include?(:all) || @borders.include?(side)
67
98
  end
68
99
 
69
100
  # Does this have a border along all sides?
70
101
  #
71
- # @return [Boolean]
102
+ # @return [boolean]
72
103
  def border_all?
73
- @borders.include?('all') \
74
- || (border_along?('top') && border_along?('bottom') && border_along?('left') && border_along?('right'))
75
- end
76
-
77
- # Set the bordercolor
78
- #
79
- # @param hex_value [String] formatted as '#000000', '#000' or '000000'
80
- def bordercolor=(hex_value)
81
- @bordercolor = ::CSVPlusPlus::Color.new(hex_value)
104
+ @borders.include?(:all) \
105
+ || (border_along?(:top) && border_along?(:bottom) && border_along?(:left) && border_along?(:right))
82
106
  end
83
107
 
84
- # Are there any borders set?
108
+ # Set this modifier to expand infinitely
85
109
  #
86
- # @return [Boolean]
87
- def any_border?
88
- !@borders.empty?
89
- end
90
-
91
- # Set the fontcolor
92
- #
93
- # @param hex_value [String] formatted as '#000000', '#000' or '000000'
94
- def fontcolor=(hex_value)
95
- @fontcolor = ::CSVPlusPlus::Color.new(hex_value)
110
+ # @return [::Expand]
111
+ def expand!
112
+ @expand = ::CSVPlusPlus::Expand.new if row_level?
96
113
  end
97
114
 
98
115
  # Set a text format (bolid, italic, underline or strikethrough)
99
116
  #
100
- # @param value ['bold', 'italic', 'underline', 'strikethrough']
117
+ # @param value [:bold, :italic, :underline, :strikethrough]
101
118
  def format=(value)
102
119
  @formats << value
103
120
  end
104
121
 
105
122
  # Is the given format set?
106
123
  #
107
- # @param type ['bold', 'italic', 'underline', 'strikethrough']
124
+ # @param type [:bold, :italic, :underline, :strikethrough]
108
125
  #
109
126
  # @return [Boolean]
110
127
  def formatted?(type)
@@ -120,7 +137,7 @@ module CSVPlusPlus
120
137
 
121
138
  # Is the row frozen?
122
139
  #
123
- # @return [Boolean]
140
+ # @return [boolean]
124
141
  def frozen?
125
142
  @frozen
126
143
  end
@@ -134,26 +151,12 @@ module CSVPlusPlus
134
151
 
135
152
  # Is this a row-level modifier?
136
153
  #
137
- # @return [Boolean]
154
+ # @return [boolean]
138
155
  def row_level?
139
156
  @row_level
140
157
  end
141
158
 
142
- # Is this a cell-level modifier?
143
- #
144
- # @return [Boolean]
145
- def cell_level?
146
- !@row_level
147
- end
148
-
149
- # Style of border
150
- #
151
- # @return [String]
152
- def borderstyle
153
- @borderstyle || 'solid'
154
- end
155
-
156
- # @return [String]
159
+ # @return [::String]
157
160
  def to_s
158
161
  # TODO... I dunno, not sure how to manage this
159
162
  "Modifier(row_level: #{@row_level} halign: #{@halign} valign: #{@valign} format: #{@formats} " \
@@ -31,12 +31,14 @@ module CSVPlusPlus
31
31
  #
32
32
  # @param sheet_id [String] The identifier used by Google's API to reference the sheet. You can find it in the URL
33
33
  # for the sheet
34
+ #
34
35
  # @return [String]
35
36
  def google_sheet_id=(sheet_id)
36
37
  @google = ::CSVPlusPlus::GoogleOptions.new(sheet_id)
37
38
  end
38
39
 
39
40
  # Returns an error string or nil if there are no validation problems
41
+ #
40
42
  # @return [String, nil]
41
43
  def validate
42
44
  return if @google || @output_filename
@@ -45,6 +47,7 @@ module CSVPlusPlus
45
47
  end
46
48
 
47
49
  # Return a string with a verbose description of what we're doing with the options
50
+ #
48
51
  # @return [String]
49
52
  def verbose_summary
50
53
  <<~SUMMARY