csv_plus_plus 0.1.2 → 0.1.3

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +23 -13
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +32 -7
  9. data/lib/csv_plus_plus/compiler.rb +82 -60
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
  11. data/lib/csv_plus_plus/entities/boolean.rb +18 -9
  12. data/lib/csv_plus_plus/entities/builtins.rb +23 -9
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
  14. data/lib/csv_plus_plus/entities/date.rb +38 -5
  15. data/lib/csv_plus_plus/entities/entity.rb +27 -61
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +23 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +24 -9
  19. data/lib/csv_plus_plus/entities/number.rb +24 -10
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
  21. data/lib/csv_plus_plus/entities/string.rb +19 -6
  22. data/lib/csv_plus_plus/entities/variable.rb +16 -4
  23. data/lib/csv_plus_plus/entities.rb +20 -13
  24. data/lib/csv_plus_plus/error/error.rb +11 -1
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
  28. data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
  29. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  30. data/lib/csv_plus_plus/error.rb +1 -0
  31. data/lib/csv_plus_plus/google_api_client.rb +7 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
  34. data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
  35. data/lib/csv_plus_plus/lexer.rb +24 -0
  36. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  37. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  38. data/lib/csv_plus_plus/modifier/expand.rb +61 -0
  39. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  40. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  41. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  42. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  43. data/lib/csv_plus_plus/modifier.rb +82 -158
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
  46. data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
  48. data/lib/csv_plus_plus/row.rb +53 -12
  49. data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
  50. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
  51. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  52. data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
  53. data/lib/csv_plus_plus/runtime/references.rb +110 -0
  54. data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
  55. data/lib/csv_plus_plus/runtime.rb +34 -191
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +62 -35
  58. data/lib/csv_plus_plus/version.rb +2 -1
  59. data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
  60. data/lib/csv_plus_plus/writer/csv.rb +11 -9
  61. data/lib/csv_plus_plus/writer/excel.rb +9 -2
  62. data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
  64. data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
  65. data/lib/csv_plus_plus/writer/open_document.rb +6 -1
  66. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +29 -12
  69. metadata +18 -14
  70. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  71. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  72. data/lib/csv_plus_plus/data_validation.rb +0 -138
  73. data/lib/csv_plus_plus/expand.rb +0 -20
  74. data/lib/csv_plus_plus/graph.rb +0 -62
  75. data/lib/csv_plus_plus/references.rb +0 -68
  76. data/lib/csv_plus_plus/scope.rb +0 -196
  77. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  78. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  79. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -7,19 +8,35 @@ module CSVPlusPlus
7
8
  #
8
9
  # @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
9
10
  class RuntimeValue < Entity
10
- attr_reader :arguments, :resolve_fn
11
+ extend ::T::Sig
11
12
 
13
+ sig { returns(::T::Array[::T.untyped]) }
14
+ attr_reader :arguments
15
+
16
+ sig { returns(::T.proc.params(arg0: ::CSVPlusPlus::Runtime::Runtime).returns(::CSVPlusPlus::Entities::Entity)) }
17
+ attr_reader :resolve_fn
18
+
19
+ sig do
20
+ params(
21
+ resolve_fn: ::T.proc.params(arg0: ::CSVPlusPlus::Runtime::Runtime).returns(::CSVPlusPlus::Entities::Entity),
22
+ arguments: ::T::Array[::T.untyped]
23
+ ).void
24
+ end
12
25
  # @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
13
- def initialize(resolve_fn, arguments: nil)
14
- super(:runtime_value)
26
+ # @param arguments [Any] Arguments to the runtime value call
27
+ def initialize(resolve_fn, arguments: [])
28
+ super(::CSVPlusPlus::Entities::Type::RuntimeValue)
15
29
 
16
30
  @arguments = arguments
17
31
  @resolve_fn = resolve_fn
18
32
  end
19
33
 
34
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
35
+ # @param _runtime [Runtime]
36
+ #
20
37
  # @return [String]
21
- def to_s
22
- '(runtime_value)'
38
+ def evaluate(_runtime)
39
+ '(runtime value)'
23
40
  end
24
41
  end
