csv_plus_plus 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -5
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  6. data/lib/csv_plus_plus/cell.rb +29 -41
  7. data/lib/csv_plus_plus/cli.rb +53 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -71
  9. data/lib/csv_plus_plus/color.rb +32 -7
  10. data/lib/csv_plus_plus/compiler.rb +98 -66
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
  12. data/lib/csv_plus_plus/entities/boolean.rb +26 -10
  13. data/lib/csv_plus_plus/entities/builtins.rb +66 -24
  14. data/lib/csv_plus_plus/entities/date.rb +42 -6
  15. data/lib/csv_plus_plus/entities/entity.rb +17 -69
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
  17. data/lib/csv_plus_plus/entities/function.rb +34 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +49 -10
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +30 -11
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
  23. data/lib/csv_plus_plus/entities/string.rb +23 -7
  24. data/lib/csv_plus_plus/entities.rb +7 -16
  25. data/lib/csv_plus_plus/error/cli_error.rb +17 -0
  26. data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
  27. data/lib/csv_plus_plus/error/error.rb +25 -2
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  33. data/lib/csv_plus_plus/error.rb +5 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +25 -10
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
  38. data/lib/csv_plus_plus/lexer.rb +64 -1
  39. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  40. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  41. data/lib/csv_plus_plus/modifier/expand.rb +78 -0
  42. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  43. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  44. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  45. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  46. data/lib/csv_plus_plus/modifier.rb +89 -160
  47. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  48. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  49. data/lib/csv_plus_plus/options/options.rb +97 -0
  50. data/lib/csv_plus_plus/options.rb +34 -77
  51. data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
  52. data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
  53. data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
  54. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  55. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  56. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  57. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  58. data/lib/csv_plus_plus/reader.rb +14 -0
  59. data/lib/csv_plus_plus/row.rb +53 -12
  60. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  61. data/lib/csv_plus_plus/runtime/position.rb +242 -0
  62. data/lib/csv_plus_plus/runtime/references.rb +115 -0
  63. data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
  64. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  65. data/lib/csv_plus_plus/runtime.rb +34 -191
  66. data/lib/csv_plus_plus/source_code.rb +71 -0
  67. data/lib/csv_plus_plus/template.rb +71 -39
  68. data/lib/csv_plus_plus/version.rb +2 -1
  69. data/lib/csv_plus_plus/writer/csv.rb +37 -8
  70. data/lib/csv_plus_plus/writer/excel.rb +25 -5
  71. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
  72. data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
  73. data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
  74. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  75. data/lib/csv_plus_plus/writer/open_document.rb +21 -2
  76. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
  77. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  78. data/lib/csv_plus_plus/writer.rb +79 -10
  79. data/lib/csv_plus_plus.rb +47 -18
  80. metadata +50 -21
  81. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  82. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  83. data/lib/csv_plus_plus/data_validation.rb +0 -138
  84. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -60
  85. data/lib/csv_plus_plus/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
  87. data/lib/csv_plus_plus/expand.rb +0 -20
  88. data/lib/csv_plus_plus/google_options.rb +0 -27
  89. data/lib/csv_plus_plus/graph.rb +0 -62
  90. data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
  91. data/lib/csv_plus_plus/references.rb +0 -68
  92. data/lib/csv_plus_plus/scope.rb +0 -196
  93. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  94. data/lib/csv_plus_plus/writer/base_writer.rb +0 -20
  95. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
  96. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  97. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,25 +1,55 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
4
+ require_relative './entity'
5
+
3
6
  module CSVPlusPlus
4
7
  module Entities
5
- # A runtime value. These are values which can be materialized at any point via the +resolve_fn+
6
- # which takes an ExecutionContext as a param
7
- #
8
- # @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
9
- class RuntimeValue < Entity
10
- attr_reader :arguments, :resolve_fn
8
+ # A runtime value. These are values which can be materialized at any point via the +resolve_fn+ which
9
+ # will provide a value depending on the +Runtime+'s current state.
10
+ class RuntimeValue < ::CSVPlusPlus::Entities::Entity
11
+ extend ::T::Sig
11
12
 
