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,8 +1,6 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './cli_flag'
4
- require_relative './google_options'
5
-
6
4
  module CSVPlusPlus
7
5
  # The options a user can supply (via CLI flags)
8
6
  #
@@ -15,28 +13,80 @@ module CSVPlusPlus
15
13
  # @attr verbose [boolean] Include extra verbose output?
16
14
  # @attr_reader google [GoogleOptions] Options that are specific to the Google Sheets writer
17
15
  class Options
18
- attr_accessor :backup, :create_if_not_exists, :key_values, :offset, :output_filename, :sheet_name, :verbose
16
+ extend ::T::Sig
17
+
18
+ # The supported output formats. We use this to dispatch flow in several places
19
+ class OutputFormat < ::T::Enum
20
+ enums do
21
+ CSV = new
22
+ Excel = new
23
+ GoogleSheets = new
24
+ OpenDocument = new
25
+ end
26
+ end
27
+
28
+ sig { returns(::T::Boolean) }
29
+ attr_accessor :backup
30
+
31
+ sig { returns(::T::Boolean) }
32
+ attr_accessor :create_if_not_exists
33
+
34
+ sig { returns(::T::Hash[::Symbol, ::String]) }
35
+ attr_accessor :key_values
36
+
37
+ sig { returns(::T::Array[::Integer]) }
38
+ attr_accessor :offset
39
+
40
+ sig { returns(::T.nilable(::String)) }
41
+ attr_accessor :output_filename
42
+
43
+ sig { returns(::T.nilable(::String)) }
44
+ attr_accessor :sheet_name
45
+
46
+ sig { returns(::T::Boolean) }
47
+ attr_accessor :verbose
48
+
49
+ sig { returns(::T.nilable(::CSVPlusPlus::GoogleOptions)) }
19
50
  attr_reader :google
20
51
 
21
- # initialize
52
+ sig { void }
53
+ # Initialize a default +Options+ object
22
54
  def initialize
23
- @offset = [0, 0]
24
- @create_if_not_exists = false
25
- @key_values = {}
26
- @verbose = false
27
- @backup = false
55
+ @offset = ::T.let([0, 0], ::T::Array[::Integer])
56
+ @create_if_not_exists = ::T.let(false, ::T::Boolean)
57
+ @key_values = ::T.let({}, ::T::Hash[::Symbol, ::String])
58
+ @verbose = ::T.let(false, ::T::Boolean)
59
+ @backup = ::T.let(false, ::T::Boolean)
60
+ @google = ::T.let(nil, ::T.nilable(::CSVPlusPlus::GoogleOptions))
28
61
  end
29
62
 
63
+ sig { params(sheet_id: ::String).returns(::CSVPlusPlus::GoogleOptions) }
30
64
  # Set the Google Sheet ID
31
65
  #
32
- # @param sheet_id [String] The identifier used by Google's API to reference the sheet. You can find it in the URL
66
+ # @param sheet_id [::String] The identifier used by Google's API to reference the sheet. You can find it in the URL
33
67
  # for the sheet
34
68
  #
35
- # @return [String]
69
+ # @return [::String]
36
70
  def google_sheet_id=(sheet_id)
37
71
  @google = ::CSVPlusPlus::GoogleOptions.new(sheet_id)
38
72
  end
39
73
 
74
+ sig { returns(::CSVPlusPlus::Options::OutputFormat) }
75
+ # Given the options, figure out which type of +OutputFormat+ we'll be writing to
76
+ #
77
+ # @return [Options::OutputFormat]
78
+ def output_format
79
+ return ::CSVPlusPlus::Options::OutputFormat::GoogleSheets if @google
80
+
81
+ case @output_filename
82
+ when /\.csv$/ then ::CSVPlusPlus::Options::OutputFormat::CSV
83
+ when /\.ods$/ then ::CSVPlusPlus::Options::OutputFormat::OpenDocument
84
+ when /\.xl(sx|sm|tx|tm)$/ then ::CSVPlusPlus::Options::OutputFormat::Excel
85
+ else raise(::CSVPlusPlus::Error::Error, "Unsupported file extension: #{@output_filename}")
86
+ end
87
+ end
88
+
89
+ sig { returns(::T.nilable(::String)) }
40
90
  # Returns an error string or nil if there are no validation problems
41
91
  #
42
92
  # @return [String, nil]
