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.
- checksums.yaml +4 -4
- data/README.md +9 -5
- data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +29 -41
- data/lib/csv_plus_plus/cli.rb +53 -80
- data/lib/csv_plus_plus/cli_flag.rb +71 -71
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +98 -66
- data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
- data/lib/csv_plus_plus/entities/boolean.rb +26 -10
- data/lib/csv_plus_plus/entities/builtins.rb +66 -24
- data/lib/csv_plus_plus/entities/date.rb +42 -6
- data/lib/csv_plus_plus/entities/entity.rb +17 -69
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
- data/lib/csv_plus_plus/entities/function.rb +34 -11
- data/lib/csv_plus_plus/entities/function_call.rb +49 -10
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +30 -11
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
- data/lib/csv_plus_plus/entities/string.rb +23 -7
- data/lib/csv_plus_plus/entities.rb +7 -16
- data/lib/csv_plus_plus/error/cli_error.rb +17 -0
- data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
- data/lib/csv_plus_plus/error/error.rb +25 -2
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +5 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +25 -10
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
- data/lib/csv_plus_plus/lexer.rb +64 -1
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
- data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
- data/lib/csv_plus_plus/modifier/expand.rb +78 -0
- data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
- data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
- data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
- data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
- data/lib/csv_plus_plus/modifier.rb +89 -160
- data/lib/csv_plus_plus/options/file_options.rb +49 -0
- data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
- data/lib/csv_plus_plus/options/options.rb +97 -0
- data/lib/csv_plus_plus/options.rb +34 -77
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
- data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
- data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
- data/lib/csv_plus_plus/reader/csv.rb +50 -0
- data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
- data/lib/csv_plus_plus/reader/reader.rb +27 -0
- data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
- data/lib/csv_plus_plus/reader.rb +14 -0
- data/lib/csv_plus_plus/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position.rb +242 -0
- data/lib/csv_plus_plus/runtime/references.rb +115 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +71 -0
- data/lib/csv_plus_plus/template.rb +71 -39
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/csv.rb +37 -8
- data/lib/csv_plus_plus/writer/excel.rb +25 -5
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
- data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
- data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
- data/lib/csv_plus_plus/writer/merger.rb +31 -0
- data/lib/csv_plus_plus/writer/open_document.rb +21 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +79 -10
- data/lib/csv_plus_plus.rb +47 -18
- metadata +50 -21
- data/lib/csv_plus_plus/can_define_references.rb +0 -88
- data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
- data/lib/csv_plus_plus/data_validation.rb +0 -138
- data/lib/csv_plus_plus/entities/cell_reference.rb +0 -60
- data/lib/csv_plus_plus/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/google_options.rb +0 -27
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
- data/lib/csv_plus_plus/references.rb +0 -68
- data/lib/csv_plus_plus/scope.rb +0 -196
- data/lib/csv_plus_plus/validated_modifier.rb +0 -164
- data/lib/csv_plus_plus/writer/base_writer.rb +0 -20
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- 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
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
18
|
+
super()
|
|
14
19
|
|
|
15
|
-
@value = value.gsub(/^"|"$/, '')
|
|
20
|
+
@value = ::T.let(value.gsub(/^"|"$/, ''), ::String)
|
|
16
21
|
end
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
31
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
32
|
+
# @param other [BasicObject]
|
|
33
|
+
#
|
|
34
|
+
# @return [T::Boolean]
|
|
24
35
|
def ==(other)
|
|
25
|
-
|
|
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
|
-
|
|
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::
|
|
12
|
-
|
|
10
|
+
class FormulaSyntaxError < ::CSVPlusPlus::Error::Error
|
|
11
|
+
extend ::T::Sig
|
|
12
|
+
include ::CSVPlusPlus::Error::PositionalError
|
|
13
13
|
|
|
14
|
-
|
|
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
|
|
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
|
-
"#{
|
|
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
|
-
#
|
|
8
|
-
class ModifierSyntaxError < ::CSVPlusPlus::Error::
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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(
|
|
14
|
-
|
|
29
|
+
def initialize(message, bad_input:, modifier: nil, wrapped_error: nil)
|
|
30
|
+
super(message, wrapped_error:)
|
|
15
31
|
|
|
16
|
-
|
|
32
|
+
@bad_input = bad_input
|
|
33
|
+
@modifier = modifier
|
|
17
34
|
end
|
|
18
35
|
|
|
19
|
-
|
|
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
|
-
|
|
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::
|
|
14
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
data/lib/csv_plus_plus/error.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
10
|
-
::
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
19
|
-
::
|
|
20
|
-
|
|
21
|
-
|
|
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
|