25
42
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -6,23 +7,35 @@ module CSVPlusPlus
6
7
  #
7
8
  # @attr_reader value [String]
8
9
  class String < Entity
10
+ extend ::T::Sig
11
+
12
+ sig { returns(::String) }
9
13
  attr_reader :value
10
14
 
15
+ sig { params(value: ::String).void }
11
16
  # @param value [String] The string that has been parsed out of the template
12
17
  def initialize(value)
13
- super(:string)
18
+ super(::CSVPlusPlus::Entities::Type::String)
14
19
 
15
- @value = value.gsub(/^"|"$/, '')
20
+ @value = ::T.let(value.gsub(/^"|"$/, ''), ::String)
16
21
  end
17
22
 
18
- # @return [String]
19
- def to_s
23
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
24
+ # @param _runtime [Runtime]
25
+ #
26
+ # @return [::String]
27
+ def evaluate(_runtime)
20
28
  "\"#{@value}\""
21
29
  end
22
30
 
23
- # @return [boolean]
31
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
32
+ # @param other [Entity]
33
+ #
34
+ # @return [T::Boolean]
24
35
  def ==(other)
25
- super && value == other.value
36
+ return false unless super
37
+
38
+ other.is_a?(self.class) && @value == other.value
26
39
  end
27
40
  end
28
41
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -6,19 +7,30 @@ module CSVPlusPlus
6
7
  #
7
8
  # A reference to a variable
8
9
  class Variable < Entity
10
+ extend ::T::Sig
11
+
12
+ sig { params(id: ::Symbol).void }
9
13
  # @param id [Symbol] The identifier of the variable
10
14
  def initialize(id)
11
- super(:variable, id:)
15
+ super(::CSVPlusPlus::Entities::Type::Variable, id:)
12
16
  end
13
17
 
14
- # @return [String]
15
- def to_s
18
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
19
+ # @param _runtime [Runtime]
20
+ #
21
+ # @return [::String]
22
+ def evaluate(_runtime)
16
23
  "$$#{@id}"
17
24
  end
18
25
 
26
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
27
+ # @param other [Entity]
28
+ #
19
29
  # @return [boolean]
20
30
  def ==(other)
21
- super && id == other.id
31
+ return false unless super
32
+
33
+ other.is_a?(self.class) && @id == other.id
22
34
  end
23
35
  end
24
36
  end
@@ -1,9 +1,12 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
4
+ require_relative 'entities/entity'
5
+ require_relative 'entities/entity_with_arguments'
6
+
3
7
  require_relative 'entities/boolean'
4
8
  require_relative 'entities/cell_reference'
5
9
  require_relative 'entities/date'
6
- require_relative 'entities/entity'
7
10
  require_relative 'entities/function'
8
11
  require_relative 'entities/function_call'
9
12
  require_relative 'entities/number'
@@ -12,20 +15,24 @@ require_relative 'entities/string'
12
15
  require_relative 'entities/variable'
13
16
 
14
17
  module CSVPlusPlus
18
+ # The entities that form abstract syntax trees which make up the language
15
19
  module Entities
16
- TYPES = {
17
- boolean: ::CSVPlusPlus::Entities::Boolean,
18
- cell_reference: ::CSVPlusPlus::Entities::CellReference,
19
- date: ::CSVPlusPlus::Entities::Date,
20
- function: ::CSVPlusPlus::Entities::Function,
21
- function_call: ::CSVPlusPlus::Entities::FunctionCall,
22
- number: ::CSVPlusPlus::Entities::Number,
23
- runtime_value: ::CSVPlusPlus::Entities::RuntimeValue,
24
- string: ::CSVPlusPlus::Entities::String,
25
- variable: ::CSVPlusPlus::Entities::Variable
26
- }.freeze
20
+ extend ::T::Sig
27
21
 
28
- public_constant :TYPES
22
+ # A primitive type. These all correspond to an implementation of the same name in the CSVPlusPlus::Entities module.
23
+ class Type < ::T::Enum
24
+ enums do
25
+ Boolean = new
26
+ CellReference = new
27
+ Date = new
28
+ Function = new
29
+ FunctionCall = new
30
+ Number = new
31
+ RuntimeValue = new
32
+ String = new
33
+ Variable = new
34
+ end
35
+ end
29
36
  end
