csv_plus_plus 0.1.2 → 0.2.0

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 (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