csv_plus_plus 0.1.2 → 0.1.3

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