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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +23 -13
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +32 -7
  9. data/lib/csv_plus_plus/compiler.rb +82 -60
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
  11. data/lib/csv_plus_plus/entities/boolean.rb +18 -9
  12. data/lib/csv_plus_plus/entities/builtins.rb +23 -9
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
  14. data/lib/csv_plus_plus/entities/date.rb +38 -5
  15. data/lib/csv_plus_plus/entities/entity.rb +27 -61
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +23 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +24 -9
  19. data/lib/csv_plus_plus/entities/number.rb +24 -10
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
  21. data/lib/csv_plus_plus/entities/string.rb +19 -6
  22. data/lib/csv_plus_plus/entities/variable.rb +16 -4
  23. data/lib/csv_plus_plus/entities.rb +20 -13
  24. data/lib/csv_plus_plus/error/error.rb +11 -1
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
  28. data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
  29. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  30. data/lib/csv_plus_plus/error.rb +1 -0
  31. data/lib/csv_plus_plus/google_api_client.rb +7 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
  34. data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
  35. data/lib/csv_plus_plus/lexer.rb +24 -0
  36. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  37. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  38. data/lib/csv_plus_plus/modifier/expand.rb +61 -0
  39. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  40. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  41. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  42. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  43. data/lib/csv_plus_plus/modifier.rb +82 -158
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
  46. data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
  48. data/lib/csv_plus_plus/row.rb +53 -12
  49. data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
  50. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
  51. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  52. data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
  53. data/lib/csv_plus_plus/runtime/references.rb +110 -0
  54. data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
  55. data/lib/csv_plus_plus/runtime.rb +34 -191
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +62 -35
  58. data/lib/csv_plus_plus/version.rb +2 -1
  59. data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
  60. data/lib/csv_plus_plus/writer/csv.rb +11 -9
  61. data/lib/csv_plus_plus/writer/excel.rb +9 -2
  62. data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
  64. data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
  65. data/lib/csv_plus_plus/writer/open_document.rb +6 -1
  66. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +29 -12
  69. metadata +18 -14
  70. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  71. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  72. data/lib/csv_plus_plus/data_validation.rb +0 -138
  73. data/lib/csv_plus_plus/expand.rb +0 -20
  74. data/lib/csv_plus_plus/graph.rb +0 -62
  75. data/lib/csv_plus_plus/references.rb +0 -68
  76. data/lib/csv_plus_plus/scope.rb +0 -196
  77. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  78. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  79. 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
  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, OpenDocument)
11
+ # Various strategies for writing to various formats (excel, google sheets, CSV & OpenDocument (not yet implemented))
11
12
  module Writer
12
- # Return an instance of a writer depending on the given +options+
13
- def self.writer(options)
14
- return ::CSVPlusPlus::Writer::GoogleSheets.new(options) if options.google
13
+ extend ::T::Sig
15
14
 
16
- case options.output_filename
17
- when /\.csv$/ then ::CSVPlusPlus::Writer::CSV.new(options)
18
- when /\.ods$/ then ::CSVPlusPlus::Writer::OpenDocument.new(options)
19
- when /\.xl(sx|sm|tx|tm)$/ then ::CSVPlusPlus::Writer::Excel.new(options)
20
- else raise(::CSVPlusPlus::Error, "Unsupported file extension: #{options.output_filename}")
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, compiler, options)
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
- def self.write_template(template, compiler, options)
62
- compiler.outputting! do
63
- output = ::CSVPlusPlus::Writer.writer(options)
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.2
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-03-20 00:00:00.000000000 Z
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/scope.rb
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,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CSVPlusPlus
4
- # Methods for classes that need to resolve references
5
- module CanResolveReferences
6
- # TODO
7
- end
8
- 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
@@ -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
@@ -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