@@ -46,6 +96,7 @@ module CSVPlusPlus
46
96
  'You must supply either a Google Sheet ID or an output file'
47
97
  end
48
98
 
99
+ sig { returns(::String) }
49
100
  # Return a string with a verbose description of what we're doing with the options
50
101
  #
51
102
  # @return [String]
@@ -55,7 +106,6 @@ module CSVPlusPlus
55
106
 
56
107
  # csv++ Command Options
57
108
 
58
- > Input filename | #{@filename}
59
109
  > Sheet name | #{@sheet_name}
60
110
  > Create sheet if it does not exist? | #{@create_if_not_exists}
61
111
  > Spreadsheet row-offset | #{@offset[0]}
@@ -73,14 +123,9 @@ module CSVPlusPlus
73
123
  SUMMARY
74
124
  end
75
125
 
76
- # @return [String]
77
- def to_s
78
- "Options(create_if_not_exists: #{@create_if_not_exists}, google: #{@google}, key_values: #{@key_values}, " \
79
- "offset: #{@offset}, sheet_name: #{@sheet_name}, verbose: #{@verbose})"
80
- end
81
-
82
126
  private
83
127
 
128
+ sig { returns(::String) }
84
129
  def summary_divider
85
130
  '========================================================================='
86
131
  end
@@ -221,7 +221,7 @@ module_eval(<<'.,.,', 'cell_value.y', 25)
221
221
 
222
222
  module_eval(<<'.,.,', 'cell_value.y', 26)
223
223
  def _reduce_5(val, _values, result)
224
- result = variable(val[1])
224
+ result = variable(val[1].to_sym)
225
225
  result
226
226
  end
227
227
  .,.,
@@ -256,21 +256,21 @@ module_eval(<<'.,.,', 'cell_value.y', 30)
256
256
 
257
257
  module_eval(<<'.,.,', 'cell_value.y', 31)
258
258
  def _reduce_10(val, _values, result)
259
- result = cell_reference(val[0])
259
+ result = cell_reference(ref: val[0])
260
260
  result
261
261
  end
262
262
  .,.,
263
263
 
264
264
  module_eval(<<'.,.,', 'cell_value.y', 33)
265
265
  def _reduce_11(val, _values, result)
266
- result = function_call(val[0], val[2])
266
+ result = function_call(val[0].to_sym, val[2])
267
267
  result
268
268
  end
269
269
  .,.,
270
270
 
271
271
  module_eval(<<'.,.,', 'cell_value.y', 34)
272
272
  def _reduce_12(val, _values, result)
273
- result = function_call(val[0], [])
273
+ result = function_call(val[0].to_sym, [])
274
274
  result
275
275
  end
276
276
  .,.,
@@ -291,7 +291,7 @@ module_eval(<<'.,.,', 'cell_value.y', 37)
291
291
 
292
292
  module_eval(<<'.,.,', 'cell_value.y', 39)
293
293
  def _reduce_15(val, _values, result)
294
- result = function_call(val[1], [val[0], val[2]], infix: true)
294
+ result = function_call(val[1].to_sym, [val[0], val[2]], infix: true)
295
295
  result
296
296
  end
297
297
  .,.,
@@ -17,11 +17,6 @@ module_eval(<<'...end code_section.y/module_eval...', 'code_section.y', 69)
17
17
  include ::CSVPlusPlus::Lexer
18
18
  include ::CSVPlusPlus::Entities::ASTBuilder
19
19
 
20
- def initialize(scope)
21
- super()
22
- @scope = scope
23
- end
24
-
25
20
  protected
26
21
 
27
22
  def anything_to_parse?(input)
@@ -66,12 +61,12 @@ module_eval(<<'...end code_section.y/module_eval...', 'code_section.y', 69)
66
61
  private
67
62
 
68
63
  def def_function(id, arguments, body)
69
- fn_def = function(id, arguments, body)
70
- @scope.def_function(fn_def.id, fn_def)
64
+ fn_def = function(id.to_sym, arguments, body)
65
+ @runtime.def_function(fn_def.id, fn_def)
71
66
  end
72
67
 
73
68
  def def_variable(id, ast)
74
- @scope.def_variable(id, ast)
69
+ @runtime.def_variable(id.to_sym, ast)
75
70
  end
76
71
  ...end code_section.y/module_eval...
77
72
  ##### State transition tables begin ###
@@ -326,7 +321,7 @@ module_eval(<<'.,.,', 'code_section.y', 45)
326
321
 
