csv_plus_plus 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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