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
data/lib/csv_plus_plus/writer.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative './writer/base_writer'
|
@@ -7,18 +8,47 @@ require_relative './writer/google_sheets'
|
|
7
8
|
require_relative './writer/open_document'
|
8
9
|
|
9
10
|
module CSVPlusPlus
|
10
|
-
# Various strategies for writing to various formats (excel, google sheets, CSV
|
11
|
+
# Various strategies for writing to various formats (excel, google sheets, CSV & OpenDocument (not yet implemented))
|
11
12
|
module Writer
|
12
|
-
|
13
|
-
def self.writer(options)
|
14
|
-
return ::CSVPlusPlus::Writer::GoogleSheets.new(options) if options.google
|
13
|
+
extend ::T::Sig
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
sig do
|
16
|
+
params(
|
17
|
+
options: ::CSVPlusPlus::Options,
|
18
|
+
runtime: ::CSVPlusPlus::Runtime::Runtime
|
19
|
+
).returns(
|
20
|
+
::T.any(
|
21
|
+
::CSVPlusPlus::Writer::CSV,
|
22
|
+
::CSVPlusPlus::Writer::Excel,
|
23
|
+
::CSVPlusPlus::Writer::GoogleSheets,
|
24
|
+
::CSVPlusPlus::Writer::OpenDocument
|
25
|
+
)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
# Return an instance of a writer depending on the given +options+
|
29
|
+
#
|
30
|
+
# @param options [Options] The supplied options.
|
31
|
+
# @param runtime [Runtime] The current runtime.
|
32
|
+
#
|
33
|
+
# @return [Writer::CSV | Writer::Excel | Writer::GoogleSheets | Writer::OpenDocument]
|
34
|
+
# rubocop:disable Metrics/MethodLength
|
35
|
+
def self.writer(options, runtime)
|
36
|
+
output_format = options.output_format
|
37
|
+
case output_format
|
38
|
+
when ::CSVPlusPlus::Options::OutputFormat::CSV then ::CSVPlusPlus::Writer::CSV.new(options, runtime)
|
39
|
+
when ::CSVPlusPlus::Options::OutputFormat::Excel then ::CSVPlusPlus::Writer::Excel.new(options, runtime)
|
40
|
+
when ::CSVPlusPlus::Options::OutputFormat::GoogleSheets then ::CSVPlusPlus::Writer::GoogleSheets.new(
|
41
|
+
options,
|
42
|
+
runtime
|
43
|
+
)
|
44
|
+
when ::CSVPlusPlus::Options::OutputFormat::OpenDocument then ::CSVPlusPlus::Writer::OpenDocument.new(
|
45
|
+
options,
|
46
|
+
runtime
|
47
|
+
)
|
48
|
+
else
|
49
|
+
::T.absurd(output_format)
|
21
50
|
end
|
22
51
|
end
|
52
|
+
# rubocop:enable Metrics/MethodLength
|
23
53
|
end
|
24
54
|
end
|
data/lib/csv_plus_plus.rb
CHANGED
@@ -1,39 +1,54 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
4
|
+
require 'sorbet-runtime'
|
5
|
+
|
3
6
|
require 'benchmark'
|
4
7
|
require 'csv'
|
5
8
|
require 'fileutils'
|
6
9
|
require 'google/apis/drive_v3'
|
7
10
|
require 'google/apis/sheets_v4'
|
8
11
|
require 'googleauth'
|
12
|
+
require 'optparse'
|
9
13
|
require 'pathname'
|
10
14
|
require 'rubyXL'
|
11
15
|
require 'rubyXL/convenience_methods'
|
12
16
|
require 'set'
|
13
17
|
require 'tempfile'
|
14
18
|
|
19
|
+
require_relative 'csv_plus_plus/source_code'
|
20
|
+
|
21
|
+
require_relative 'csv_plus_plus/runtime'
|
22
|
+
|
23
|
+
require_relative 'csv_plus_plus/cli_flag'
|
15
24
|
require_relative 'csv_plus_plus/entities'
|
16
25
|
require_relative 'csv_plus_plus/error'
|
17
26
|
|
18
27
|
require_relative 'csv_plus_plus/cell'
|
19
28
|
require_relative 'csv_plus_plus/cli'
|
20
29
|
require_relative 'csv_plus_plus/color'
|
30
|
+
require_relative 'csv_plus_plus/modifier'
|
31
|
+
|
32
|
+
require_relative 'csv_plus_plus/parser/cell_value.tab'
|
33
|
+
require_relative 'csv_plus_plus/parser/code_section.tab'
|
34
|
+
require_relative 'csv_plus_plus/parser/modifier.tab'
|
21
35
|
|
22
36
|
require_relative 'csv_plus_plus/compiler'
|
23
|
-
require_relative 'csv_plus_plus/runtime'
|
24
37
|
|
38
|
+
require_relative 'csv_plus_plus/google_options'
|
25
39
|
require_relative 'csv_plus_plus/lexer'
|
26
|
-
require_relative 'csv_plus_plus/lexer/tokenizer'
|
27
|
-
require_relative 'csv_plus_plus/modifier'
|
28
40
|
require_relative 'csv_plus_plus/options'
|
29
|
-
require_relative 'csv_plus_plus/parser/modifier.tab'
|
30
41
|
require_relative 'csv_plus_plus/row'
|
31
42
|
require_relative 'csv_plus_plus/template'
|
32
|
-
require_relative 'csv_plus_plus/validated_modifier'
|
33
43
|
require_relative 'csv_plus_plus/writer'
|
34
44
|
|
45
|
+
require_relative 'csv_plus_plus/benchmarked_compiler'
|
46
|
+
|
35
47
|
# A programming language for writing rich CSV files
|
36
48
|
module CSVPlusPlus
|
49
|
+
extend ::T::Sig
|
50
|
+
|
51
|
+
sig { params(input: ::String, filename: ::T.nilable(::String), options: ::CSVPlusPlus::Options).void }
|
37
52
|
# Parse the input into a +Template+ and write it to the desired format
|
38
53
|
#
|
39
54
|
# @param input [String] The csvpp input to compile
|
@@ -42,25 +57,27 @@ module CSVPlusPlus
|
|
42
57
|
def self.apply_template_to_sheet!(input, filename, options)
|
43
58
|
warn(options.verbose_summary) if options.verbose
|
44
59
|
|
45
|
-
runtime = ::CSVPlusPlus::Runtime.new(input:, filename:)
|
60
|
+
runtime = ::CSVPlusPlus::Runtime.new(source_code: ::CSVPlusPlus::SourceCode.new(input:, filename:))
|
46
61
|
|
47
62
|
::CSVPlusPlus::Compiler.with_compiler(options:, runtime:) do |compiler|
|
48
63
|
template = compiler.compile_template
|
49
|
-
|
50
64
|
warn(template.verbose_summary) if options.verbose
|
51
65
|
|
52
|
-
write_template(template
|
66
|
+
write_template(template:, compiler:, options:)
|
53
67
|
end
|
54
68
|
end
|
55
69
|
|
70
|
+
sig do
|
71
|
+
params(compiler: ::CSVPlusPlus::Compiler, options: ::CSVPlusPlus::Options, template: ::CSVPlusPlus::Template).void
|
72
|
+
end
|
56
73
|
# Write the results (and possibly make a backup) of a compiled +template+
|
57
74
|
#
|
58
|
-
# @param template [Template] The compiled template
|
59
75
|
# @param compiler [Compiler] The compiler currently in use
|
60
76
|
# @param options [Options] The options we're running with
|
61
|
-
|
62
|
-
|
63
|
-
|
77
|
+
# @param template [Template] The compiled template
|
78
|
+
def self.write_template(compiler:, options:, template:)
|
79
|
+
compiler.outputting! do |runtime|
|
80
|
+
output = ::CSVPlusPlus::Writer.writer(options, runtime)
|
64
81
|
output.write_backup if options.backup
|
65
82
|
output.write(template)
|
66
83
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_plus_plus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Carroll
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-apis-drive_v3
|
@@ -118,20 +118,17 @@ executables:
|
|
118
118
|
extensions: []
|
119
119
|
extra_rdoc_files: []
|
120
120
|
files:
|
121
|
-
- CHANGELOG.md
|
122
121
|
- README.md
|
123
122
|
- bin/csv++
|
124
123
|
- bin/csvpp
|
124
|
+
- docs/CHANGELOG.md
|
125
125
|
- lib/csv_plus_plus.rb
|
126
126
|
- lib/csv_plus_plus/benchmarked_compiler.rb
|
127
|
-
- lib/csv_plus_plus/can_define_references.rb
|
128
|
-
- lib/csv_plus_plus/can_resolve_references.rb
|
129
127
|
- lib/csv_plus_plus/cell.rb
|
130
128
|
- lib/csv_plus_plus/cli.rb
|
131
129
|
- lib/csv_plus_plus/cli_flag.rb
|
132
130
|
- lib/csv_plus_plus/color.rb
|
133
131
|
- lib/csv_plus_plus/compiler.rb
|
134
|
-
- lib/csv_plus_plus/data_validation.rb
|
135
132
|
- lib/csv_plus_plus/entities.rb
|
136
133
|
- lib/csv_plus_plus/entities/ast_builder.rb
|
137
134
|
- lib/csv_plus_plus/entities/boolean.rb
|
@@ -139,6 +136,7 @@ files:
|
|
139
136
|
- lib/csv_plus_plus/entities/cell_reference.rb
|
140
137
|
- lib/csv_plus_plus/entities/date.rb
|
141
138
|
- lib/csv_plus_plus/entities/entity.rb
|
139
|
+
- lib/csv_plus_plus/entities/entity_with_arguments.rb
|
142
140
|
- lib/csv_plus_plus/entities/function.rb
|
143
141
|
- lib/csv_plus_plus/entities/function_call.rb
|
144
142
|
- lib/csv_plus_plus/entities/number.rb
|
@@ -152,25 +150,33 @@ files:
|
|
152
150
|
- lib/csv_plus_plus/error/modifier_validation_error.rb
|
153
151
|
- lib/csv_plus_plus/error/syntax_error.rb
|
154
152
|
- lib/csv_plus_plus/error/writer_error.rb
|
155
|
-
- lib/csv_plus_plus/expand.rb
|
156
153
|
- lib/csv_plus_plus/google_api_client.rb
|
157
154
|
- lib/csv_plus_plus/google_options.rb
|
158
|
-
- lib/csv_plus_plus/graph.rb
|
159
155
|
- lib/csv_plus_plus/lexer.rb
|
160
156
|
- lib/csv_plus_plus/lexer/lexer.rb
|
161
157
|
- lib/csv_plus_plus/lexer/tokenizer.rb
|
162
158
|
- lib/csv_plus_plus/modifier.rb
|
163
159
|
- lib/csv_plus_plus/modifier/conditional_formatting.rb
|
160
|
+
- lib/csv_plus_plus/modifier/data_validation.rb
|
161
|
+
- lib/csv_plus_plus/modifier/expand.rb
|
162
|
+
- lib/csv_plus_plus/modifier/google_sheet_modifier.rb
|
163
|
+
- lib/csv_plus_plus/modifier/modifier.rb
|
164
|
+
- lib/csv_plus_plus/modifier/modifier_validator.rb
|
165
|
+
- lib/csv_plus_plus/modifier/rubyxl_modifier.rb
|
164
166
|
- lib/csv_plus_plus/options.rb
|
165
167
|
- lib/csv_plus_plus/parser/cell_value.tab.rb
|
166
168
|
- lib/csv_plus_plus/parser/code_section.tab.rb
|
167
169
|
- lib/csv_plus_plus/parser/modifier.tab.rb
|
168
|
-
- lib/csv_plus_plus/references.rb
|
169
170
|
- lib/csv_plus_plus/row.rb
|
170
171
|
- lib/csv_plus_plus/runtime.rb
|
171
|
-
- lib/csv_plus_plus/
|
172
|
+
- lib/csv_plus_plus/runtime/can_define_references.rb
|
173
|
+
- lib/csv_plus_plus/runtime/can_resolve_references.rb
|
174
|
+
- lib/csv_plus_plus/runtime/graph.rb
|
175
|
+
- lib/csv_plus_plus/runtime/position_tracker.rb
|
176
|
+
- lib/csv_plus_plus/runtime/references.rb
|
177
|
+
- lib/csv_plus_plus/runtime/runtime.rb
|
178
|
+
- lib/csv_plus_plus/source_code.rb
|
172
179
|
- lib/csv_plus_plus/template.rb
|
173
|
-
- lib/csv_plus_plus/validated_modifier.rb
|
174
180
|
- lib/csv_plus_plus/version.rb
|
175
181
|
- lib/csv_plus_plus/writer.rb
|
176
182
|
- lib/csv_plus_plus/writer/base_writer.rb
|
@@ -178,11 +184,9 @@ files:
|
|
178
184
|
- lib/csv_plus_plus/writer/excel.rb
|
179
185
|
- lib/csv_plus_plus/writer/file_backer_upper.rb
|
180
186
|
- lib/csv_plus_plus/writer/google_sheet_builder.rb
|
181
|
-
- lib/csv_plus_plus/writer/google_sheet_modifier.rb
|
182
187
|
- lib/csv_plus_plus/writer/google_sheets.rb
|
183
188
|
- lib/csv_plus_plus/writer/open_document.rb
|
184
189
|
- lib/csv_plus_plus/writer/rubyxl_builder.rb
|
185
|
-
- lib/csv_plus_plus/writer/rubyxl_modifier.rb
|
186
190
|
homepage: https://github.com/patrickomatic/csv-plus-plus
|
187
191
|
licenses:
|
188
192
|
- MIT
|
@@ -192,7 +196,7 @@ metadata:
|
|
192
196
|
github_repo: git://github.com/patrickomatic/csv-plus-plus
|
193
197
|
homepage_uri: https://github.com/patrickomatic/csv-plus-plus
|
194
198
|
source_code_uri: https://github.com/patrickomatic/csv-plus-plus
|
195
|
-
changelog_uri: https://github.com/patrickomatic/csv-plus-plus/blob/main/CHANGELOG.md
|
199
|
+
changelog_uri: https://github.com/patrickomatic/csv-plus-plus/blob/main/docs/CHANGELOG.md
|
196
200
|
rubygems_mfa_required: 'true'
|
197
201
|
post_install_message:
|
198
202
|
rdoc_options: []
|
@@ -1,88 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
# Methods for classes that need to manage +@variables+ and +@functions+
|
5
|
-
module CanDefineReferences
|
6
|
-
# Define a (or re-define an existing) variable
|
7
|
-
#
|
8
|
-
# @param id [String, Symbol] The identifier for the variable
|
9
|
-
# @param entity [Entity] The value (entity) the variable holds
|
10
|
-
def def_variable(id, entity)
|
11
|
-
variables[id.to_sym] = entity
|
12
|
-
end
|
13
|
-
|
14
|
-
# Define (or re-define existing) variables
|
15
|
-
#
|
16
|
-
# @param variables [Hash<Symbol, Variable>] Variables to define
|
17
|
-
def def_variables(vars)
|
18
|
-
vars.each { |id, entity| def_variable(id, entity) }
|
19
|
-
end
|
20
|
-
|
21
|
-
# Define a (or re-define an existing) function
|
22
|
-
#
|
23
|
-
# @param id [String, Symbol] The identifier for the function
|
24
|
-
# @param entity [Entities::Function] The defined function
|
25
|
-
def def_function(id, entity)
|
26
|
-
functions[id.to_sym] = entity
|
27
|
-
end
|
28
|
-
|
29
|
-
# Is the variable defined?
|
30
|
-
#
|
31
|
-
# @param var_id [Symbol, String] The identifier of the variable
|
32
|
-
#
|
33
|
-
# @return [boolean]
|
34
|
-
def defined_variable?(var_id)
|
35
|
-
variables.key?(var_id.to_sym)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Is the function defined?
|
39
|
-
#
|
40
|
-
# @param fn_id [Symbol, String] The identifier of the function
|
41
|
-
#
|
42
|
-
# @return [boolean]
|
43
|
-
def defined_function?(fn_id)
|
44
|
-
functions.key?(fn_id.to_sym)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Provide a summary of the functions and variables compiled (to show in verbose mode)
|
48
|
-
#
|
49
|
-
# @return [String]
|
50
|
-
def verbose_summary
|
51
|
-
<<~SUMMARY
|
52
|
-
# Code Section Summary
|
53
|
-
|
54
|
-
## Resolved Variables
|
55
|
-
|
56
|
-
#{variable_summary}
|
57
|
-
|
58
|
-
## Functions
|
59
|
-
|
60
|
-
#{function_summary}
|
61
|
-
SUMMARY
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def variables
|
67
|
-
@variables ||= {}
|
68
|
-
end
|
69
|
-
|
70
|
-
def functions
|
71
|
-
@functions ||= {}
|
72
|
-
end
|
73
|
-
|
74
|
-
def variable_summary
|
75
|
-
return '(no variables defined)' if variables.empty?
|
76
|
-
|
77
|
-
variables.map { |k, v| "#{k} := #{v}" }
|
78
|
-
.join("\n")
|
79
|
-
end
|
80
|
-
|
81
|
-
def function_summary
|
82
|
-
return '(no functions defined)' if functions.empty?
|
83
|
-
|
84
|
-
functions.map { |k, f| "#{k}: #{f}" }
|
85
|
-
.join("\n")
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,138 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
# A validation on a cell value. Used to support the `validate=` modifier directive. This is mostly based on the
|
5
|
-
# Google Sheets API spec which can be seen here:
|
6
|
-
#
|
7
|
-
# {https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionType}
|
8
|
-
#
|
9
|
-
# @attr_reader arguments [Array<::String>] The parsed arguments as required by the condition.
|
10
|
-
# @attr_reader condition [Symbol] The condition (:blank, :text_eq, :date_before, etc.)
|
11
|
-
# @attr_reader invalid_reason [::String, nil] If set, the reason why this modifier is not valid.
|
12
|
-
class DataValidation
|
13
|
-
attr_reader :arguments, :condition, :invalid_reason
|
14
|
-
|
15
|
-
# @param value [::String] The value to parse as a data validation
|
16
|
-
def initialize(value)
|
17
|
-
condition, args = unquote(value).split(/\s*:\s*/)
|
18
|
-
@arguments = unquote(args || '').split(/\s+/)
|
19
|
-
@condition = condition.to_sym
|
20
|
-
|
21
|
-
validate!
|
22
|
-
end
|
23
|
-
|
24
|
-
# Each data validation represented by (+condition+) has their own require
|
25
|
-
# @return [boolean]
|
26
|
-
def valid?
|
27
|
-
@invalid_reason.nil?
|
28
|
-
end
|
29
|
-
|
30
|
-
protected
|
31
|
-
|
32
|
-
def unquote(str)
|
33
|
-
# TODO: I'm pretty sure this isn't sufficient and we need to deal with the backslashes
|
34
|
-
str.gsub(/^['\s]*|['\s]*$/, '')
|
35
|
-
end
|
36
|
-
|
37
|
-
def invalid!(reason)
|
38
|
-
@invalid_reason = reason
|
39
|
-
end
|
40
|
-
|
41
|
-
def a_number(arg)
|
42
|
-
Float(arg)
|
43
|
-
rescue ::ArgumentError
|
44
|
-
invalid!("Requires a number but given: #{arg}")
|
45
|
-
end
|
46
|
-
|
47
|
-
def a1_notation(arg)
|
48
|
-
return arg if ::CSVPlusPlus::Entities::CellReference.valid_cell_reference?(arg)
|
49
|
-
end
|
50
|
-
|
51
|
-
def a_date(arg, allow_relative_date: false)
|
52
|
-
return arg if ::CSVPlusPlus::Entities::Date.valid_date?(arg)
|
53
|
-
|
54
|
-
if allow_relative_date
|
55
|
-
a_relative_date(arg)
|
56
|
-
else
|
57
|
-
invalid!("Requires a date but given: #{arg}")
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def a_relative_date(arg)
|
62
|
-
return arg if %w[past_month past_week past_year yesterday today tomorrow].include?(arg.downcase)
|
63
|
-
|
64
|
-
invalid!('Requires a relative date: past_month, past_week, past_year, yesterday, today or tomorrow')
|
65
|
-
end
|
66
|
-
|
67
|
-
def no_args
|
68
|
-
return if @arguments.empty?
|
69
|
-
|
70
|
-
invalid!("Requires no arguments but #{@arguments.length} given: #{@arguments}")
|
71
|
-
end
|
72
|
-
|
73
|
-
def one_arg
|
74
|
-
return @arguments[0] if @arguments.length == 1
|
75
|
-
|
76
|
-
invalid!("Requires only one argument but #{@arguments.length} given: #{@arguments}")
|
77
|
-
end
|
78
|
-
|
79
|
-
def one_arg_or_more
|
80
|
-
return @arguments if @arguments.length.positive?
|
81
|
-
|
82
|
-
invalid!("Requires at least one argument but #{@arguments.length} given: #{@arguments}")
|
83
|
-
end
|
84
|
-
|
85
|
-
def two_dates
|
86
|
-
return @arguments if @arguments.length == 2 && a_date(@arguments[0]) && a_date(@arguments[1])
|
87
|
-
|
88
|
-
invalid!("Requires exactly two dates but given: #{@arguments}")
|
89
|
-
end
|
90
|
-
|
91
|
-
def two_numbers
|
92
|
-
return @arguments if @arguments.length == 2 && a_number(@arguments[0]) && a_number(@arguments[1])
|
93
|
-
|
94
|
-
invalid!("Requires exactly two numbers but given: #{@arguments}")
|
95
|
-
end
|
96
|
-
|
97
|
-
# validate_boolean is a weird one because it can have 0, 1 or 2 @arguments - all of them must be (true | false)
|
98
|
-
def validate_boolean
|
99
|
-
return @arguments if @arguments.empty?
|
100
|
-
|
101
|
-
converted_args = @arguments.map(&:strip).map(&:downcase)
|
102
|
-
return @arguments if [1, 2].include?(@arguments.length) && converted_args.all? do |arg|
|
103
|
-
%w[true false].include?(arg)
|
104
|
-
end
|
105
|
-
|
106
|
-
invalid!("Requires 0, 1 or 2 arguments and they all must be either 'true' or 'false'. Received: #{arguments}")
|
107
|
-
end
|
108
|
-
|
109
|
-
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
110
|
-
def validate!
|
111
|
-
case condition.to_sym
|
112
|
-
when :blank, :date_is_valid, :not_blank, :text_is_email, :text_is_url
|
113
|
-
no_args
|
114
|
-
when :text_contains, :text_ends_with, :text_eq, :text_not_contains, :text_starts_with
|
115
|
-
one_arg
|
116
|
-
when :date_after, :date_before, :date_on_or_after, :date_on_or_before
|
117
|
-
a_date(one_arg, allow_relative_date: true)
|
118
|
-
when :date_eq, :date_not_eq
|
119
|
-
a_date(one_arg)
|
120
|
-
when :date_between, :date_not_between
|
121
|
-
two_dates
|
122
|
-
when :one_of_range
|
123
|
-
a1_notation(one_arg)
|
124
|
-
when :custom_formula, :one_of_list, :text_not_eq
|
125
|
-
one_arg_or_more
|
126
|
-
when :number_eq, :number_greater, :number_greater_than_eq, :number_less, :number_less_than_eq, :number_not_eq
|
127
|
-
a_number(one_arg)
|
128
|
-
when :number_between, :number_not_between
|
129
|
-
two_numbers
|
130
|
-
when :boolean
|
131
|
-
validate_boolean
|
132
|
-
else
|
133
|
-
invalid!('Not a recognized data validation directive')
|
134
|
-
end
|
135
|
-
end
|
136
|
-
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
137
|
-
end
|
138
|
-
end
|
data/lib/csv_plus_plus/expand.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
Expand =
|
5
|
-
::Struct.new(:repetitions) do
|
6
|
-
# Does this infinitely expand?
|
7
|
-
#
|
8
|
-
# @return [boolean]
|
9
|
-
def infinite?
|
10
|
-
repetitions.nil?
|
11
|
-
end
|
12
|
-
|
13
|
-
# @return [::String]
|
14
|
-
def to_s
|
15
|
-
"Expand #{repetitions || 'infinity'}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
public_constant :Expand
|
20
|
-
end
|
data/lib/csv_plus_plus/graph.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'tsort'
|
4
|
-
|
5
|
-
module CSVPlusPlus
|
6
|
-
# Graph ordering and searching functions
|
7
|
-
module Graph
|
8
|
-
# Get a list of all variables references in a given +ast+
|
9
|
-
# TODO: this is only used in one place - refactor it
|
10
|
-
def self.variable_references(ast, runtime, include_runtime_variables: false)
|
11
|
-
depth_first_search(ast) do |node|
|
12
|
-
next unless node.variable?
|
13
|
-
|
14
|
-
node.id if !runtime.runtime_variable?(node.id) || include_runtime_variables
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# Create a dependency graph of +variables+
|
19
|
-
def self.dependency_graph(variables, runtime)
|
20
|
-
::CSVPlusPlus::Graph::DependencyGraph[
|
21
|
-
variables.map { |var_id, ast| [var_id, variable_references(ast, runtime)] }
|
22
|
-
]
|
23
|
-
end
|
24
|
-
|
25
|
-
# Perform a topological sort on a +DependencyGraph+. A toplogical sort is noteworthy
|
26
|
-
# because it will give us the order in which we need to resolve our variable dependencies.
|
27
|
-
#
|
28
|
-
# Given this dependency graph:
|
29
|
-
#
|
30
|
-
# { a: [b c], b: [c], c: [d], d: [] }
|
31
|
-
#
|
32
|
-
# it will return:
|
33
|
-
#
|
34
|
-
# [d, c, b, a]
|
35
|
-
#
|
36
|
-
def self.topological_sort(dependencies)
|
37
|
-
dependencies.tsort
|
38
|
-
end
|
39
|
-
|
40
|
-
# Do a DFS on an AST starting at +node+
|
41
|
-
def self.depth_first_search(node, accum = [], &)
|
42
|
-
ret = yield(node)
|
43
|
-
accum << ret unless ret.nil?
|
44
|
-
|
45
|
-
return accum unless node.function_call?
|
46
|
-
|
47
|
-
node.arguments.each { |n| depth_first_search(n, accum, &) }
|
48
|
-
accum
|
49
|
-
end
|
50
|
-
|
51
|
-
# A dependency graph represented as a +Hash+ which will be used by our +topological_sort+ function
|
52
|
-
class DependencyGraph < Hash
|
53
|
-
include ::TSort
|
54
|
-
alias tsort_each_node each_key
|
55
|
-
|
56
|
-
# sort each child
|
57
|
-
def tsort_each_child(node, &)
|
58
|
-
fetch(node).each(&)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'graph'
|
4
|
-
require_relative 'scope'
|
5
|
-
|
6
|
-
module CSVPlusPlus
|
7
|
-
# References in an AST that need to be resolved
|
8
|
-
#
|
9
|
-
# @attr functions [Array<Entities::Function>] Functions references
|
10
|
-
# @attr variables [Array<Entities::Variable>] Variable references
|
11
|
-
class References
|
12
|
-
attr_accessor :functions, :variables
|
13
|
-
|
14
|
-
# Extract references from an AST and return them in a new +References+ object
|
15
|
-
#
|
16
|
-
# @param ast [Entity] An +Entity+ to do a depth first search on for references. Entities can be
|
17
|
-
# infinitely deep because they can contain other function calls as params to a function call
|
18
|
-
# @param scope [Scope] The +CodeSection+ containing all currently defined functions & variables
|
19
|
-
#
|
20
|
-
# @return [References]
|
21
|
-
def self.extract(ast, scope)
|
22
|
-
new.tap do |refs|
|
23
|
-
::CSVPlusPlus::Graph.depth_first_search(ast) do |node|
|
24
|
-
next unless node.function_call? || node.variable?
|
25
|
-
|
26
|
-
refs.functions << node if function_reference?(node, scope)
|
27
|
-
refs.variables << node if node.variable?
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Is the node a resolvable reference?
|
33
|
-
#
|
34
|
-
# @param node [Entity] The node to check if it's resolvable
|
35
|
-
#
|
36
|
-
# @return [boolean]
|
37
|
-
# TODO: move this into the Entity subclasses
|
38
|
-
def self.function_reference?(node, scope)
|
39
|
-
node.function_call? && (scope.defined_function?(node.id) \
|
40
|
-
|| ::CSVPlusPlus::Entities::Builtins::FUNCTIONS.key?(node.id))
|
41
|
-
end
|
42
|
-
|
43
|
-
private_class_method :function_reference?
|
44
|
-
|
45
|
-
# Create an object with empty references. The caller will build them up as it depth-first-searches
|
46
|
-
def initialize
|
47
|
-
@functions = []
|
48
|
-
@variables = []
|
49
|
-
end
|
50
|
-
|
51
|
-
# Are there any references to be resolved?
|
52
|
-
#
|
53
|
-
# @return [boolean]
|
54
|
-
def empty?
|
55
|
-
@functions.empty? && @variables.empty?
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return [String]
|
59
|
-
def to_s
|
60
|
-
"References(functions: #{@functions}, variables: #{@variables})"
|
61
|
-
end
|
62
|
-
|
63
|
-
# @return [boolean]
|
64
|
-
def ==(other)
|
65
|
-
@functions == other.functions && @variables == other.variables
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|