csv_plus_plus 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -63
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +17 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +112 -0
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +44 -17
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +42 -11
  9. data/lib/csv_plus_plus/compiler.rb +178 -0
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +50 -0
  11. data/lib/csv_plus_plus/entities/boolean.rb +40 -0
  12. data/lib/csv_plus_plus/entities/builtins.rb +58 -0
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +231 -0
  14. data/lib/csv_plus_plus/entities/date.rb +63 -0
  15. data/lib/csv_plus_plus/entities/entity.rb +50 -0
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +45 -0
  18. data/lib/csv_plus_plus/entities/function_call.rb +50 -0
  19. data/lib/csv_plus_plus/entities/number.rb +48 -0
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -0
  21. data/lib/csv_plus_plus/entities/string.rb +42 -0
  22. data/lib/csv_plus_plus/entities/variable.rb +37 -0
  23. data/lib/csv_plus_plus/entities.rb +40 -0
  24. data/lib/csv_plus_plus/error/error.rb +20 -0
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +37 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +75 -0
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +69 -0
  28. data/lib/csv_plus_plus/error/syntax_error.rb +71 -0
  29. data/lib/csv_plus_plus/error/writer_error.rb +17 -0
  30. data/lib/csv_plus_plus/error.rb +10 -2
  31. data/lib/csv_plus_plus/google_api_client.rb +11 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +17 -6
  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 +18 -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 -150
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/{language → parser}/cell_value.tab.rb +25 -25
  46. data/lib/csv_plus_plus/{language → parser}/code_section.tab.rb +86 -95
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +478 -0
  48. data/lib/csv_plus_plus/row.rb +53 -15
  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 +42 -0
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +63 -36
  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 +7 -4
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +88 -45
  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 -33
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +41 -15
  69. metadata +44 -30
  70. data/lib/csv_plus_plus/code_section.rb +0 -101
  71. data/lib/csv_plus_plus/expand.rb +0 -18
  72. data/lib/csv_plus_plus/graph.rb +0 -62
  73. data/lib/csv_plus_plus/language/ast_builder.rb +0 -68
  74. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
  75. data/lib/csv_plus_plus/language/builtins.rb +0 -46
  76. data/lib/csv_plus_plus/language/compiler.rb +0 -152
  77. data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
  78. data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
  79. data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
  80. data/lib/csv_plus_plus/language/entities/function.rb +0 -35
  81. data/lib/csv_plus_plus/language/entities/function_call.rb +0 -37
  82. data/lib/csv_plus_plus/language/entities/number.rb +0 -36
  83. data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
  84. data/lib/csv_plus_plus/language/entities/string.rb +0 -31
  85. data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/language/entities.rb +0 -28
  87. data/lib/csv_plus_plus/language/references.rb +0 -70
  88. data/lib/csv_plus_plus/language/runtime.rb +0 -205
  89. data/lib/csv_plus_plus/language/scope.rb +0 -192
  90. data/lib/csv_plus_plus/language/syntax_error.rb +0 -66
  91. data/lib/csv_plus_plus/modifier.tab.rb +0 -907
  92. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -56
  93. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -0,0 +1,57 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # An entity that can take other entities as arguments. Current use cases for this
