csv_plus_plus 0.1.2 → 0.1.3
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 +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
|