327
322
  module_eval(<<'.,.,', 'code_section.y', 46)
328
323
  def _reduce_16(val, _values, result)
329
- result = variable(val[1])
324
+ result = variable(val[1].to_sym)
330
325
  result
331
326
  end
332
327
  .,.,
@@ -361,28 +356,28 @@ module_eval(<<'.,.,', 'code_section.y', 50)
361
356
 
362
357
  module_eval(<<'.,.,', 'code_section.y', 51)
363
358
  def _reduce_21(val, _values, result)
364
- result = cell_reference(val[0])
359
+ result = cell_reference(ref: val[0])
365
360
  result
366
361
  end
367
362
  .,.,
368
363
 
369
364
  module_eval(<<'.,.,', 'code_section.y', 53)
370
365
  def _reduce_22(val, _values, result)
371
- result = function_call(val[1], [val[0], val[2]], infix: true)
366
+ result = function_call(val[1].to_sym, [val[0], val[2]], infix: true)
372
367
  result
373
368
  end
374
369
  .,.,
375
370
 
376
371
  module_eval(<<'.,.,', 'code_section.y', 55)
377
372
  def _reduce_23(val, _values, result)
378
- result = function_call(val[0], val[2])
373
+ result = function_call(val[0].to_sym, val[2])
379
374
  result
380
375
  end
381
376
  .,.,
382
377
 
383
378
  module_eval(<<'.,.,', 'code_section.y', 56)
384
379
  def _reduce_24(val, _values, result)
385
- result = function_call(val[0], [])
380
+ result = function_call(val[0].to_sym, [])
386
381
  result
387
382
  end
388
383
  .,.,
@@ -7,28 +7,25 @@
7
7
  require 'racc/parser.rb'
8
8
 
9
9
 
10
- require_relative '../expand'
11
10
  require_relative '../lexer'
12
11
 
13
12
  module CSVPlusPlus
14
13
  module Parser
15
14
  class Modifier < Racc::Parser
16
15
 
17
- module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 61)
16
+ module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 60)
18
17
  attr_reader :return_value
19
18
 
20
19
  include ::CSVPlusPlus::Lexer
21
20
 
22
21
  # @param cell_modifier [Modifier]
23
22
  # @param row_modifier [Modifier]
24
- # @param scope [Scope]
25
- def initialize(cell_modifier:, row_modifier:, scope:)
23
+ def initialize(cell_modifier:, row_modifier:)
26
24
  super()
27
25
 
28
26
  @parsing_row = false
29
- @cell_modifier = cell_modifier
30
- @row_modifier = row_modifier
31
- @scope = scope
27
+ @cell_modifier = ::CSVPlusPlus::Modifier::ModifierValidator.new(cell_modifier)
28
+ @row_modifier = ::CSVPlusPlus::Modifier::ModifierValidator.new(row_modifier)
32
29
  end
33
30
 
34
31
  protected
@@ -82,14 +79,16 @@ module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 61)
82
79
  [
83
80
  /
84
81
  (?:
85
- [\w,_:-] # something that accepts most basic input if it doesn't need to be quoted
86
- [\w\s,_:-]+ # same thing but allow spaces in the middle
87
- [\w,_:-] # no spaces at the end
88
- )
89
- | # - or -
82
+ \w+\s*:\s*'([^'\\]|\\.)*') # allow for a single-quoted string which can accept any input and also allow
83
+ # for escaping via backslash (i.e., 'ain\\'t won\\'t something' is valid)
84
+ | # - or -
85
+ (?:'([^'\\]|\\.)*') # allow for a single-quoted string which can accept any input and also allow
86
+ |
90
87
  (?:
91
- '([^'\\]|\\.)*') # allow for a single-quoted string which can accept any input and also allow
92
- # for escaping via backslash (i.e., 'ain\\'t won\\'t something' is valid)
88
+ [\w,_:-] # something that accepts most basic input if it doesn't need to be quoted
89
+ [\w\s,_:-]+ # same thing but allow spaces in the middle
90
+ [\w,_:-] # no spaces at the end
91
+ )
93
92
  /x,
94
93
  :RIGHT_SIDE,
95
94
  ],
@@ -107,7 +106,7 @@ module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 61)
107
106
  private
108
107
 
109
108
  def assign_defaults!