7
+ # are function calls and function definitions
8
+ #
9
+ # @attr_reader arguments [Array<Entity>] The arguments supplied to this entity.
10
+ class EntityWithArguments < Entity
11
+ extend ::T::Sig
12
+
13
+ abstract!
14
+
15
+ sig { returns(::T::Array[::CSVPlusPlus::Entities::Entity]) }
16
+ attr_reader :arguments
17
+
18
+ sig do
19
+ params(
20
+ type: ::CSVPlusPlus::Entities::Type,
21
+ id: ::T.nilable(::Symbol),
22
+ arguments: ::T::Array[::CSVPlusPlus::Entities::Entity]
23
+ ).void
24
+ end
25
+ # @param type [Entities::Type]
26
+ # @param id [::String]
27
+ # @param arguments [Array<Entity>]
28
+ def initialize(type, id: nil, arguments: [])
29
+ super(type, id:)
30
+ @arguments = arguments
31
+ end
32
+
33
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
34
+ # @param other [Entity]
35
+ #
36
+ # @return [boolean]
37
+ def ==(other)
38
+ return false unless other.is_a?(self.class)
39
+
40
+ @arguments == other.arguments && super
41
+ end
42
+
43
+ protected
44
+
45
+ sig do
46
+ params(arguments: ::T::Array[::CSVPlusPlus::Entities::Entity])
47
+ .returns(::T::Array[::CSVPlusPlus::Entities::Entity])
48
+ end
49
+ attr_writer :arguments
50
+
51
+ sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T::Array[::String]) }
52
+ def evaluate_arguments(runtime)
53
+ @arguments.map { |arg| arg.evaluate(runtime) }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # A function definition
7
+ #
8
+ # @attr_reader body [Entity] The body of the function. +body+ can contain variable references
9
+ # from +@arguments+
10
+ class Function < EntityWithArguments
11
+ extend ::T::Sig
12
+
13
+ sig { returns(::CSVPlusPlus::Entities::Entity) }
14
+ attr_reader :body
15
+
16
+ sig { params(id: ::Symbol, arguments: ::T::Array[::Symbol], body: ::CSVPlusPlus::Entities::Entity).void }
17
+ # @param id [Symbol] the name of the function - what it will be callable by
18
+ # @param arguments [Array<Symbol>]
19
+ # @param body [Entity]
20
+ def initialize(id, arguments, body)
21
+ super(::CSVPlusPlus::Entities::Type::Function, id:, arguments: arguments.map(&:to_sym))
22
+
23
+ @body = ::T.let(body, ::CSVPlusPlus::Entities::Entity)
24
+ end
25
+
26
+ sig { override.params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
27
+ # @param runtime [Runtime]
28
+ #
29
+ # @return [::String]
30
+ def evaluate(runtime)
31
+ "def #{@id.to_s.upcase}(#{arguments.map(&:to_s).join(', ')}) #{@body.evaluate(runtime)}"
32
+ end
33
+
34
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
35
+ # @param other [Entity]
36
+ #
37
+ # @return [::T::Boolean]
38
+ def ==(other)
39
+ return false unless super
40
+
41
+ other.is_a?(self.class) && @body == other.body
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # A function call that can be either infix (A + B) or prefix (ADD(A, B))
7
+ #
8
+ # @attr_reader infix [boolean] Whether or not this function call is infix (X * Y, A + B, etc)
9
+ class FunctionCall < EntityWithArguments
10
+ extend ::T::Sig
11
+
12
+ sig { returns(::T::Boolean) }
13
+ attr_reader :infix
14
+
15
+ sig { params(id: ::Symbol, arguments: ::T::Array[::CSVPlusPlus::Entities::Entity], infix: ::T::Boolean).void }
16
+ # @param id [::String] The name of the function
17
+ # @param arguments [Array<Entity>] The arguments to the function
18
+ # @param infix [T::Boolean] Whether the function is infix
19
+ def initialize(id, arguments, infix: false)
20
+ super(::CSVPlusPlus::Entities::Type::FunctionCall, id:, arguments:)
21
+
22
+ @infix = infix
23
+ end
24
+
25
+ sig { override.params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
26
+ # @param runtime [Runtime]
27
+ #
28
+ # @return [::String]
29
+ def evaluate(runtime)
30
+ evaluated_arguments = evaluate_arguments(runtime)
31
+
32
+ if @infix
33
+ "(#{evaluated_arguments.join(" #{@id} ")})"
34
+ else
35
+ "#{@id.to_s.upcase}(#{evaluated_arguments.join(', ')})"
36
+ end
37
+ end
38
+
39
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
40
+ # @param other [Entity]
41
+ #
42
+ # @return [boolean]
43
+ def ==(other)
44
+ return false unless super
45
+
46
+ other.is_a?(self.class) && @id == other.id && @infix == other.infix
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # A number value
7
+ #
8
+ # @attr_reader value [Numeric] The parsed number value
9
+ class Number < Entity
10
+ sig { returns(::Numeric) }
11
+ attr_reader :value
12
+
13
+ sig { params(value: ::T.any(::String, ::Numeric)).void }
14
+ # @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
15
+ def initialize(value)
16
+ super(::CSVPlusPlus::Entities::Type::Number)
17
+
18
+ @value =
19
+ ::T.let(
20
+ (if value.is_a?(::String)
21
+ value.include?('.') ? Float(value) : Integer(value, 10)
22
+ else
23
+ value
24
+ end),
25
+ ::Numeric
26
+ )
27
+ end
28
+
29
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
30
+ # @param _runtime [Runtime]
31
+ #
32
+ # @return [::String]
33
+ def evaluate(_runtime)
34
+ @value.to_s
35
+ end
36
+
37
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
38
+ # @param other [Entity]
39
+ #
40
+ # @return [::T::Boolean]
41
+ def ==(other)
42
+ return false unless super
43
+
44
+ other.is_a?(self.class) && @value == other.value
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # A runtime value. These are values which can be materialized at any point via the +resolve_fn+
7
+ # which takes an ExecutionContext as a param
8
+ #
9
+ # @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
10
+ class RuntimeValue < Entity
11
+ extend ::T::Sig
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
25
+ # @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
26
+ # @param arguments [Any] Arguments to the runtime value call
27
+ def initialize(resolve_fn, arguments: [])
28
+ super(::CSVPlusPlus::Entities::Type::RuntimeValue)
29
+
30
+ @arguments = arguments
31
+ @resolve_fn = resolve_fn
32
+ end
33
+
34
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
35
+ # @param _runtime [Runtime]
36
+ #
37
+ # @return [String]
38
+ def evaluate(_runtime)
39
+ '(runtime value)'
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # A string value
7
+ #
8
+ # @attr_reader value [String]
9
+ class String < Entity
10
+ extend ::T::Sig
11
+
12
+ sig { returns(::String) }
13
+ attr_reader :value
14
+
15
+ sig { params(value: ::String).void }
16
+ # @param value [String] The string that has been parsed out of the template
17
+ def initialize(value)
18
+ super(::CSVPlusPlus::Entities::Type::String)
19
+
20
+ @value = ::T.let(value.gsub(/^"|"$/, ''), ::String)
21
+ end
22
+
23
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
24
+ # @param _runtime [Runtime]
25
+ #
26
+ # @return [::String]
27
+ def evaluate(_runtime)
28
+ "\"#{@value}\""
29
+ end
30
+
31
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
32
+ # @param other [Entity]
33
+ #
34
+ # @return [T::Boolean]
35
+ def ==(other)
36
+ return false unless super
37
+
38
+ other.is_a?(self.class) && @value == other.value
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Entities
6
+ # TODO: get rid of this I think - everything will just be References
7
+ #
8
+ # A reference to a variable
9
+ class Variable < Entity
10
+ extend ::T::Sig
11
+
12
+ sig { params(id: ::Symbol).void }
13
+ # @param id [Symbol] The identifier of the variable
14
+ def initialize(id)
15
+ super(::CSVPlusPlus::Entities::Type::Variable, id:)
16
+ end
17
+
18
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
19
+ # @param _runtime [Runtime]
20
+ #
21
+ # @return [::String]
22
+ def evaluate(_runtime)
23
+ "$$#{@id}"
24
+ end
25
+
26
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
27
+ # @param other [Entity]
28
+ #
29
+ # @return [boolean]
30
+ def ==(other)
31
+ return false unless super
32
+
33
+ other.is_a?(self.class) && @id == other.id
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'entities/entity'
5
+ require_relative 'entities/entity_with_arguments'
6
+
7
+ require_relative 'entities/boolean'
8
+ require_relative 'entities/cell_reference'
9
+ require_relative 'entities/date'
10
+ require_relative 'entities/function'
11
+ require_relative 'entities/function_call'
12
+ require_relative 'entities/number'
13
+ require_relative 'entities/runtime_value'
14
+ require_relative 'entities/string'
15
+ require_relative 'entities/variable'
16
+
17
+ module CSVPlusPlus
18
+ # The entities that form abstract syntax trees which make up the language
19
+ module Entities
20
+ extend ::T::Sig
21
+
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
36
+ end
37
+ end
38
+
39
+ require_relative 'entities/ast_builder'
40
+ require_relative 'entities/builtins'
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # An error thrown by our code (generally to be handled at the top level bin/ command)
7
+ class Error < StandardError
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
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative './syntax_error'
5
+
6
+ module CSVPlusPlus
7
+ module Error
8
+ # An error that can be thrown when there is an error parsing a modifier
9
+ #
10
+ # @attr_reader message [::String] A helpful error message
11
+ # @attr_reader bad_input [String] The offending input that caused the error to be thrown
12
+ class FormulaSyntaxError < ::CSVPlusPlus::Error::SyntaxError
13
+ attr_reader :message, :bad_input
14
+
15
+ # You must supply either a +choices+ or +message+
16
+ #
17
+ # @param message [String] A relevant message to show
18
+ # @param bad_input [String] The offending input that caused the error to be thrown
19
+ # @param runtime [Runtime] The current runtime
20
+ # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
21
+ # Racc::ParseError that was thrown
22
+ def initialize(message, bad_input, runtime, wrapped_error: nil)
23
+ @bad_input = bad_input
24
+ @message = message
25
+
26
+ super(runtime, wrapped_error:)
27
+ end
28
+
29
+ # Create a relevant error message given +@bad_input+ and +@message+.
30
+ #
31
+ # @return [::String]
32
+ def error_message
33
+ "#{@message}: \"#{@bad_input}\""
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,75 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative './syntax_error'
5
+
6
+ module CSVPlusPlus
7
+ module Error
8
+ # An Error that wraps a +ModifierValidationError+ with a +Runtime+.
9
+ class ModifierSyntaxError < ::CSVPlusPlus::Error::SyntaxError
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+.
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
52
+ # @param runtime [Runtime] The current runtime
53
+ # @param wrapped_error [ModifierValidationError] The validtion error that this is wrapping
54
+ def initialize(runtime, bad_input:, message:, modifier: nil, wrapped_error: nil)
55
+ @bad_input = bad_input
56
+ @modifier = modifier
57
+ @message = message
58
+
59
+ super(runtime, wrapped_error:)
60
+ end
61
+
62
+ sig { override.returns(::String) }
63
+ # Create a relevant error message given +@choices+ or +@message+ (one of them must be supplied).
64
+ #
65
+ # @return [::String]
66
+ def error_message
67
+ <<~ERROR_MESSAGE
68
+ Error parsing modifier: [[#{@modifier}=...]]
69
+ Bad input: #{@bad_input}
70
+ Reason: #{@message}
71
+ ERROR_MESSAGE
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,69 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # An error that can be thrown when a modifier doesn't pass our validation.
7
+ #
8
+ # @attr_reader modifier [Symbol] The modifier being parsed when the bad input was encountered
9
+ # @attr_reader bad_input [String] The offending input that caused the error to be thrown
10
+ # @attr_reader choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
11
+ # @attr_reader message [String, nil] A relevant message to show
12
+ class ModifierValidationError < ::CSVPlusPlus::Error::Error
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
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
35
+ # You must supply either a +choices+ or +message+
36
+ #
37
+ # @param modifier [Symbol] The modifier being parsed when the bad input was encountered
38
+ # @param bad_input [String] The offending input that caused the error to be thrown
39
+ # @param choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
40
+ # @param message [String, nil] A relevant message to show
41
+ # rubocop:disable Metrics/MethodLength
42
+ def initialize(modifier, bad_input:, choices: nil, message: nil)
43
+ @bad_input = bad_input
44
+ @choices = choices
45
+ @modifier = modifier
46
+
47
+ @message = ::T.let(
48
+ if @choices
49
+ "must be one of (#{@choices.values.map(&:serialize).join(', ')})"
50
+ else
51
+ ::T.must(message)
52
+ end,
53
+ ::String
54
+ )
55
+
56
+ super(@message)
57
+ end
58
+ # rubocop:enable Metrics/MethodLength
59
+
60
+ sig { returns(::String) }
61
+ # A user-facing error message
62
+ #
63
+ # @return [::String]
64
+ def error_message
65
+ @message
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,71 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # An error that can be thrown for various syntax errors
7
+ class SyntaxError < ::CSVPlusPlus::Error::Error
8
+ extend ::T::Sig
9
+
10
+ sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime, wrapped_error: ::T.nilable(::StandardError)).void }
11
+ # @param runtime [Runtime] The current runtime
12
+ # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
13
+ # Racc::ParseError that was thrown
14
+ def initialize(runtime, wrapped_error: nil)
15
+ @runtime = runtime
16
+ @wrapped_error = wrapped_error
17
+
18
+ super()
19
+ end
20
+
21
+ sig { returns(::String) }
22
+ # @return [::String]
23
+ def to_s
24
+ to_trace
25
+ end
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) }
35
+ # Output a verbose user-helpful string that references the current runtime
36
+ def to_verbose_trace
37
+ warn(@wrapped_error.full_message) if @wrapped_error
38
+ warn((@wrapped_error.backtrace || []).join("\n")) if @wrapped_error&.backtrace
39
+ to_trace
40
+ end
41
+
42
+ sig { returns(::String) }
43
+ # Output a user-helpful string that references the runtime state
44
+ #
45
+ # @return [String]
46
+ def to_trace
47
+ "#{message_prefix}#{cell_index} #{error_message}"
48
+ end
49
+
50
+ private
51
+
52
+ sig { returns(::String) }
53
+ def cell_index
54
+ if @runtime.parsing_csv_section?
55
+ "[#{@runtime.row_index},#{@runtime.cell_index}]"
56
+ else
57
+ ''
58
+ end
59
+ end
60
+
61
+ sig { returns(::String) }
62
+ def message_prefix
63
+ line_number = @runtime.line_number
64
+ filename = @runtime.source_code.filename
65
+
66
+ line_str = ":#{line_number}"
67
+ "#{filename}#{line_str}"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # An error that can be thrown when writing a spreadsheet
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
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,15 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
4
+ require_relative './error/error'
5
+ require_relative './error/formula_syntax_error'
6
+ require_relative './error/modifier_syntax_error'
7
+ require_relative './error/modifier_validation_error'
8
+ require_relative './error/syntax_error'
9
+ require_relative './error/writer_error'
10
+
3
11
  module CSVPlusPlus
4
- # An error thrown by our code (generally to be handled at the top level bin/ command)
5
- class Error < StandardError
12
+ # A module containing errors to be raised
13
+ module Error
6
14
  end
7
15
  end