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.
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