110
- @cell_modifier.take_defaults_from!(@row_modifier)
109
+ @cell_modifier.modifier.take_defaults_from!(@row_modifier.modifier)
111
110
  end
112
111
 
113
112
  def parsing_row!
@@ -123,11 +122,6 @@ module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 61)
123
122
  assign_defaults!
124
123
  end
125
124
 
126
- def define_var(var_id)
127
- @scope.bind_variable_to_cell(var_id)
128
- modifier.var = var_id.to_sym
129
- end
130
-
131
125
  def modifier
132
126
  @parsing_row ? @row_modifier : @cell_modifier
133
127
  end
@@ -393,7 +387,7 @@ module_eval(<<'.,.,', 'modifier.y', 39)
393
387
 
394
388
  module_eval(<<'.,.,', 'modifier.y', 40)
395
389
  def _reduce_15(val, _values, result)
396
- modifier.expand!
390
+ modifier.infinite_expand!
397
391
  result
398
392
  end
399
393
  .,.,
@@ -456,7 +450,7 @@ module_eval(<<'.,.,', 'modifier.y', 48)
456
450
 
457
451
  module_eval(<<'.,.,', 'modifier.y', 49)
458
452
  def _reduce_24(val, _values, result)
459
- modifier.validation = val[2]
453
+ modifier.validate = val[2]
460
454
  result
461
455
  end
462
456
  .,.,
@@ -470,7 +464,7 @@ module_eval(<<'.,.,', 'modifier.y', 50)
470
464
 
471
465
  module_eval(<<'.,.,', 'modifier.y', 51)
472
466
  def _reduce_26(val, _values, result)
473
- define_var(val[2])
467
+ modifier.var = val[2]
474
468
  result
475
469
  end
476
470
  .,.,
@@ -1,23 +1,37 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
- # A row of a template
5
+ # A row of a template. A row contains an +Array+ of +Cell+s and possibly a row-level +Modifier+.
5
6
  #
6
- # @attr_reader cells [Array<Cell>]
7
- # @attr_reader index [Integer] The index of this row
7
+ # @attr_reader cells [Array<Cell>] The cells contained by this row.
8
+ # @attr_reader index [Integer] The index of this row. Starts at 0.
8
9
  # @attr_reader modifier [Modifier] The modifier to apply to all cells in this row
9
10
  class Row
10
- attr_reader :cells, :index, :modifier
11
+ extend ::T::Sig
11
12
 
12
- # @param index [Integer] The index of this row (starts at 0)
13
+ sig { returns(::T::Array[::CSVPlusPlus::Cell]) }
14
+ attr_reader :cells
15
+
16
+ sig { returns(::Integer) }
17
+ attr_reader :index
18
+
19
+ sig { returns(::CSVPlusPlus::Modifier::Modifier) }
20
+ attr_reader :modifier
21
+
22
+ sig do
23
+ params(cells: ::T::Array[::CSVPlusPlus::Cell], index: ::Integer, modifier: ::CSVPlusPlus::Modifier::Modifier).void
24
+ end
13
25
  # @param cells [Array<Cell>] The cells belonging to this row
26
+ # @param index [Integer] The index of this row (starts at 0)
14
27
  # @param modifier [Modifier] The modifier to apply to all cells in this row
15
- def initialize(index, cells, modifier)
28
+ def initialize(cells:, index:, modifier:)
16
29
  @cells = cells
17
30
  @modifier = modifier
18
31
  @index = index
19
32
  end
20
33
 
34
+ sig { params(index: ::Integer).void }
21
35
  # Set the row's +index+ and update the +row_index+ of all affected cells
22
36
  #
23
37
  # @param index [Integer] The index of this row (starts at 0)
@@ -26,25 +40,52 @@ module CSVPlusPlus
26
40
  @cells.each { |cell| cell.row_index = index }
27
41
  end
28
42
 
43
+ sig { returns(::Integer) }
29
44
  # How much this row will expand itself, if at all (0)
30
45
  #
31
46
  # @return [Integer]
32
47
  def expand_amount
33
- return 0 unless @modifier.expand
48
+ return 0 if @modifier.expand.nil?
34
49
 
35
- @modifier.expand.repetitions || (1000 - @index)
50
+ ::T.must(@modifier.expand).repetitions || (1000 - @index)
36
51
  end
37
52
 