12
- # @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)
13
+ ResolveFn =
14
+ ::T.type_alias do
15
+ ::T.proc.params(
16
+ position: ::CSVPlusPlus::Runtime::Position,
17
+ arguments: ::T::Enumerable[::CSVPlusPlus::Entities::Entity]
18
+ ).returns(::CSVPlusPlus::Entities::Entity)
19
+ end
20
+ public_constant :ResolveFn
15
21
 
16
- @arguments = arguments
22
+ sig do
23
+ params(resolve_fn: ::CSVPlusPlus::Entities::RuntimeValue::ResolveFn).void
24
+ end
25
+ # @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
26
+ def initialize(resolve_fn)
17
27
  @resolve_fn = resolve_fn
28
+ super()
29
+ end
30
+
31
+ sig do
32
+ params(
33
+ position: ::CSVPlusPlus::Runtime::Position,
34
+ arguments: ::T::Enumerable[::CSVPlusPlus::Entities::Entity]
35
+ ).returns(::CSVPlusPlus::Entities::Entity)
36
+ end
37
+ # Using the +@resolve_fn+, evaluate this runtime value into an +Entity+ that can be later evaluated
38
+ def call(position, arguments)
39
+ @resolve_fn.call(position, arguments)
18
40
  end
19
41
 
20
- # @return [String]
21
- def to_s
22
- '(runtime_value)'
42
+ sig { override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
43
+ # Given the current runtime, call +@resolve_fn+ to produce a value
44
+ #
45
+ # @param _position [Position]
46
+ #
47
+ # @return [Entities::Entity]
48
+ def evaluate(_position)
49
+ # TODO: we can do a check on arguments here and make sure that the RuntimeValue is being called
50
+ # with the number of arguments it requires
51
+ # @resolve_fn.call(position, ::T.must(arguments)).evaluate(position)
52
+ '(runtime value)'
23
53
  end
24
54
  end
25
55
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -5,24 +6,39 @@ module CSVPlusPlus
5
6
  # A string value
6
7
  #
7
8
  # @attr_reader value [String]
8
- class String < Entity
9
+ class String < ::CSVPlusPlus::Entities::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()
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(_position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
24
+ # @param _position [Position]
25
+ #
26
+ # @return [::String]
27
+ def evaluate(_position)
20
28
  "\"#{@value}\""
21
29
  end
22
30
 
23
- # @return [boolean]
31
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
32
+ # @param other [BasicObject]
33
+ #
34
+ # @return [T::Boolean]
24
35
  def ==(other)
25
- super && value == other.value
36
+ case other
37
+ when self.class
38
+ @value == other.value
39
+ else
40
+ false
41
+ end
26
42
  end
27
43
  end
28
44
  end
@@ -1,31 +1,22 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
4
+ require_relative 'entities/entity'
5
+ require_relative 'entities/entity_with_arguments'
6
+ require_relative 'entities/has_identifier'
7
+
3
8
  require_relative 'entities/boolean'
4
- 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'
13
+ require_relative 'entities/reference'
10
14
  require_relative 'entities/runtime_value'
11
15
  require_relative 'entities/string'
12
- 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
27
-
28
- public_constant :TYPES
29
20
  end
30
21
  end
31
22
 
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # An error that represents an invalid CLI option or state
7
+ class CLIError < ::CSVPlusPlus::Error::Error
8
+ extend ::T::Sig
9
+
10
+ sig { override.returns(::String) }
11
+ # @return [String]
12
+ def error_message
13
+ message
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # An error that represents an invalid state during compilation.
7
+ class CompilerError < ::CSVPlusPlus::Error::Error
8
+ extend ::T::Sig
9
+
10
+ sig { override.returns(::String) }
11
+ # @return [String]
12
+ def error_message
13
+ message
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,10 +1,33 @@
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
- class Error < StandardError
7
- # TODO: perhaps give this a better name? something more descriptive than just Error
7
+ class Error < ::StandardError
8
+ extend ::T::Sig
9
+ extend ::T::Helpers
10
+
11
+ abstract!
12
+
13
+ sig { returns(::T.nilable(::StandardError)) }
14
+ attr_reader :wrapped_error
15
+
16
+ sig { params(message: ::String, wrapped_error: ::T.nilable(::StandardError)).void }
17
+ # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
18
+ # Racc::ParseError that was thrown
19
+ def initialize(message, wrapped_error: nil)
20
+ super(message)
21
+
22
+ @message = message
23
+ @wrapped_error = wrapped_error
24
+ end
25
+
26
+ sig { abstract.returns(::String) }
27
+ # Return an error message for display to a command-line user.
28
+ #
29
+ # @return [::String]
30
+ def error_message; end
8
31
  end
9
32
  end
10
33
  end
@@ -1,35 +1,35 @@
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 there is an error parsing a modifier
8
7
  #
9
8
  # @attr_reader message [::String] A helpful error message
10
9
  # @attr_reader bad_input [String] The offending input that caused the error to be thrown
11
- class FormulaSyntaxError < ::CSVPlusPlus::Error::SyntaxError
12
- attr_reader :message, :bad_input
10
+ class FormulaSyntaxError < ::CSVPlusPlus::Error::Error
11
+ extend ::T::Sig
12
+ include ::CSVPlusPlus::Error::PositionalError
13
13
 
14
- # You must supply either a +choices+ or +message+
15
- #
14
+ sig { returns(::String) }
15
+ attr_reader :bad_input
16
+
17
+ sig { params(message: ::String, bad_input: ::String, wrapped_error: ::T.nilable(::StandardError)).void }
16
18
  # @param message [String] A relevant message to show
17
19
  # @param bad_input [String] The offending input that caused the error to be thrown
18
- # @param runtime [Runtime] The current runtime
19
20
  # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
20
21
  # Racc::ParseError that was thrown
21
- def initialize(message, bad_input, runtime, wrapped_error: nil)
22
+ def initialize(message, bad_input:, wrapped_error: nil)
23
+ super(message, wrapped_error:)
22
24
  @bad_input = bad_input
23
- @message = message
24
-
25
- super(runtime, wrapped_error:)
26
25
  end
27
26
 
27
+ sig { override.returns(::String) }
28
28
  # Create a relevant error message given +@bad_input+ and +@message+.
29
29
  #
30
30
  # @return [::String]
31
31
  def error_message
32
- "#{@message}: \"#{@bad_input}\""
32
+ "#{message}: \"#{bad_input}\""
33
33
  end
34
34
  end
35
35
  end
@@ -1,26 +1,48 @@
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
- # 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
6
+ # A syntax error encountered when parsing a modifier definition
7
+ class ModifierSyntaxError < ::CSVPlusPlus::Error::Error
8
+ extend ::T::Sig
9
+ include ::CSVPlusPlus::Error::PositionalError
10
+
11
+ sig { returns(::String) }
12
+ attr_reader :bad_input
13
+
14
+ sig { returns(::T.nilable(::Symbol)) }
15
+ attr_reader :modifier
16
+
17
+ sig do
18
+ params(
19
+ message: ::String,
20
+ bad_input: ::String,
21
+ modifier: ::T.nilable(::Symbol),
22
+ wrapped_error: ::T.nilable(::StandardError)
23
+ ).void
24
+ end
25
+ # @param message [String] The error message
26
+ # @param bad_input [String] The offending input
27
+ # @param modifier [Symbol] The modifier being parsed
12
28
  # @param wrapped_error [ModifierValidationError] The validtion error that this is wrapping
13
- def initialize(runtime, wrapped_error:)
14
- @wrapped_error = wrapped_error
29
+ def initialize(message, bad_input:, modifier: nil, wrapped_error: nil)
30
+ super(message, wrapped_error:)
15
31
 
16
- super(runtime, wrapped_error:)
32
+ @bad_input = bad_input
33
+ @modifier = modifier
17
34
  end
18
35
 
19
- # Calls +wrapped_error.error_message+.
36
+ sig { override.returns(::String) }
37
+ # Create a relevant error message given +@choices+ or +@message+ (one of them must be supplied).
20
38
  #
21
39
  # @return [::String]
22
40
  def error_message
23
- @wrapped_error.error_message
41
+ <<~ERROR_MESSAGE
42
+ Error parsing modifier: [[#{@modifier}=...]]
43
+ Bad input: #{@bad_input}
44
+ Reason: #{@message}
45
+ ERROR_MESSAGE
24
46
  end
25
47
  end
26
48
  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.
@@ -10,39 +9,34 @@ module CSVPlusPlus
10
9
  # @attr_reader bad_input [String] The offending input that caused the error to be thrown
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
- class ModifierValidationError < ::CSVPlusPlus::Error::Error
14
- attr_reader :bad_input, :choices, :message, :modifier
12
+ class ModifierValidationError < ::CSVPlusPlus::Error::ModifierSyntaxError
13
+ extend ::T::Sig
14
+ include ::CSVPlusPlus::Error::PositionalError
15
15
 
16
+ sig do
17
+ params(
18
+ modifier: ::Symbol,
19
+ bad_input: ::String,
20
+ choices: ::T.nilable(::T.class_of(::T::Enum)),
21
+ message: ::T.nilable(::String)
22
+ ).void
23
+ end
16
24
  # You must supply either a +choices+ or +message+
17
25
  #
18
26
  # @param modifier [Symbol] The modifier being parsed when the bad input was encountered
19
27
  # @param bad_input [String] The offending input that caused the error to be thrown
20
28
  # @param choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
21
29
  # @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
+ def initialize(modifier, bad_input:, choices: nil, message: nil)
31
+ message = ::T.let(
32
+ if choices
33
+ "must be one of (#{choices.values.map(&:serialize).join(', ')})"
30
34
  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
35
+ ::T.must(message)
36
+ end,
37
+ ::String
38
+ )
39
+ super(message, bad_input:, modifier:)
46
40
  end
47
41
  end
48
42
  end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Error
6
+ # Methods that can be included into a class to denote that it's result it dependent on the current
7
+ # +Runtime::Position+
8
+ module PositionalError
9
+ extend ::T::Sig
10
+ extend ::T::Helpers
11
+
12
+ interface!
13
+ end
14
+ end
15
+ 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 csvpp template: #{message}"
14
+ end
7
15
  end
8
16
  end
9
17
  end
@@ -1,10 +1,14 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './error/error'
5
+ require_relative './error/positional_error'
6
+
7
+ require_relative './error/cli_error'
8
+ require_relative './error/compiler_error'
4
9
  require_relative './error/formula_syntax_error'
5
10
  require_relative './error/modifier_syntax_error'
6
11
  require_relative './error/modifier_validation_error'
7
- require_relative './error/syntax_error'
8
12
  require_relative './error/writer_error'
9
13
 
10
14
  module CSVPlusPlus
@@ -0,0 +1,111 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ # Handle any errors potentially thrown during compilation. This could be anything from a user error (for example
6
+ # calling with invalid csvpp code) to an error calling Google Sheets API or writing to the filesystem.
7
+ class ErrorFormatter
8
+ extend ::T::Sig
9
+
10
+ sig do
11
+ params(options: ::CSVPlusPlus::Options::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void
12
+ end
13
+ # @param options [Options]
14
+ # @param runtime [Runtime::Runtime]
15
+ def initialize(options:, runtime:)
16
+ @options = options
17
+ @runtime = runtime
18
+ end
19
+
20
+ sig { params(error: ::StandardError).void }
21
+ # Nicely handle a given error. How it's handled depends on if it's our error and if @options.verbose
22
+ #
23
+ # @param error [CSVPlusPlus::Error, Google::Apis::ClientError, StandardError]
24
+ def handle_error(error)
25
+ # make sure that we're on a newline (verbose mode will probably be in the middle of printing a benchmark)
26
+ puts("\n\n") if @options.verbose
27
+
28
+ case error
29
+ when ::CSVPlusPlus::Error::Error
30
+ handle_internal_error(error)
31
+ when ::Google::Apis::ClientError
32
+ handle_google_error(error)
33
+ else
34
+ unhandled_error(error)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ sig { params(error: ::StandardError).void }
41
+ # An error was thrown that we weren't planning on
42
+ def unhandled_error(error)
43
+ warn(
44
+ <<~ERROR_MESSAGE)
45
+ An unexpected error was encountered. Please try running again with --verbose and
46
+ report the error at: https://github.com/patrickomatic/csv-plus-plus/issues/new'
47
+ ERROR_MESSAGE
48
+
49
+ return unless @options.verbose
50
+
51
+ warn(error.full_message)
52
+ warn("Cause: #{error.cause}") if error.cause
53
+ end
54
+
55
+ sig { params(error: ::CSVPlusPlus::Error::Error).void }
56
+ def handle_internal_error(error)
57
+ warn(with_position(error))
58
+ handle_wrapped_error(::T.must(error.wrapped_error)) if error.wrapped_error
59
+ end
60
+
61
+ sig { params(wrapped_error: ::StandardError).void }
62
+ def handle_wrapped_error(wrapped_error)
63
+ return unless @options.verbose
64
+
65
+ warn(wrapped_error.full_message)
66
+ warn((wrapped_error.backtrace || []).join("\n")) if wrapped_error.backtrace
67
+ end
68
+
69
+ sig { params(error: ::Google::Apis::ClientError).void }
70
+ def handle_google_error(error)
71
+ warn("Error making Google Sheets API request: #{error.message}")
72
+ return unless @options.verbose
73
+
74
+ warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
75
+ end
76
+
77
+ sig { params(error: ::CSVPlusPlus::Error::Error).returns(::String) }
78
+ # Output a user-helpful string that references the runtime state
79
+ #
80
+ # @param error [Error::Error] The error message to be prefixed with a filename and position
81
+ #
82
+ # @return [String]
83
+ def with_position(error)
84
+ message = error.error_message
85
+ case error
86
+ when ::CSVPlusPlus::Error::PositionalError
87
+ "#{message_prefix}#{cell_index} #{message}"
88
+ else
89
+ message
90
+ end
91
+ end
92
+
93
+ sig { returns(::String) }
94
+ def cell_index
95
+ if @runtime.parsing_csv_section?
96
+ "[#{@runtime.position.row_index},#{@runtime.position.cell_index}]"
97
+ else
98
+ ''
99
+ end
100
+ end
101
+
102
+ sig { returns(::String) }
103
+ def message_prefix
104
+ line_number = @runtime.position.line_number
105
+ filename = @runtime.source_code.filename
106
+
107
+ line_str = ":#{line_number}"
108
+ "#{filename}#{line_str}"
109
+ end
110
+ end
111
+ end
@@ -1,24 +1,39 @@
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
- def self.sheets_client
10
- ::Google::Apis::SheetsV4::SheetsService.new.tap do |s|
11
- s.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/spreadsheets'].freeze)
12
- end
13
+ def sheets_client
14
+ ::T.must(
15
+ @sheets_client ||= ::T.let(
16
+ ::Google::Apis::SheetsV4::SheetsService.new.tap do |s|
17
+ s.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/spreadsheets'].freeze)
18
+ end,
19
+ ::T.nilable(::Google::Apis::SheetsV4::SheetsService)
20
+ )
21
+ )
13
22
  end
14
23
 
15
- # Get a +::Google::Apis::DriveV3::DriveService+ instance connected to the drive API
24
+ sig { returns(::Google::Apis::DriveV3::DriveService) }
25
+ # Get a +Google::Apis::DriveV3::DriveService+ instance connected to the drive API
16
26
  #
17
27
  # @return [Google::Apis::DriveV3::DriveService]
18
- def self.drive_client
19
- ::Google::Apis::DriveV3::DriveService.new.tap do |d|
20
- d.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/drive.file'].freeze)
21
- end
28
+ def drive_client
29
+ ::T.must(
30
+ @drive_client ||= ::T.let(
31
+ ::Google::Apis::DriveV3::DriveService.new.tap do |d|
32
+ d.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/drive.file'].freeze)
33
+ end,
34
+ ::T.nilable(::Google::Apis::DriveV3::DriveService)
35
+ )
36
+ )
22
37
  end
23
38
  end
24
39
  end