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