csv_plus_plus 0.1.3 → 0.2.1
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 +13 -3
- data/docs/CHANGELOG.md +18 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +3 -3
- data/lib/csv_plus_plus/cell.rb +1 -35
- data/lib/csv_plus_plus/cli.rb +43 -80
- data/lib/csv_plus_plus/cli_flag.rb +77 -70
- data/lib/csv_plus_plus/color.rb +1 -1
- data/lib/csv_plus_plus/compiler.rb +31 -21
- data/lib/csv_plus_plus/entities/ast_builder.rb +11 -4
- data/lib/csv_plus_plus/entities/boolean.rb +16 -9
- data/lib/csv_plus_plus/entities/builtins.rb +68 -40
- data/lib/csv_plus_plus/entities/date.rb +14 -11
- data/lib/csv_plus_plus/entities/entity.rb +11 -29
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +18 -31
- data/lib/csv_plus_plus/entities/function.rb +22 -11
- data/lib/csv_plus_plus/entities/function_call.rb +35 -11
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +15 -10
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +36 -23
- data/lib/csv_plus_plus/entities/string.rb +13 -10
- data/lib/csv_plus_plus/entities.rb +2 -18
- 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 +18 -5
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -13
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +10 -36
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +6 -32
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +1 -1
- data/lib/csv_plus_plus/error.rb +4 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +18 -8
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +53 -17
- data/lib/csv_plus_plus/lexer.rb +40 -1
- data/lib/csv_plus_plus/modifier/data_validation.rb +1 -1
- data/lib/csv_plus_plus/modifier/expand.rb +17 -0
- data/lib/csv_plus_plus/modifier.rb +6 -1
- 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 +102 -0
- data/lib/csv_plus_plus/options.rb +22 -110
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +65 -66
- data/lib/csv_plus_plus/parser/code_section.tab.rb +92 -84
- data/lib/csv_plus_plus/parser/modifier.tab.rb +40 -30
- 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/runtime/graph.rb +6 -6
- data/lib/csv_plus_plus/runtime/{position_tracker.rb → position.rb} +16 -5
- data/lib/csv_plus_plus/runtime/references.rb +32 -27
- data/lib/csv_plus_plus/runtime/runtime.rb +73 -67
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +9 -9
- data/lib/csv_plus_plus/source_code.rb +14 -9
- data/lib/csv_plus_plus/template.rb +17 -12
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/csv.rb +32 -5
- data/lib/csv_plus_plus/writer/excel.rb +19 -6
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -14
- data/lib/csv_plus_plus/writer/google_sheets.rb +23 -129
- data/lib/csv_plus_plus/writer/{google_sheet_builder.rb → google_sheets_builder.rb} +39 -55
- data/lib/csv_plus_plus/writer/merger.rb +56 -0
- data/lib/csv_plus_plus/writer/open_document.rb +16 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +68 -43
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +58 -19
- data/lib/csv_plus_plus.rb +26 -14
- metadata +43 -18
- data/lib/csv_plus_plus/entities/cell_reference.rb +0 -231
- data/lib/csv_plus_plus/entities/variable.rb +0 -37
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -71
- data/lib/csv_plus_plus/google_options.rb +0 -32
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -89
- data/lib/csv_plus_plus/runtime/can_define_references.rb +0 -87
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +0 -209
- data/lib/csv_plus_plus/writer/base_writer.rb +0 -45
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module CSVPlusPlus
|
|
@@ -8,83 +8,90 @@ module CSVPlusPlus
|
|
|
8
8
|
# @attr_reader long_flag [String] A definition of the long/word-based flag
|
|
9
9
|
# @attr_reader description [String] A description of what the flag does
|
|
10
10
|
# @attr_reader handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
|
|
11
|
-
class
|
|
12
|
-
|
|
11
|
+
class CLIFlag
|
|
12
|
+
extend ::T::Sig
|
|
13
13
|
|
|
14
|
+
sig { returns(::String) }
|
|
15
|
+
attr_reader :description
|
|
16
|
+
|
|
17
|
+
sig { returns(::String) }
|
|
18
|
+
attr_reader :long_flag
|
|
19
|
+
|
|
20
|
+
sig { returns(::String) }
|
|
21
|
+
attr_reader :short_flag
|
|
22
|
+
|
|
23
|
+
sig { params(short_flag: ::String, long_flag: ::String, description: ::String).void }
|
|
14
24
|
# @param short_flag [String] A definition of the short/single-character flag
|
|
15
25
|
# @param long_flag [String] A definition of the long/word-based flag
|
|
16
26
|
# @param description [String] A description of what the flag does
|
|
17
|
-
|
|
18
|
-
def initialize(short_flag, long_flag, description, handler)
|
|
27
|
+
def initialize(short_flag, long_flag, description)
|
|
19
28
|
@short_flag = short_flag
|
|
20
29
|
@long_flag = long_flag
|
|
21
30
|
@description = description
|
|
22
|
-
@handler = handler
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# @return [String]
|
|
26
|
-
def to_s
|
|
27
|
-
"#{@short_flag}, #{@long_flag} #{@description}"
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
|
-
end
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
),
|
|
52
|
-
::CSVPlusPlus::CliFlag.new(
|
|
53
|
-
'-k',
|
|
54
|
-
'--key-values KEY_VALUES',
|
|
55
|
-
'A comma-separated list of key=values which will be made available to the template',
|
|
56
|
-
lambda do |options, v|
|
|
57
|
-
options.key_values =
|
|
58
|
-
begin
|
|
59
|
-
[v.split('=')].to_h
|
|
60
|
-
rescue ::StandardError
|
|
61
|
-
{}
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
),
|
|
65
|
-
::CSVPlusPlus::CliFlag.new(
|
|
66
|
-
'-n SHEET_NAME',
|
|
67
|
-
'--sheet-name SHEET_NAME',
|
|
68
|
-
'The name of the sheet to apply the template to',
|
|
69
|
-
->(options, v) { options.sheet_name = v }
|
|
70
|
-
),
|
|
71
|
-
::CSVPlusPlus::CliFlag.new(
|
|
72
|
-
'-o OUTPUT_FILE',
|
|
73
|
-
'--output OUTPUT_FILE',
|
|
74
|
-
'The file to write to (must be .csv, .ods, .xls)',
|
|
75
|
-
->(options, v) { options.output_filename = v }
|
|
76
|
-
),
|
|
77
|
-
::CSVPlusPlus::CliFlag.new('-v', '--verbose', 'Enable verbose output', ->(options, _v) { options.verbose = true }),
|
|
78
|
-
::CSVPlusPlus::CliFlag.new(
|
|
79
|
-
'-x OFFSET',
|
|
80
|
-
'--offset-columns OFFSET',
|
|
81
|
-
'Apply the template offset by OFFSET cells',
|
|
82
|
-
->(options, v) { options.offset[0] = v }
|
|
83
|
-
),
|
|
84
|
-
::CSVPlusPlus::CliFlag.new(
|
|
85
|
-
'-y OFFSET',
|
|
86
|
-
'--offset-rows OFFSET',
|
|
87
|
-
'Apply the template offset by OFFSET rows',
|
|
88
|
-
->(options, v) { options.offset[1] = v }
|
|
34
|
+
FLAG_HANDLERS = ::T.let(
|
|
35
|
+
{
|
|
36
|
+
backup: ->(options, _v) { options.backup = true },
|
|
37
|
+
create: ->(options, _v) { options.create_if_not_exists = true },
|
|
38
|
+
'key-values': lambda { |options, v|
|
|
39
|
+
options.key_values =
|
|
40
|
+
begin
|
|
41
|
+
[v.split('=')].to_h
|
|
42
|
+
rescue ::StandardError
|
|
43
|
+
{}
|
|
44
|
+
end
|
|
45
|
+
},
|
|
46
|
+
'offset-columns': ->(options, v) { options.offset[0] = v },
|
|
47
|
+
'offset-rows': ->(options, v) { options.offset[1] = v },
|
|
48
|
+
output: ->(options, v) { options.output_filename = ::Pathname.new(v) },
|
|
49
|
+
safe: ->(options, _v) { options.overwrite_values = false },
|
|
50
|
+
verbose: ->(options, _v) { options.verbose = true }
|
|
51
|
+
},
|
|
52
|
+
::T::Hash[::Symbol, ::T.proc.params(options: ::CSVPlusPlus::Options::Options, v: ::String).void]
|
|
89
53
|
)
|
|
90
|
-
|
|
54
|
+
public_constant :FLAG_HANDLERS
|
|
55
|
+
|
|
56
|
+
SUPPORTED_CSVPP_FLAGS = ::T.let(
|
|
57
|
+
[
|
|
58
|
+
::CSVPlusPlus::CLIFlag.new('-b', '--backup', 'Create a backup of the spreadsheet before applying changes.'),
|
|
59
|
+
::CSVPlusPlus::CLIFlag.new(
|
|
60
|
+
'-c',
|
|
61
|
+
'--create',
|
|
62
|
+
"Create the sheet if it doesn't exist. It will use --sheet-name if specified"
|
|
63
|
+
),
|
|
64
|
+
::CSVPlusPlus::CLIFlag.new(
|
|
65
|
+
'-g SHEET_ID',
|
|
66
|
+
'--google-sheet-id SHEET_ID',
|
|
67
|
+
'The id of the sheet - you can extract this from the URL: ' \
|
|
68
|
+
'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0'
|
|
69
|
+
),
|
|
70
|
+
::CSVPlusPlus::CLIFlag.new(
|
|
71
|
+
'-k',
|
|
72
|
+
'--key-values KEY_VALUES',
|
|
73
|
+
'A comma-separated list of key=values which will be made available to the template'
|
|
74
|
+
),
|
|
75
|
+
::CSVPlusPlus::CLIFlag.new(
|
|
76
|
+
'-n SHEET_NAME',
|
|
77
|
+
'--sheet-name SHEET_NAME',
|
|
78
|
+
'The name of the sheet to apply the template to'
|
|
79
|
+
),
|
|
80
|
+
::CSVPlusPlus::CLIFlag.new(
|
|
81
|
+
'-o OUTPUT_FILE',
|
|
82
|
+
'--output OUTPUT_FILE',
|
|
83
|
+
'The file to write to (must be .csv, .ods, .xls)'
|
|
84
|
+
),
|
|
85
|
+
::CSVPlusPlus::CLIFlag.new(
|
|
86
|
+
'-s',
|
|
87
|
+
'--safe',
|
|
88
|
+
'Do not overwrite values in the spreadsheet being written to. The default is to overwrite'
|
|
89
|
+
),
|
|
90
|
+
::CSVPlusPlus::CLIFlag.new('-v', '--verbose', 'Enable verbose output'),
|
|
91
|
+
::CSVPlusPlus::CLIFlag.new('-x OFFSET', '--offset-columns OFFSET', 'Apply the template offset by OFFSET cells'),
|
|
92
|
+
::CSVPlusPlus::CLIFlag.new('-y OFFSET', '--offset-rows OFFSET', 'Apply the template offset by OFFSET rows')
|
|
93
|
+
].freeze,
|
|
94
|
+
::T::Array[::CSVPlusPlus::CLIFlag]
|
|
95
|
+
)
|
|
96
|
+
public_constant :SUPPORTED_CSVPP_FLAGS
|
|
97
|
+
end
|
data/lib/csv_plus_plus/color.rb
CHANGED
|
@@ -42,7 +42,7 @@ module CSVPlusPlus
|
|
|
42
42
|
red_hex, green_hex, blue_hex = hex_string.strip.match(::CSVPlusPlus::Color::HEX_STRING_REGEXP)
|
|
43
43
|
&.captures
|
|
44
44
|
&.map { |s| s.length == 1 ? s + s : s }
|
|
45
|
-
raise(::CSVPlusPlus::Error::
|
|
45
|
+
raise(::CSVPlusPlus::Error::CompilerError, "Invalid color: #{hex_string}") unless red_hex && green_hex && blue_hex
|
|
46
46
|
|
|
47
47
|
@red_hex = ::T.let(red_hex, ::String)
|
|
48
48
|
@green_hex = ::T.let(green_hex, ::String)
|
|
@@ -11,7 +11,7 @@ module CSVPlusPlus
|
|
|
11
11
|
class Compiler
|
|
12
12
|
extend ::T::Sig
|
|
13
13
|
|
|
14
|
-
sig { returns(::CSVPlusPlus::Options) }
|
|
14
|
+
sig { returns(::CSVPlusPlus::Options::Options) }
|
|
15
15
|
attr_reader :options
|
|
16
16
|
|
|
17
17
|
sig { returns(::CSVPlusPlus::Runtime::Runtime) }
|
|
@@ -19,7 +19,7 @@ module CSVPlusPlus
|
|
|
19
19
|
|
|
20
20
|
sig do
|
|
21
21
|
params(
|
|
22
|
-
options: ::CSVPlusPlus::Options,
|
|
22
|
+
options: ::CSVPlusPlus::Options::Options,
|
|
23
23
|
runtime: ::CSVPlusPlus::Runtime::Runtime,
|
|
24
24
|
block: ::T.proc.params(arg0: ::CSVPlusPlus::Compiler).void
|
|
25
25
|
).void
|
|
@@ -37,10 +37,10 @@ module CSVPlusPlus
|
|
|
37
37
|
block.call(new(options:, runtime:))
|
|
38
38
|
end
|
|
39
39
|
ensure
|
|
40
|
-
runtime.cleanup!
|
|
40
|
+
runtime.position.cleanup!
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
|
43
|
+
sig { params(options: ::CSVPlusPlus::Options::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
|
44
44
|
# @param options [Options]
|
|
45
45
|
# @param runtime [Runtime]
|
|
46
46
|
def initialize(options:, runtime:)
|
|
@@ -49,7 +49,7 @@ module CSVPlusPlus
|
|
|
49
49
|
|
|
50
50
|
# TODO: infer a type
|
|
51
51
|
# allow user-supplied key/values to override anything global or from the code section
|
|
52
|
-
@runtime.def_variables(
|
|
52
|
+
@runtime.scope.def_variables(
|
|
53
53
|
options.key_values.transform_values { |v| ::CSVPlusPlus::Entities::String.new(v.to_s) }
|
|
54
54
|
)
|
|
55
55
|
end
|
|
@@ -72,17 +72,17 @@ module CSVPlusPlus
|
|
|
72
72
|
rows = parse_csv_section!
|
|
73
73
|
|
|
74
74
|
::CSVPlusPlus::Template.new(rows:, runtime: @runtime).tap do |t|
|
|
75
|
-
t.validate_infinite_expands
|
|
75
|
+
t.validate_infinite_expands
|
|
76
76
|
expanding! { t.expand_rows! }
|
|
77
77
|
bind_all_vars! { t.bind_all_vars!(@runtime) }
|
|
78
78
|
resolve_all_cells!(t)
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
sig { params(block: ::T.proc.params(
|
|
82
|
+
sig { params(block: ::T.proc.params(position: ::CSVPlusPlus::Runtime::Position).void).void }
|
|
83
83
|
# Write the compiled results
|
|
84
84
|
def outputting!(&block)
|
|
85
|
-
@runtime.start_at_csv! { block.call(@runtime) }
|
|
85
|
+
@runtime.start_at_csv! { block.call(@runtime.position) }
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
protected
|
|
@@ -94,7 +94,7 @@ module CSVPlusPlus
|
|
|
94
94
|
# TODO: this flow can probably be refactored, it used to have more needs back when we had to
|
|
95
95
|
# parse and save the code_section
|
|
96
96
|
parsing_code_section do |input|
|
|
97
|
-
csv_section = ::CSVPlusPlus::Parser::CodeSection.new.parse(input
|
|
97
|
+
csv_section = ::CSVPlusPlus::Parser::CodeSection.new(@runtime.scope).parse(input)
|
|
98
98
|
|
|
99
99
|
# return the csv_section to the caller because they're gonna re-write input with it
|
|
100
100
|
next csv_section
|
|
@@ -108,16 +108,19 @@ module CSVPlusPlus
|
|
|
108
108
|
# @return [Array<Row>]
|
|
109
109
|
def parse_csv_section!
|
|
110
110
|
@runtime.start_at_csv! do
|
|
111
|
-
@runtime.map_lines(::CSV.new(::T.unsafe(@runtime.input))) do |csv_row|
|
|
111
|
+
@runtime.position.map_lines(::CSV.new(::T.unsafe(@runtime.position.input))) do |csv_row|
|
|
112
112
|
parse_row(::T.cast(csv_row, ::T::Array[::String]))
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
ensure
|
|
116
116
|
# we're done with the file and everything is in memory
|
|
117
|
-
@runtime.cleanup!
|
|
117
|
+
@runtime.position.cleanup!
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
sig
|
|
120
|
+
sig do
|
|
121
|
+
params(template: ::CSVPlusPlus::Template)
|
|
122
|
+
.returns(::T::Array[::T::Array[::T.nilable(::CSVPlusPlus::Entities::Entity)]])
|
|
123
|
+
end
|
|
121
124
|
# Iterates through each cell of each row and resolves it's variable and function references.
|
|
122
125
|
#
|
|
123
126
|
# @param template [Template]
|
|
@@ -125,8 +128,8 @@ module CSVPlusPlus
|
|
|
125
128
|
# @return [Array<Entity>]
|
|
126
129
|
def resolve_all_cells!(template)
|
|
127
130
|
@runtime.start_at_csv! do
|
|
128
|
-
@runtime.map_all_cells(template.rows) do |cell|
|
|
129
|
-
cell.ast = @runtime.resolve_cell_value if cell.ast
|
|
131
|
+
@runtime.position.map_all_cells(template.rows) do |cell|
|
|
132
|
+
cell.ast = @runtime.resolve_cell_value(::T.must(cell.ast)) if cell.ast
|
|
130
133
|
end
|
|
131
134
|
end
|
|
132
135
|
end
|
|
@@ -147,8 +150,8 @@ module CSVPlusPlus
|
|
|
147
150
|
|
|
148
151
|
sig { params(block: ::T.proc.params(arg0: ::String).returns(::String)).void }
|
|
149
152
|
def parsing_code_section(&block)
|
|
150
|
-
csv_section = block.call(::T.must(::T.must(@runtime.input).read))
|
|
151
|
-
@runtime.rewrite_input!(csv_section)
|
|
153
|
+
csv_section = block.call(::T.must(::T.must(@runtime.position.input).read))
|
|
154
|
+
@runtime.position.rewrite_input!(csv_section)
|
|
152
155
|
end
|
|
153
156
|
|
|
154
157
|
sig { params(csv_row: ::T::Array[::T.nilable(::String)]).returns(::CSVPlusPlus::Row) }
|
|
@@ -161,17 +164,24 @@ module CSVPlusPlus
|
|
|
161
164
|
def parse_row(csv_row)
|
|
162
165
|
row_modifier = ::CSVPlusPlus::Modifier.new(@options, row_level: true)
|
|
163
166
|
|
|
164
|
-
cells = @runtime.map_row(csv_row) { |value, _cell_index| parse_cell(value || '', row_modifier) }
|
|
167
|
+
cells = @runtime.position.map_row(csv_row) { |value, _cell_index| parse_cell(value || '', row_modifier) }
|
|
165
168
|
|
|
166
|
-
::CSVPlusPlus::Row.new(cells:, index: @runtime.row_index, modifier: row_modifier)
|
|
169
|
+
::CSVPlusPlus::Row.new(cells:, index: @runtime.position.row_index, modifier: row_modifier)
|
|
167
170
|
end
|
|
168
171
|
|
|
169
172
|
sig { params(value: ::String, row_modifier: ::CSVPlusPlus::Modifier::Modifier).returns(::CSVPlusPlus::Cell) }
|
|
170
173
|
def parse_cell(value, row_modifier)
|
|
171
174
|
cell_modifier = ::CSVPlusPlus::Modifier.new(@options)
|
|
172
|
-
parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:).parse(value
|
|
173
|
-
|
|
174
|
-
::CSVPlusPlus::Cell.
|
|
175
|
+
parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:).parse(value)
|
|
176
|
+
|
|
177
|
+
::CSVPlusPlus::Cell.new(
|
|
178
|
+
value: parsed_value,
|
|
179
|
+
row_index: @runtime.position.row_index,
|
|
180
|
+
index: @runtime.position.cell_index,
|
|
181
|
+
modifier: cell_modifier
|
|
182
|
+
).tap do |c|
|
|
183
|
+
c.ast = ::CSVPlusPlus::Parser::CellValue.new.parse(parsed_value) unless parsed_value.nil?
|
|
184
|
+
end
|
|
175
185
|
end
|
|
176
186
|
end
|
|
177
187
|
# rubocop:enable Metrics/ClassLength
|
|
@@ -25,8 +25,7 @@ module CSVPlusPlus
|
|
|
25
25
|
# @return [Entity, #super]
|
|
26
26
|
# rubocop:disable Naming/BlockForwarding
|
|
27
27
|
def method_missing(method_name, *args, **kwargs, &block)
|
|
28
|
-
|
|
29
|
-
::CSVPlusPlus::Entities.const_get(entity_class_name).new(*args, **kwargs, &block)
|
|
28
|
+
::CSVPlusPlus::Entities.const_get(snake_case_to_class_name(method_name)).new(*args, **kwargs, &block)
|
|
30
29
|
rescue ::NameError
|
|
31
30
|
super
|
|
32
31
|
end
|
|
@@ -41,10 +40,18 @@ module CSVPlusPlus
|
|
|
41
40
|
#
|
|
42
41
|
# @return [::T::Boolean, #super]
|
|
43
42
|
def respond_to_missing?(method_name, *_args)
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
::CSVPlusPlus::Entities.const_get(snake_case_to_class_name(method_name))
|
|
44
|
+
true
|
|
45
|
+
rescue ::NameError
|
|
46
46
|
super
|
|
47
47
|
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
sig { params(method_name: ::Symbol).returns(::Symbol) }
|
|
52
|
+
def snake_case_to_class_name(method_name)
|
|
53
|
+
method_name.to_s.split('_').map(&:capitalize).join.to_sym
|
|
54
|
+
end
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
end
|
|
@@ -6,34 +6,41 @@ module CSVPlusPlus
|
|
|
6
6
|
# A boolean value
|
|
7
7
|
#
|
|
8
8
|
# @attr_reader value [true, false]
|
|
9
|
-
class Boolean < Entity
|
|
9
|
+
class Boolean < ::CSVPlusPlus::Entities::Entity
|
|
10
|
+
extend ::T::Sig
|
|
11
|
+
|
|
10
12
|
sig { returns(::T::Boolean) }
|
|
11
13
|
attr_reader :value
|
|
12
14
|
|
|
13
15
|
sig { params(value: ::T.any(::String, ::T::Boolean)).void }
|
|
14
16
|
# @param value [::String, boolean]
|
|
15
17
|
def initialize(value)
|
|
16
|
-
super(
|
|
18
|
+
super()
|
|
17
19
|
# TODO: probably can do a lot better in general on type validation
|
|
18
20
|
@value = ::T.let(value.is_a?(::String) ? (value.downcase == 'true') : value, ::T::Boolean)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
sig
|
|
22
|
-
|
|
23
|
+
sig do
|
|
24
|
+
override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String)
|
|
25
|
+
end
|
|
26
|
+
# @param _position [Position]
|
|
23
27
|
#
|
|
24
28
|
# @return [::String]
|
|
25
|
-
def evaluate(
|
|
29
|
+
def evaluate(_position)
|
|
26
30
|
@value.to_s.upcase
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
sig { override.params(other: ::
|
|
33
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
30
34
|
# @param other [Entity]
|
|
31
35
|
#
|
|
32
36
|
# @return [::T::Boolean]
|
|
33
37
|
def ==(other)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
case other
|
|
39
|
+
when self.class
|
|
40
|
+
value == other.value
|
|
41
|
+
else
|
|
42
|
+
false
|
|
43
|
+
end
|
|
37
44
|
end
|
|
38
45
|
end
|
|
39
46
|
end
|
|
@@ -1,58 +1,86 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module CSVPlusPlus
|
|
5
5
|
module Entities
|
|
6
|
-
# Provides
|
|
6
|
+
# Provides +RuntimeValue+s for builtin functions and variables
|
|
7
7
|
module Builtins
|
|
8
|
+
extend ::T::Sig
|
|
9
|
+
|
|
8
10
|
extend ::CSVPlusPlus::Entities::ASTBuilder
|
|
9
11
|
|
|
10
|
-
VARIABLES =
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
VARIABLES = ::T.let(
|
|
13
|
+
{
|
|
14
|
+
# The number (integer) of the current cell. Starts at 1
|
|
15
|
+
cellnum: runtime_value(->(p, _args) { number(p.cell_index + 1) }),
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
# A reference to the current cell
|
|
18
|
+
cellref: runtime_value(->(p, _args) { cell_ref(p.row_index, p.cell_index) }),
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
# A reference to the row above
|
|
21
|
+
rowabove: runtime_value(->(p, _args) { cell_ref([0, (p.row_index - 1)].max) }),
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
# A reference to the row below
|
|
24
|
+
rowbelow: runtime_value(->(p, _args) { cell_ref(p.row_index + 1) }),
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
# The number (integer) of the current row. Starts at 1
|
|
27
|
+
rownum: runtime_value(->(p, _args) { number(p.rownum) }),
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
# A reference to the current row
|
|
30
|
+
rowref: runtime_value(->(p, _args) { cell_ref(p.row_index) })
|
|
31
|
+
}.freeze,
|
|
32
|
+
::T::Hash[::Symbol, ::CSVPlusPlus::Entities::RuntimeValue]
|
|
33
|
+
)
|
|
29
34
|
public_constant :VARIABLES
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
),
|
|
47
|
-
|
|
48
|
-
# A reference to a cell below the current row
|
|
49
|
-
cellbelow: runtime_value(
|
|
50
|
-
lambda { |r, args|
|
|
51
|
-
cell_reference(cell_index: args[0].cell_index, row_index: r.row_index + 1)
|
|
52
|
-
}
|
|
53
|
-
)
|
|
54
|
-
}.freeze
|
|
36
|
+
# TODO: A reference to a cell in a given row?
|
|
37
|
+
# TODO: check types of the args and throw a more friendly error?
|
|
38
|
+
FUNCTIONS = ::T.let(
|
|
39
|
+
{
|
|
40
|
+
# A reference to a cell above the current row
|
|
41
|
+
cellabove: runtime_value(->(p, args) { cell_ref([0, (p.row_index - 1)].max, args[0].a1_ref.cell_index) }),
|
|
42
|
+
|
|
43
|
+
# A reference to a cell in the current row
|
|
44
|
+
celladjacent: runtime_value(->(p, args) { cell_ref(p.row_index, args[0].a1_ref.cell_index) }),
|
|
45
|
+
|
|
46
|
+
# A reference to a cell below the current row
|
|
47
|
+
cellbelow: runtime_value(->(p, args) { cell_ref(p.row_index + 1, args[0].a1_ref.cell_index) })
|
|
48
|
+
}.freeze,
|
|
49
|
+
::T::Hash[::Symbol, ::CSVPlusPlus::Entities::RuntimeValue]
|
|
50
|
+
)
|
|
55
51
|
public_constant :FUNCTIONS
|
|
52
|
+
|
|
53
|
+
sig { params(fn_id: ::Symbol).returns(::T::Boolean) }
|
|
54
|
+
# Is +fn_id+ a builtin function?
|
|
55
|
+
#
|
|
56
|
+
# @param fn_id [Symbol] The Function#id to check if it's a runtime variable
|
|
57
|
+
#
|
|
58
|
+
# @return [T::Boolean]
|
|
59
|
+
def self.builtin_function?(fn_id)
|
|
60
|
+
::CSVPlusPlus::Entities::Builtins::FUNCTIONS.key?(fn_id)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
sig { params(var_id: ::Symbol).returns(::T::Boolean) }
|
|
64
|
+
# Is +var_id+ a builtin variable?
|
|
65
|
+
#
|
|
66
|
+
# @param var_id [Symbol] The Variable#id to check if it's a runtime variable
|
|
67
|
+
#
|
|
68
|
+
# @return [Boolean]
|
|
69
|
+
def self.builtin_variable?(var_id)
|
|
70
|
+
::CSVPlusPlus::Entities::Builtins::VARIABLES.key?(var_id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sig do
|
|
74
|
+
params(row_index: ::Integer, cell_index: ::T.nilable(::Integer)).returns(::CSVPlusPlus::Entities::Reference)
|
|
75
|
+
end
|
|
76
|
+
# @param row_index [Integer]
|
|
77
|
+
# @param cell_index [Integer, nil]
|
|
78
|
+
#
|
|
79
|
+
# @return [Runtime::Reference]
|
|
80
|
+
def self.cell_ref(row_index, cell_index = nil)
|
|
81
|
+
::CSVPlusPlus::Entities::Reference.new(a1_ref: ::CSVPlusPlus::A1Reference.new(row_index:, cell_index:))
|
|
82
|
+
end
|
|
83
|
+
private_class_method :cell_ref
|
|
56
84
|
end
|
|
57
85
|
end
|
|
58
86
|
end
|
|
@@ -6,7 +6,7 @@ module CSVPlusPlus
|
|
|
6
6
|
# A date value
|
|
7
7
|
#
|
|
8
8
|
# @attr_reader value [Date] The parsed date
|
|
9
|
-
class Date < Entity
|
|
9
|
+
class Date < ::CSVPlusPlus::Entities::Entity
|
|
10
10
|
extend ::T::Sig
|
|
11
11
|
|
|
12
12
|
sig { returns(::Date) }
|
|
@@ -30,7 +30,7 @@ module CSVPlusPlus
|
|
|
30
30
|
sig { params(value: ::String).void }
|
|
31
31
|
# @param value [::String] The user-inputted date value
|
|
32
32
|
def initialize(value)
|
|
33
|
-
super(
|
|
33
|
+
super()
|
|
34
34
|
|
|
35
35
|
parsed =
|
|
36
36
|
begin
|
|
@@ -41,22 +41,25 @@ module CSVPlusPlus
|
|
|
41
41
|
@value = ::T.let(parsed, ::Date)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
sig { override.params(
|
|
45
|
-
# @param
|
|
44
|
+
sig { override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
|
|
45
|
+
# @param _position [Position]
|
|
46
46
|
#
|
|
47
47
|
# @return [::String]
|
|
48
|
-
def evaluate(
|
|
48
|
+
def evaluate(_position)
|
|
49
49
|
@value.strftime('%m/%d/%y')
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
sig { override.params(other: ::
|
|
53
|
-
# @param other [
|
|
52
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
53
|
+
# @param other [BasicObject]
|
|
54
54
|
#
|
|
55
|
-
# @return [
|
|
55
|
+
# @return [Boolean]
|
|
56
56
|
def ==(other)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
case other
|
|
58
|
+
when self.class
|
|
59
|
+
other.value == @value
|
|
60
|
+
else
|
|
61
|
+
false
|
|
62
|
+
end
|
|
60
63
|
end
|
|
61
64
|
end
|
|
62
65
|
end
|
|
@@ -3,48 +3,30 @@
|
|
|
3
3
|
|
|
4
4
|
module CSVPlusPlus
|
|
5
5
|
module Entities
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# @attr_reader id [Symbol] The identifier of the entity. For functions this is the function name,
|
|
9
|
-
# for variables it's the variable name
|
|
10
|
-
# @attr_reader type [Entities::Type] The type of the entity. Each type should have a corresponding class definition
|
|
11
|
-
# in CSVPlusPlus::Entities
|
|
6
|
+
# All classes that are a part of an AST must implement this interface
|
|
12
7
|
class Entity
|
|
13
8
|
extend ::T::Sig
|
|
14
9
|
extend ::T::Helpers
|
|
15
10
|
|
|
16
11
|
abstract!
|
|
17
12
|
|
|
18
|
-
sig {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sig { returns(::CSVPlusPlus::Entities::Type) }
|
|
22
|
-
attr_reader :type
|
|
23
|
-
|
|
24
|
-
sig { params(type: ::CSVPlusPlus::Entities::Type, id: ::T.nilable(::Symbol)).void }
|
|
25
|
-
# @param type [Entities::Type]
|
|
26
|
-
# @param id [Symbol, nil]
|
|
27
|
-
def initialize(type, id: nil)
|
|
28
|
-
@type = type
|
|
29
|
-
@id = ::T.let(id&.downcase&.to_sym || nil, ::T.nilable(::Symbol))
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
sig { overridable.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
|
33
|
-
# Each class should define it's own version of #==
|
|
13
|
+
sig { abstract.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
14
|
+
# Each node in the AST needs to implement #== so we can compare entities for equality
|
|
15
|
+
#
|
|
34
16
|
# @param other [Entity]
|
|
35
17
|
#
|
|
36
18
|
# @return [boolean]
|
|
37
|
-
def ==(other)
|
|
38
|
-
self.class == other.class && @type == other.type && @id == other.id
|
|
39
|
-
end
|
|
19
|
+
def ==(other); end
|
|
40
20
|
|
|
41
|
-
sig
|
|
42
|
-
|
|
21
|
+
sig do
|
|
22
|
+
abstract.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String)
|
|
23
|
+
end
|
|
24
|
+
# Uses the given +position+ to evaluate itself in the current context
|
|
43
25
|
#
|
|
44
|
-
# @param
|
|
26
|
+
# @param position [Position] The current runtime
|
|
45
27
|
#
|
|
46
28
|
# @return [::String]
|
|
47
|
-
def evaluate(
|
|
29
|
+
def evaluate(position); end
|
|
48
30
|
end
|
|
49
31
|
end
|
|
50
32
|
end
|