30
37
  end
31
38
 
@@ -1,10 +1,20 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Error
5
6
  # An error thrown by our code (generally to be handled at the top level bin/ command)
6
7
  class Error < StandardError
7
- # TODO: perhaps give this a better name? something more descriptive than just Error
8
+ extend ::T::Sig
9
+ extend ::T::Helpers
10
+
11
+ sig { returns(::String) }
12
+ # Return an error message for display to a command-line user.
13
+ #
14
+ # @return [::String]
15
+ def error_message
16
+ message
17
+ end
8
18
  end
9
19
  end
10
20
  end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './syntax_error'
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './syntax_error'
@@ -6,21 +7,68 @@ module CSVPlusPlus
6
7
  module Error
7
8
  # An Error that wraps a +ModifierValidationError+ with a +Runtime+.
8
9
  class ModifierSyntaxError < ::CSVPlusPlus::Error::SyntaxError
9
- # You must supply either a +choices+ or +message+
10
+ extend ::T::Sig
11
+
12
+ sig { returns(::String) }
13
+ attr_reader :bad_input
14
+
15
+ sig { returns(::String) }
16
+ attr_reader :message
17
+
18
+ sig { returns(::T.nilable(::Symbol)) }
19
+ attr_reader :modifier
20
+
21
+ sig do
22
+ params(
23
+ runtime: ::CSVPlusPlus::Runtime::Runtime,
24
+ modifier_validation_error: ::CSVPlusPlus::Error::ModifierValidationError
25
+ ).returns(::CSVPlusPlus::Error::ModifierSyntaxError)
26
+ end
27
+ # Create a +ModifierSyntaxError+ given a +runtime+ and +ModifierValidationError+.
10
28
  #
29
+ # @param runtime [Runtime]
30
+ # @param modifier_validation_error [ModifierValidationError]
31
+ #
32
+ # @return [ModifierSyntaxError]
33
+ def self.from_validation_error(runtime, modifier_validation_error)
34
+ new(
35
+ runtime,
36
+ modifier: modifier_validation_error.modifier,
37
+ bad_input: modifier_validation_error.bad_input,
38
+ message: modifier_validation_error.message,
39
+ wrapped_error: modifier_validation_error
40
+ )
41
+ end
42
+
43
+ sig do
44
+ params(
45
+ runtime: ::CSVPlusPlus::Runtime::Runtime,
46
+ bad_input: ::String,
47
+ message: ::String,
48
+ modifier: ::T.nilable(::Symbol),
49
+ wrapped_error: ::T.nilable(::StandardError)
50
+ ).void
51
+ end
11
52
  # @param runtime [Runtime] The current runtime
12
53
  # @param wrapped_error [ModifierValidationError] The validtion error that this is wrapping
13
- def initialize(runtime, wrapped_error:)
14
- @wrapped_error = wrapped_error
54
+ def initialize(runtime, bad_input:, message:, modifier: nil, wrapped_error: nil)
55
+ @bad_input = bad_input
56
+ @modifier = modifier
57
+ @message = message
15
58
 
16
59
  super(runtime, wrapped_error:)
17
60
  end
18
61
 
19
- # Calls +wrapped_error.error_message+.
62
+ sig { override.returns(::String) }
63
+ # Create a relevant error message given +@choices+ or +@message+ (one of them must be supplied).
20
64
  #
21
65
  # @return [::String]
22
66
  def error_message
