csv_plus_plus 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +23 -13
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +82 -60
- data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
- data/lib/csv_plus_plus/entities/boolean.rb +18 -9
- data/lib/csv_plus_plus/entities/builtins.rb +23 -9
- data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
- data/lib/csv_plus_plus/entities/date.rb +38 -5
- data/lib/csv_plus_plus/entities/entity.rb +27 -61
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +23 -11
- data/lib/csv_plus_plus/entities/function_call.rb +24 -9
- data/lib/csv_plus_plus/entities/number.rb +24 -10
- data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
- data/lib/csv_plus_plus/entities/string.rb +19 -6
- data/lib/csv_plus_plus/entities/variable.rb +16 -4
- data/lib/csv_plus_plus/entities.rb +20 -13
- data/lib/csv_plus_plus/error/error.rb +11 -1
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
- data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +1 -0
- data/lib/csv_plus_plus/google_api_client.rb +7 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
- data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
- data/lib/csv_plus_plus/lexer.rb +24 -0
- 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 +61 -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 +82 -158
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
- data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
- data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
- data/lib/csv_plus_plus/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
- data/lib/csv_plus_plus/runtime/references.rb +110 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +62 -35
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
- data/lib/csv_plus_plus/writer/csv.rb +11 -9
- data/lib/csv_plus_plus/writer/excel.rb +9 -2
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
- data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
- data/lib/csv_plus_plus/writer/open_document.rb +6 -1
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +29 -12
- metadata +18 -14
- 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/expand.rb +0 -20
- data/lib/csv_plus_plus/graph.rb +0 -62
- 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/google_sheet_modifier.rb +0 -77
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -7,19 +8,35 @@ module CSVPlusPlus
|
|
7
8
|
#
|
8
9
|
# @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
|
9
10
|
class RuntimeValue < Entity
|
10
|
-
|
11
|
+
extend ::T::Sig
|
11
12
|
|
13
|
+
sig { returns(::T::Array[::T.untyped]) }
|
14
|
+
attr_reader :arguments
|
15
|
+
|
16
|
+
sig { returns(::T.proc.params(arg0: ::CSVPlusPlus::Runtime::Runtime).returns(::CSVPlusPlus::Entities::Entity)) }
|
17
|
+
attr_reader :resolve_fn
|
18
|
+
|
19
|
+
sig do
|
20
|
+
params(
|
21
|
+
resolve_fn: ::T.proc.params(arg0: ::CSVPlusPlus::Runtime::Runtime).returns(::CSVPlusPlus::Entities::Entity),
|
22
|
+
arguments: ::T::Array[::T.untyped]
|
23
|
+
).void
|
24
|
+
end
|
12
25
|
# @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
|
13
|
-
|
14
|
-
|
26
|
+
# @param arguments [Any] Arguments to the runtime value call
|
27
|
+
def initialize(resolve_fn, arguments: [])
|
28
|
+
super(::CSVPlusPlus::Entities::Type::RuntimeValue)
|
15
29
|
|
16
30
|
@arguments = arguments
|
17
31
|
@resolve_fn = resolve_fn
|
18
32
|
end
|
19
33
|
|
34
|
+
sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
35
|
+
# @param _runtime [Runtime]
|
36
|
+
#
|
20
37
|
# @return [String]
|
21
|
-
def
|
22
|
-
'(
|
38
|
+
def evaluate(_runtime)
|
39
|
+
'(runtime value)'
|
23
40
|
end
|
24
41
|
end
|
25
42
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -6,23 +7,35 @@ module CSVPlusPlus
|
|
6
7
|
#
|
7
8
|
# @attr_reader value [String]
|
8
9
|
class String < 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(::CSVPlusPlus::Entities::Type::String)
|
14
19
|
|
15
|
-
@value = value.gsub(/^"|"$/, '')
|
20
|
+
@value = ::T.let(value.gsub(/^"|"$/, ''), ::String)
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
24
|
+
# @param _runtime [Runtime]
|
25
|
+
#
|
26
|
+
# @return [::String]
|
27
|
+
def evaluate(_runtime)
|
20
28
|
"\"#{@value}\""
|
21
29
|
end
|
22
30
|
|
23
|
-
|
31
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
32
|
+
# @param other [Entity]
|
33
|
+
#
|
34
|
+
# @return [T::Boolean]
|
24
35
|
def ==(other)
|
25
|
-
|
36
|
+
return false unless super
|
37
|
+
|
38
|
+
other.is_a?(self.class) && @value == other.value
|
26
39
|
end
|
27
40
|
end
|
28
41
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -6,19 +7,30 @@ module CSVPlusPlus
|
|
6
7
|
#
|
7
8
|
# A reference to a variable
|
8
9
|
class Variable < Entity
|
10
|
+
extend ::T::Sig
|
11
|
+
|
12
|
+
sig { params(id: ::Symbol).void }
|
9
13
|
# @param id [Symbol] The identifier of the variable
|
10
14
|
def initialize(id)
|
11
|
-
super(
|
15
|
+
super(::CSVPlusPlus::Entities::Type::Variable, id:)
|
12
16
|
end
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
19
|
+
# @param _runtime [Runtime]
|
20
|
+
#
|
21
|
+
# @return [::String]
|
22
|
+
def evaluate(_runtime)
|
16
23
|
"$$#{@id}"
|
17
24
|
end
|
18
25
|
|
26
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
27
|
+
# @param other [Entity]
|
28
|
+
#
|
19
29
|
# @return [boolean]
|
20
30
|
def ==(other)
|
21
|
-
|
31
|
+
return false unless super
|
32
|
+
|
33
|
+
other.is_a?(self.class) && @id == other.id
|
22
34
|
end
|
23
35
|
end
|
24
36
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
4
|
+
require_relative 'entities/entity'
|
5
|
+
require_relative 'entities/entity_with_arguments'
|
6
|
+
|
3
7
|
require_relative 'entities/boolean'
|
4
8
|
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'
|
@@ -12,20 +15,24 @@ require_relative 'entities/string'
|
|
12
15
|
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
|
-
|
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
|
20
|
+
extend ::T::Sig
|
27
21
|
|
28
|
-
|
22
|
+
# A primitive type. These all correspond to an implementation of the same name in the CSVPlusPlus::Entities module.
|
23
|
+
class Type < ::T::Enum
|
24
|
+
enums do
|
25
|
+
Boolean = new
|
26
|
+
CellReference = new
|
27
|
+
Date = new
|
28
|
+
Function = new
|
29
|
+
FunctionCall = new
|
30
|
+
Number = new
|
31
|
+
RuntimeValue = new
|
32
|
+
String = new
|
33
|
+
Variable = new
|
34
|
+
end
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|
31
38
|
|
@@ -1,10 +1,20 @@
|
|
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
7
|
class Error < StandardError
|
7
|
-
|
8
|
+
extend ::T::Sig
|
9
|
+
extend ::T::Helpers
|
10
|
+
|
11
|
+
sig { returns(::String) }
|
12
|
+
# Return an error message for display to a command-line user.
|
13
|
+
#
|
14
|
+
# @return [::String]
|
15
|
+
def error_message
|
16
|
+
message
|
17
|
+
end
|
8
18
|
end
|
9
19
|
end
|
10
20
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative './syntax_error'
|
@@ -6,21 +7,68 @@ module CSVPlusPlus
|
|
6
7
|
module Error
|
7
8
|
# An Error that wraps a +ModifierValidationError+ with a +Runtime+.
|
8
9
|
class ModifierSyntaxError < ::CSVPlusPlus::Error::SyntaxError
|
9
|
-
|
10
|
+
extend ::T::Sig
|
11
|
+
|
12
|
+
sig { returns(::String) }
|
13
|
+
attr_reader :bad_input
|
14
|
+
|
15
|
+
sig { returns(::String) }
|
16
|
+
attr_reader :message
|
17
|
+
|
18
|
+
sig { returns(::T.nilable(::Symbol)) }
|
19
|
+
attr_reader :modifier
|
20
|
+
|
21
|
+
sig do
|
22
|
+
params(
|
23
|
+
runtime: ::CSVPlusPlus::Runtime::Runtime,
|
24
|
+
modifier_validation_error: ::CSVPlusPlus::Error::ModifierValidationError
|
25
|
+
).returns(::CSVPlusPlus::Error::ModifierSyntaxError)
|
26
|
+
end
|
27
|
+
# Create a +ModifierSyntaxError+ given a +runtime+ and +ModifierValidationError+.
|
10
28
|
#
|
29
|
+
# @param runtime [Runtime]
|
30
|
+
# @param modifier_validation_error [ModifierValidationError]
|
31
|
+
#
|
32
|
+
# @return [ModifierSyntaxError]
|
33
|
+
def self.from_validation_error(runtime, modifier_validation_error)
|
34
|
+
new(
|
35
|
+
runtime,
|
36
|
+
modifier: modifier_validation_error.modifier,
|
37
|
+
bad_input: modifier_validation_error.bad_input,
|
38
|
+
message: modifier_validation_error.message,
|
39
|
+
wrapped_error: modifier_validation_error
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig do
|
44
|
+
params(
|
45
|
+
runtime: ::CSVPlusPlus::Runtime::Runtime,
|
46
|
+
bad_input: ::String,
|
47
|
+
message: ::String,
|
48
|
+
modifier: ::T.nilable(::Symbol),
|
49
|
+
wrapped_error: ::T.nilable(::StandardError)
|
50
|
+
).void
|
51
|
+
end
|
11
52
|
# @param runtime [Runtime] The current runtime
|
12
53
|
# @param wrapped_error [ModifierValidationError] The validtion error that this is wrapping
|
13
|
-
def initialize(runtime, wrapped_error:)
|
14
|
-
@
|
54
|
+
def initialize(runtime, bad_input:, message:, modifier: nil, wrapped_error: nil)
|
55
|
+
@bad_input = bad_input
|
56
|
+
@modifier = modifier
|
57
|
+
@message = message
|
15
58
|
|
16
59
|
super(runtime, wrapped_error:)
|
17
60
|
end
|
18
61
|
|
19
|
-
|
62
|
+
sig { override.returns(::String) }
|
63
|
+
# Create a relevant error message given +@choices+ or +@message+ (one of them must be supplied).
|
20
64
|
#
|
21
65
|
# @return [::String]
|
22
66
|
def error_message
|
23
|
-
|
67
|
+
<<~ERROR_MESSAGE
|
68
|
+
Error parsing modifier: [[#{@modifier}=...]]
|
69
|
+
Bad input: #{@bad_input}
|
70
|
+
Reason: #{@message}
|
71
|
+
ERROR_MESSAGE
|
24
72
|
end
|
25
73
|
end
|
26
74
|
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.
|
@@ -11,38 +10,59 @@ module CSVPlusPlus
|
|
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
12
|
class ModifierValidationError < ::CSVPlusPlus::Error::Error
|
14
|
-
|
13
|
+
extend ::T::Sig
|
14
|
+
|
15
|
+
sig { returns(::String) }
|
16
|
+
attr_reader :bad_input
|
17
|
+
|
18
|
+
sig { returns(::T.nilable(::T.class_of(::T::Enum))) }
|
19
|
+
attr_reader :choices
|
15
20
|
|
21
|
+
sig { returns(::String) }
|
22
|
+
attr_reader :message
|
23
|
+
|
24
|
+
sig { returns(::Symbol) }
|
25
|
+
attr_reader :modifier
|
26
|
+
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
modifier: ::Symbol,
|
30
|
+
bad_input: ::String,
|
31
|
+
choices: ::T.nilable(::T.class_of(::T::Enum)),
|
32
|
+
message: ::T.nilable(::String)
|
33
|
+
).void
|
34
|
+
end
|
16
35
|
# You must supply either a +choices+ or +message+
|
17
36
|
#
|
18
37
|
# @param modifier [Symbol] The modifier being parsed when the bad input was encountered
|
19
38
|
# @param bad_input [String] The offending input that caused the error to be thrown
|
20
39
|
# @param choices [Array<Symbol>, nil] The choices that +value+ must be one of (but violated)
|
21
40
|
# @param message [String, nil] A relevant message to show
|
22
|
-
|
41
|
+
# rubocop:disable Metrics/MethodLength
|
42
|
+
def initialize(modifier, bad_input:, choices: nil, message: nil)
|
23
43
|
@bad_input = bad_input
|
24
44
|
@choices = choices
|
25
45
|
@modifier = modifier
|
26
46
|
|
27
|
-
@message =
|
47
|
+
@message = ::T.let(
|
28
48
|
if @choices
|
29
|
-
"must be one of (#{@choices.map(&:
|
49
|
+
"must be one of (#{@choices.values.map(&:serialize).join(', ')})"
|
30
50
|
else
|
31
|
-
message
|
32
|
-
end
|
51
|
+
::T.must(message)
|
52
|
+
end,
|
53
|
+
::String
|
54
|
+
)
|
33
55
|
|
34
56
|
super(@message)
|
35
57
|
end
|
58
|
+
# rubocop:enable Metrics/MethodLength
|
36
59
|
|
37
|
-
|
60
|
+
sig { returns(::String) }
|
61
|
+
# A user-facing error message
|
38
62
|
#
|
39
63
|
# @return [::String]
|
40
64
|
def error_message
|
41
|
-
|
42
|
-
Error parsing modifier: [[#{@modifier}=...]]
|
43
|
-
Bad input: #{@bad_input}
|
44
|
-
Reason: #{@message}
|
45
|
-
ERROR_MESSAGE
|
65
|
+
@message
|
46
66
|
end
|
47
67
|
end
|
48
68
|
end
|
@@ -1,9 +1,13 @@
|
|
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 for various syntax errors
|
6
7
|
class SyntaxError < ::CSVPlusPlus::Error::Error
|
8
|
+
extend ::T::Sig
|
9
|
+
|
10
|
+
sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime, wrapped_error: ::T.nilable(::StandardError)).void }
|
7
11
|
# @param runtime [Runtime] The current runtime
|
8
12
|
# @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
|
9
13
|
# Racc::ParseError that was thrown
|
@@ -14,18 +18,28 @@ module CSVPlusPlus
|
|
14
18
|
super()
|
15
19
|
end
|
16
20
|
|
17
|
-
|
21
|
+
sig { returns(::String) }
|
22
|
+
# @return [::String]
|
18
23
|
def to_s
|
19
24
|
to_trace
|
20
25
|
end
|
21
26
|
|
27
|
+
# TODO: clean up all these different string-formatting error classes
|
28
|
+
sig { override.returns(::String) }
|
29
|
+
# @return [::String]
|
30
|
+
def error_message
|
31
|
+
''
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { returns(::String) }
|
22
35
|
# Output a verbose user-helpful string that references the current runtime
|
23
36
|
def to_verbose_trace
|
24
37
|
warn(@wrapped_error.full_message) if @wrapped_error
|
25
|
-
warn(@wrapped_error.backtrace) if @wrapped_error
|
38
|
+
warn((@wrapped_error.backtrace || []).join("\n")) if @wrapped_error&.backtrace
|
26
39
|
to_trace
|
27
40
|
end
|
28
41
|
|
42
|
+
sig { returns(::String) }
|
29
43
|
# Output a user-helpful string that references the runtime state
|
30
44
|
#
|
31
45
|
# @return [String]
|
@@ -35,22 +49,21 @@ module CSVPlusPlus
|
|
35
49
|
|
36
50
|
private
|
37
51
|
|
52
|
+
sig { returns(::String) }
|
38
53
|
def cell_index
|
39
|
-
|
40
|
-
|
41
|
-
"[#{row_index},#{@runtime.cell_index}]"
|
42
|
-
elsif row_index
|
43
|
-
"[#{row_index}]"
|
54
|
+
if @runtime.parsing_csv_section?
|
55
|
+
"[#{@runtime.row_index},#{@runtime.cell_index}]"
|
44
56
|
else
|
45
57
|
''
|
46
58
|
end
|
47
59
|
end
|
48
60
|
|
61
|
+
sig { returns(::String) }
|
49
62
|
def message_prefix
|
50
63
|
line_number = @runtime.line_number
|
51
|
-
filename = @runtime.filename
|
64
|
+
filename = @runtime.source_code.filename
|
52
65
|
|
53
|
-
line_str =
|
66
|
+
line_str = ":#{line_number}"
|
54
67
|
"#{filename}#{line_str}"
|
55
68
|
end
|
56
69
|
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 template: #{message}"
|
14
|
+
end
|
7
15
|
end
|
8
16
|
end
|
9
17
|
end
|
data/lib/csv_plus_plus/error.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
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
13
|
def self.sheets_client
|
@@ -12,7 +16,8 @@ module CSVPlusPlus
|
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
15
|
-
|
19
|
+
sig { returns(::Google::Apis::DriveV3::DriveService) }
|
20
|
+
# Get a +Google::Apis::DriveV3::DriveService+ instance connected to the drive API
|
16
21
|
#
|
17
22
|
# @return [Google::Apis::DriveV3::DriveService]
|
18
23
|
def self.drive_client
|
@@ -1,27 +1,32 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
4
|
-
# The Google-specific options a user can supply
|
5
|
+
# The Google-specific options a user can supply.
|
5
6
|
#
|
6
|
-
# attr sheet_id [String] The ID of the Google Sheet to write to
|
7
|
-
GoogleOptions
|
8
|
-
::
|
9
|
-
# Format a string with a verbose description of what we're doing with the options
|
10
|
-
#
|
11
|
-
# @return [String]
|
12
|
-
def verbose_summary
|
13
|
-
<<~SUMMARY
|
14
|
-
## Google Sheets Options
|
7
|
+
# @attr sheet_id [String] The ID of the Google Sheet to write to.
|
8
|
+
class GoogleOptions
|
9
|
+
extend ::T::Sig
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
11
|
+
sig { returns(::String) }
|
12
|
+
attr_reader :sheet_id
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
sig { params(sheet_id: ::String).void }
|
15
|
+
# @param sheet_id [String] The unique ID Google uses to reference the sheet
|
16
|
+
def initialize(sheet_id)
|
17
|
+
@sheet_id = sheet_id
|
24
18
|
end
|
25
19
|
|
26
|
-
|
20
|
+
sig { returns(::String) }
|
21
|
+
# Format a string with a verbose description of what we're doing with the options
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
def verbose_summary
|
25
|
+
<<~SUMMARY
|
26
|
+
## Google Sheets Options
|
27
|
+
|
28
|
+
> Sheet ID | #{@sheet_id}
|
29
|
+
SUMMARY
|
30
|
+
end
|
31
|
+
end
|
27
32
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
+
# typed: false
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require_relative '../color'
|
4
|
-
|
5
4
|
module CSVPlusPlus
|
6
5
|
# Common methods to be mixed into the Racc parsers
|
7
6
|
#
|
@@ -30,13 +29,15 @@ module CSVPlusPlus
|
|
30
29
|
|
31
30
|
return return_value unless anything_to_parse?(input)
|
32
31
|
|
32
|
+
@runtime = runtime
|
33
|
+
|
33
34
|
tokenize(input, runtime)
|
34
35
|
do_parse
|
35
36
|
return_value
|
36
37
|
rescue ::Racc::ParseError => e
|
37
38
|
runtime.raise_formula_syntax_error("Error parsing #{parse_subject}", e.message, wrapped_error: e)
|
38
39
|
rescue ::CSVPlusPlus::Error::ModifierValidationError => e
|
39
|
-
raise(::CSVPlusPlus::Error::ModifierSyntaxError.
|
40
|
+
raise(::CSVPlusPlus::Error::ModifierSyntaxError.from_validation_error(runtime, e))
|
40
41
|
end
|
41
42
|
|
42
43
|
TOKEN_LIBRARY = {
|
@@ -76,8 +77,11 @@ module CSVPlusPlus
|
|
76
77
|
@tokens << [tokenizer.last_token, tokenizer.last_match]
|
77
78
|
elsif tokenizer.scan_catchall
|
78
79
|
@tokens << [tokenizer.last_match, tokenizer.last_match]
|
80
|
+
# TODO: checking the +parse_subject+ like this is a little hacky... but we need to know if we're parsing
|
81
|
+
# modifiers or code_section (or formulas in a cell)
|
82
|
+
elsif parse_subject == 'modifier'
|
83
|
+
runtime.raise_modifier_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
|
79
84
|
else
|
80
|
-
# TODO: this should raise a modifier_syntax_error if we're on the modifier parser
|
81
85
|
runtime.raise_formula_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
|
82
86
|
end
|
83
87
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: true
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'strscan'
|
@@ -11,7 +12,11 @@ module CSVPlusPlus
|
|
11
12
|
class Tokenizer
|
12
13
|
attr_reader :last_token, :scanner
|
13
14
|
|
14
|
-
# @param
|
15
|
+
# @param tokens [Array<Regexp, String>] The list of tokens to scan
|
16
|
+
# @param catchall [Regexp] A final regexp to try if nothing else matches
|
17
|
+
# @param ignore [Regexp] Ignore anything matching this regexp
|
18
|
+
# @param alter_matches [Object] A map of matches to alter
|
19
|
+
# @param stop_fn [Proc] Stop parsing when this is true
|
15
20
|
def initialize(tokens:, catchall: nil, ignore: nil, alter_matches: {}, stop_fn: nil)
|
16
21
|
@last_token = nil
|
17
22
|
|
data/lib/csv_plus_plus/lexer.rb
CHANGED
@@ -1,14 +1,38 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative './lexer/lexer'
|
4
5
|
require_relative './lexer/tokenizer'
|
5
6
|
|
6
7
|
module CSVPlusPlus
|
8
|
+
# Code for tokenizing a csvpp file
|
7
9
|
module Lexer
|
10
|
+
extend ::T::Sig
|
11
|
+
|
8
12
|
END_OF_CODE_SECTION = '---'
|
9
13
|
public_constant :END_OF_CODE_SECTION
|
10
14
|
|
11
15
|
VARIABLE_REF = '$$'
|
12
16
|
public_constant :VARIABLE_REF
|
17
|
+
|
18
|
+
sig { params(str: ::String).returns(::String) }
|
19
|
+
# When parsing a modifier with a quoted string field, we need a way to unescape. Some examples of quoted and
|
20
|
+
# unquoted results:
|
21
|
+
#
|
22
|
+
# * "just a string" => "just a string"
|
23
|
+
# * "' this is a string'" => "this is a string"
|
24
|
+
# * "won\'t this work?" => "won't this work"
|
25
|
+
#
|
26
|
+
# @param str [::String]
|
27
|
+
#
|
28
|
+
# @return [::String]
|
29
|
+
def self.unquote(str)
|
30
|
+
# could probably do this with one regex but we do it in 3 steps:
|
31
|
+
#
|
32
|
+
# 1. remove leading and trailing spaces and '
|
33
|
+
# 2. remove any backslashes that are by themselves (none on either side)
|
34
|
+
# 3. turn double backslashes into singles
|
35
|
+
str.gsub(/^\s*'?|'?\s*$/, '').gsub(/([^\\]+)\\([^\\]+)/, '\1\2').gsub(/\\\\/, '\\')
|
36
|
+
end
|
13
37
|
end
|
14
38
|
end
|