38
- # @return [String]
39
- def to_s
40
- "Row(index: #{index}, modifier: #{modifier}, cells: #{cells})"
53
+ sig { params(starts_at: ::Integer, into: ::T::Array[::CSVPlusPlus::Row]).returns(::T::Array[::CSVPlusPlus::Row]) }
54
+ # Starting at +starts_at+, do a deep copy of this row into the +Array+ referenced by +into+.
55
+ #
56
+ # @param starts_at [Integer] The +row_index+ where this row was expanded.
57
+ # @param into [Array<Row>] An array where the expanded rows will be accumulated.
58
+ #
59
+ # @return [Array<Row>] The rows expanded
60
+ def expand_rows(starts_at:, into: [])
61
+ return into if @modifier.expand.nil?
62
+
63
+ ::T.must(@modifier.expand).starts_at = starts_at
64
+
65
+ starts_at.upto(expand_amount + starts_at - 1) do |row_index|
66
+ into << deep_clone.tap { |c| c.index = row_index }
67
+ end
68
+ into
41
69
  end
42
70
 
71
+ sig { returns(::T::Boolean) }
72
+ # Does the row have an ![[expand]] modifier but is yet to be expanded?
73
+ #
74
+ # @return [T::Boolean]
75
+ def unexpanded?
76
+ return false if @modifier.expand.nil?
77
+
78
+ !::T.must(@modifier.expand).expanded?
79
+ end
80
+
81
+ private
82
+
83
+ sig { returns(::CSVPlusPlus::Row) }
43
84
  # Return a deep copy of this row
44
85
  #
45
86
  # @return [Row]
46
87
  def deep_clone
47
- ::Marshal.load(::Marshal.dump(self))
88
+ ::T.cast(::Marshal.load(::Marshal.dump(self)), ::CSVPlusPlus::Row)
48
89
  end
49
90
  end
50
91
  end
@@ -0,0 +1,87 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Runtime
6
+ # Methods for classes that need to manage +@variables+ and +@functions+
7
+ module CanDefineReferences
8
+ # Define a (or re-define an existing) variable
9
+ #
10
+ # @param id [String, Symbol] The identifier for the variable
11
+ # @param entity [Entity] The value (entity) the variable holds
12
+ #
13
+ # @return [Entity] The value of the variable (+entity+)
14
+ def def_variable(id, entity)
15
+ @variables[id.to_sym] = entity
16
+ end
17
+
18
+ # Define (or re-define existing) variables
19
+ #
20
+ # @param vars [Hash<Symbol, Variable>] Variables to define
21
+ def def_variables(vars)
22
+ vars.each { |id, entity| def_variable(id, entity) }
23
+ end
24
+
25
+ # Define a (or re-define an existing) function
26
+ #
27
+ # @param id [String, Symbol] The identifier for the function
28
+ # @param entity [Entities::Function] The defined function
29
+ #
30
+ # @return [Entities::Function] The defined function
31
+ def def_function(id, entity)
32
+ @functions[id.to_sym] = entity
33
+ end
34
+
35
+ # Is the variable defined?
36
+ #
37
+ # @param var_id [Symbol, String] The identifier of the variable
38
+ #
39
+ # @return [boolean]
40
+ def defined_variable?(var_id)
41
+ @variables.key?(var_id.to_sym)
42
+ end
43
+
44
+ # Is the function defined?
45
+ #
46
+ # @param fn_id [Symbol, String] The identifier of the function
47
+ #
48
+ # @return [boolean]
49
+ def defined_function?(fn_id)
50
+ @functions.key?(fn_id.to_sym)
51
+ end
52
+
53
+ # Provide a summary of the functions and variables compiled (to show in verbose mode)
54
+ #
55
+ # @return [String]
56
+ def verbose_summary
57
+ <<~SUMMARY
58
+ # Code Section Summary
59
+
60
+ ## Resolved Variables
61
+
62
+ #{variable_summary}
63
+
64
+ ## Functions
65
+
66
+ #{function_summary}
67
+ SUMMARY
68
+ end
69
+
70
+ private
71
+
72
+ def variable_summary
73
+ return '(no variables defined)' if @variables.empty?
74
+
75
+ @variables.map { |k, v| "#{k} := #{v}" }
76
+ .join("\n")
77
+ end
78
+
79
+ def function_summary
80
+ return '(no functions defined)' if @functions.empty?
81
+
82
+ @functions.map { |k, f| "#{k}: #{f}" }
83
+ .join("\n")
84
+ end
85
+ end
86
+ end
87
+ end