23
- @wrapped_error.error_message
67
+ <<~ERROR_MESSAGE
68
+ Error parsing modifier: [[#{@modifier}=...]]
69
+ Bad input: #{@bad_input}
70
+ Reason: #{@message}
71
+ ERROR_MESSAGE
24
72
  end
25
73
  end
26
74
  end
@@ -1,7 +1,6 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './syntax_error'
4
-
5
4
  module CSVPlusPlus
6
5
  module Error
7
6
  # An error that can be thrown when a modifier doesn't pass our validation.
@@ -11,38 +10,59 @@ module CSVPlusPlus
11
10
  # @attr_reader choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
12
11
  # @attr_reader message [String, nil] A relevant message to show
13
12
  class ModifierValidationError < ::CSVPlusPlus::Error::Error
14
- attr_reader :bad_input, :choices, :message, :modifier
13
+ extend ::T::Sig
14
+
15
+ sig { returns(::String) }
16
+ attr_reader :bad_input
17
+
18
+ sig { returns(::T.nilable(::T.class_of(::T::Enum))) }
19
+ attr_reader :choices
15
20
 
21
+ sig { returns(::String) }
22
+ attr_reader :message
23
+
24
+ sig { returns(::Symbol) }
25
+ attr_reader :modifier
26
+
27
+ sig do
28
+ params(
29
+ modifier: ::Symbol,
30
+ bad_input: ::String,
31
+ choices: ::T.nilable(::T.class_of(::T::Enum)),
32
+ message: ::T.nilable(::String)
33
+ ).void
34
+ end
16
35
  # You must supply either a +choices+ or +message+
17
36
  #
18
37
  # @param modifier [Symbol] The modifier being parsed when the bad input was encountered
19
38
  # @param bad_input [String] The offending input that caused the error to be thrown
20
39
  # @param choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
21
40
  # @param message [String, nil] A relevant message to show
22
- def initialize(modifier, bad_input, choices: nil, message: nil)
41
+ # rubocop:disable Metrics/MethodLength
42
+ def initialize(modifier, bad_input:, choices: nil, message: nil)
23
43
  @bad_input = bad_input
24
44
  @choices = choices
25
45
  @modifier = modifier
26
46
 
27
- @message =
47
+ @message = ::T.let(
28
48
  if @choices
29
- "must be one of (#{@choices.map(&:to_s).join(', ')})"
49
+ "must be one of (#{@choices.values.map(&:serialize).join(', ')})"
30
50
  else
31
- message
32
- end
51
+ ::T.must(message)
52
+ end,
53
+ ::String
54
+ )
33
55
 
34
56
  super(@message)
35
57
  end
58
+ # rubocop:enable Metrics/MethodLength
36
59
 
37
- # Create a relevant error message given +@choices+ or +@message+ (one of them must be supplied).
60
+ sig { returns(::String) }
61
+ # A user-facing error message
38
62
  #
39
63
  # @return [::String]
40
64
  def error_message
41
- <<~ERROR_MESSAGE
42
- Error parsing modifier: [[#{@modifier}=...]]
43
- Bad input: #{@bad_input}
44
- Reason: #{@message}
45
- ERROR_MESSAGE
65
+ @message
46
66
  end
47
67
  end
48
68
  end
@@ -1,9 +1,13 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Error
5
6
  # An error that can be thrown for various syntax errors
6
7
  class SyntaxError < ::CSVPlusPlus::Error::Error
8
+ extend ::T::Sig
9
+
10
+ sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime, wrapped_error: ::T.nilable(::StandardError)).void }
7
11
  # @param runtime [Runtime] The current runtime
8
12
  # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
9
13
  # Racc::ParseError that was thrown
@@ -14,18 +18,28 @@ module CSVPlusPlus
14
18
  super()
15
19
  end
16
20
 
17
- # @return [String]
21
+ sig { returns(::String) }
22
+ # @return [::String]
18
23
  def to_s
19
24
  to_trace
20
25
  end
21
26
 
27
+ # TODO: clean up all these different string-formatting error classes
28
+ sig { override.returns(::String) }
29
+ # @return [::String]
30
+ def error_message
31
+ ''
32
+ end
33
+
34
+ sig { returns(::String) }
22
35
  # Output a verbose user-helpful string that references the current runtime
23
36
  def to_verbose_trace
24
37
  warn(@wrapped_error.full_message) if @wrapped_error
25
- warn(@wrapped_error.backtrace) if @wrapped_error
38
+ warn((@wrapped_error.backtrace || []).join("\n")) if @wrapped_error&.backtrace
26
39
  to_trace
27
40
  end
28
41
 
42
+ sig { returns(::String) }
29
43
  # Output a user-helpful string that references the runtime state
30
44
  #
31
45
  # @return [String]
@@ -35,22 +49,21 @@ module CSVPlusPlus
35
49
 
36
50
  private
37
51
 
52
+ sig { returns(::String) }
38
53
  def cell_index
39
- row_index = @runtime.row_index
40
- if @runtime.cell_index
41
- "[#{row_index},#{@runtime.cell_index}]"
42
- elsif row_index
43
- "[#{row_index}]"
54
+ if @runtime.parsing_csv_section?
55
+ "[#{@runtime.row_index},#{@runtime.cell_index}]"
44
56
  else
45
57
  ''
46
58
  end
47
59
  end
48
60
 
61
+ sig { returns(::String) }
49
62
  def message_prefix
50
63
  line_number = @runtime.line_number
51
- filename = @runtime.filename
64
+ filename = @runtime.source_code.filename
52
65
 
53
- line_str = line_number ? ":#{line_number}" : ''
66
+ line_str = ":#{line_number}"
54
67
  "#{filename}#{line_str}"
55
68
  end
56
69
  end
@@ -1,9 +1,17 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Error
5
6
  # An error that can be thrown when writing a spreadsheet
6
7
  class WriterError < ::CSVPlusPlus::Error::Error
8
+ extend ::T::Sig
9
+
10
+ sig { override.returns(::String) }
11
+ # @return [::String]
12
+ def error_message
13
+ "Error writing template: #{message}"
14
+ end
7
15
  end
8
16
  end
9
17
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './error/error'
@@ -1,9 +1,13 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  # A convenience wrapper around Google's REST API client
5
6
  module GoogleApiClient
6
- # Get a +::Google::Apis::SheetsV4::SheetsService+ instance connected to the sheets API
7
+ extend ::T::Sig
8
+
9
+ sig { returns(::Google::Apis::SheetsV4::SheetsService) }
10
+ # Get a +Google::Apis::SheetsV4::SheetsService+ instance configured to connect to the sheets API
7
11
  #
8
12
  # @return [Google::Apis::SheetsV4::SheetsService]
9
13
  def self.sheets_client
@@ -12,7 +16,8 @@ module CSVPlusPlus
12
16
  end
13
17
  end
14
18
 
15
- # Get a +::Google::Apis::DriveV3::DriveService+ instance connected to the drive API
19
+ sig { returns(::Google::Apis::DriveV3::DriveService) }
20
+ # Get a +Google::Apis::DriveV3::DriveService+ instance connected to the drive API
16
21
  #
17
22
  # @return [Google::Apis::DriveV3::DriveService]
18
23
  def self.drive_client
@@ -1,27 +1,32 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
- # The Google-specific options a user can supply
5
+ # The Google-specific options a user can supply.
5
6
  #
6
- # attr sheet_id [String] The ID of the Google Sheet to write to
7
- GoogleOptions =
8
- ::Struct.new(:sheet_id) do
9
- # Format a string with a verbose description of what we're doing with the options
10
- #
11
- # @return [String]
12
- def verbose_summary
13
- <<~SUMMARY
14
- ## Google Sheets Options
7
+ # @attr sheet_id [String] The ID of the Google Sheet to write to.
8
+ class GoogleOptions
9
+ extend ::T::Sig
15
10
 
16
- > Sheet ID | #{sheet_id}
17
- SUMMARY
18
- end
11
+ sig { returns(::String) }
12
+ attr_reader :sheet_id
19
13
 
20
- # @return [String]
21
- def to_s
22
- "GoogleOptions(sheet_id: #{sheet_id})"
23
- end
14
+ sig { params(sheet_id: ::String).void }
15
+ # @param sheet_id [String] The unique ID Google uses to reference the sheet
16
+ def initialize(sheet_id)
17
+ @sheet_id = sheet_id
24
18
  end
25
19
 
26
- public_constant :GoogleOptions
20
+ sig { returns(::String) }
21
+ # Format a string with a verbose description of what we're doing with the options
22
+ #
23
+ # @return [String]
24
+ def verbose_summary
25
+ <<~SUMMARY
26
+ ## Google Sheets Options
27
+
28
+ > Sheet ID | #{@sheet_id}
29
+ SUMMARY
30
+ end
31
+ end
27
32
  end
@@ -1,7 +1,6 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative '../color'
4
-
5
4
  module CSVPlusPlus
6
5
  # Common methods to be mixed into the Racc parsers
7
6
  #
@@ -30,13 +29,15 @@ module CSVPlusPlus
30
29
 
31
30
  return return_value unless anything_to_parse?(input)
32
31
 
32
+ @runtime = runtime
33
+
33
34
  tokenize(input, runtime)
34
35
  do_parse
35
36
  return_value
36
37
  rescue ::Racc::ParseError => e
37
38
  runtime.raise_formula_syntax_error("Error parsing #{parse_subject}", e.message, wrapped_error: e)
38
39
  rescue ::CSVPlusPlus::Error::ModifierValidationError => e
39
- raise(::CSVPlusPlus::Error::ModifierSyntaxError.new(runtime, wrapped_error: e))
40
+ raise(::CSVPlusPlus::Error::ModifierSyntaxError.from_validation_error(runtime, e))
40
41
  end
41
42
 
42
43
  TOKEN_LIBRARY = {
@@ -76,8 +77,11 @@ module CSVPlusPlus
76
77
  @tokens << [tokenizer.last_token, tokenizer.last_match]
77
78
  elsif tokenizer.scan_catchall
78
79
  @tokens << [tokenizer.last_match, tokenizer.last_match]
80
+ # TODO: checking the +parse_subject+ like this is a little hacky... but we need to know if we're parsing
81
+ # modifiers or code_section (or formulas in a cell)
82
+ elsif parse_subject == 'modifier'
83
+ runtime.raise_modifier_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
79
84
  else
80
- # TODO: this should raise a modifier_syntax_error if we're on the modifier parser
81
85
  runtime.raise_formula_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
82
86
  end
83
87
  end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'strscan'
@@ -11,7 +12,11 @@ module CSVPlusPlus
11
12
  class Tokenizer
12
13
  attr_reader :last_token, :scanner
13
14
 
14
- # @param input [String]
15
+ # @param tokens [Array<Regexp, String>] The list of tokens to scan
16
+ # @param catchall [Regexp] A final regexp to try if nothing else matches
17
+ # @param ignore [Regexp] Ignore anything matching this regexp
18
+ # @param alter_matches [Object] A map of matches to alter
19
+ # @param stop_fn [Proc] Stop parsing when this is true
15
20
  def initialize(tokens:, catchall: nil, ignore: nil, alter_matches: {}, stop_fn: nil)
16
21
  @last_token = nil
17
22
 
@@ -1,14 +1,38 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './lexer/lexer'
4
5
  require_relative './lexer/tokenizer'
5
6
 
6
7
  module CSVPlusPlus
8
+ # Code for tokenizing a csvpp file
7
9
  module Lexer
10
+ extend ::T::Sig
11
+
8
12
  END_OF_CODE_SECTION = '---'
9
13
  public_constant :END_OF_CODE_SECTION
10
14
 
11
15
  VARIABLE_REF = '$$'
12
16
  public_constant :VARIABLE_REF
17
+
18
+ sig { params(str: ::String).returns(::String) }
19
+ # When parsing a modifier with a quoted string field, we need a way to unescape. Some examples of quoted and
20
+ # unquoted results:
21
+ #
22
+ # * "just a string" => "just a string"
23
+ # * "' this is a string'" => "this is a string"
24
+ # * "won\'t this work?" => "won't this work"
25
+ #
26
+ # @param str [::String]
27
+ #
28
+ # @return [::String]
29
+ def self.unquote(str)
30
+ # could probably do this with one regex but we do it in 3 steps:
31
+ #
32
+ # 1. remove leading and trailing spaces and '
33
+ # 2. remove any backslashes that are by themselves (none on either side)
34
+ # 3. turn double backslashes into singles
35
+ str.gsub(/^\s*'?|'?\s*$/, '').gsub(/([^\\]+)\\([^\\]+)/, '\1\2').gsub(/\\\\/, '\\')
36
+ end
13
37
  end
14
38
  end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus