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