csv_plus_plus 0.1.2 → 0.2.0

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -5
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  6. data/lib/csv_plus_plus/cell.rb +29 -41
  7. data/lib/csv_plus_plus/cli.rb +53 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -71
  9. data/lib/csv_plus_plus/color.rb +32 -7
  10. data/lib/csv_plus_plus/compiler.rb +98 -66
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
  12. data/lib/csv_plus_plus/entities/boolean.rb +26 -10
  13. data/lib/csv_plus_plus/entities/builtins.rb +66 -24
  14. data/lib/csv_plus_plus/entities/date.rb +42 -6
  15. data/lib/csv_plus_plus/entities/entity.rb +17 -69
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
  17. data/lib/csv_plus_plus/entities/function.rb +34 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +49 -10
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +30 -11
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
  23. data/lib/csv_plus_plus/entities/string.rb +23 -7
  24. data/lib/csv_plus_plus/entities.rb +7 -16
  25. data/lib/csv_plus_plus/error/cli_error.rb +17 -0
  26. data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
  27. data/lib/csv_plus_plus/error/error.rb +25 -2
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  33. data/lib/csv_plus_plus/error.rb +5 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +25 -10
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
  38. data/lib/csv_plus_plus/lexer.rb +64 -1
  39. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  40. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  41. data/lib/csv_plus_plus/modifier/expand.rb +78 -0
  42. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  43. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  44. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  45. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  46. data/lib/csv_plus_plus/modifier.rb +89 -160
  47. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  48. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  49. data/lib/csv_plus_plus/options/options.rb +97 -0
  50. data/lib/csv_plus_plus/options.rb +34 -77
  51. data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
  52. data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
  53. data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
  54. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  55. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  56. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  57. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  58. data/lib/csv_plus_plus/reader.rb +14 -0
  59. data/lib/csv_plus_plus/row.rb +53 -12
  60. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  61. data/lib/csv_plus_plus/runtime/position.rb +242 -0
  62. data/lib/csv_plus_plus/runtime/references.rb +115 -0
  63. data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
  64. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  65. data/lib/csv_plus_plus/runtime.rb +34 -191
  66. data/lib/csv_plus_plus/source_code.rb +71 -0
  67. data/lib/csv_plus_plus/template.rb +71 -39
  68. data/lib/csv_plus_plus/version.rb +2 -1
  69. data/lib/csv_plus_plus/writer/csv.rb +37 -8
  70. data/lib/csv_plus_plus/writer/excel.rb +25 -5
  71. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
  72. data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
  73. data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
  74. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  75. data/lib/csv_plus_plus/writer/open_document.rb +21 -2
  76. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
  77. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  78. data/lib/csv_plus_plus/writer.rb +79 -10
  79. data/lib/csv_plus_plus.rb +47 -18
  80. metadata +50 -21
  81. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  82. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  83. data/lib/csv_plus_plus/data_validation.rb +0 -138
  84. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -60
  85. data/lib/csv_plus_plus/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
  87. data/lib/csv_plus_plus/expand.rb +0 -20
  88. data/lib/csv_plus_plus/google_options.rb +0 -27
  89. data/lib/csv_plus_plus/graph.rb +0 -62
  90. data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
  91. data/lib/csv_plus_plus/references.rb +0 -68
  92. data/lib/csv_plus_plus/scope.rb +0 -196
  93. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  94. data/lib/csv_plus_plus/writer/base_writer.rb +0 -20
  95. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
  96. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  97. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,7 +1,6 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './google_options'
4
-
5
4
  module CSVPlusPlus
6
5
  # Individual CLI flags that a user can supply
7
6
  #
@@ -9,83 +8,84 @@ module CSVPlusPlus
9
8
  # @attr_reader long_flag [String] A definition of the long/word-based flag
10
9
  # @attr_reader description [String] A description of what the flag does
11
10
  # @attr_reader handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
12
- class CliFlag
13
- attr_reader :short_flag, :long_flag, :description, :handler
11
+ class CLIFlag
12
+ extend ::T::Sig
13
+
14
+ sig { returns(::String) }
15
+ attr_reader :description
16
+
17
+ sig { returns(::String) }
18
+ attr_reader :long_flag
14
19
 
20
+ sig { returns(::String) }
21
+ attr_reader :short_flag
22
+
23
+ sig { params(short_flag: ::String, long_flag: ::String, description: ::String).void }
15
24
  # @param short_flag [String] A definition of the short/single-character flag
16
25
  # @param long_flag [String] A definition of the long/word-based flag
17
26
  # @param description [String] A description of what the flag does
18
- # @param handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
19
- def initialize(short_flag, long_flag, description, handler)
27
+ def initialize(short_flag, long_flag, description)
20
28
  @short_flag = short_flag
21
29
  @long_flag = long_flag
22
30
  @description = description
23
- @handler = handler
24
- end
25
-
26
- # @return [String]
27
- def to_s
28
- "#{@short_flag}, #{@long_flag} #{@description}"
29
31
  end
30
32
  end
31
- end
32
33
 
33
- SUPPORTED_CSVPP_FLAGS = [
34
- ::CSVPlusPlus::CliFlag.new(
35
- '-b',
36
- '--backup',
37
- 'Create a backup of the spreadsheet before applying changes.',
38
- ->(options, _v) { options.backup = true }
39
- ),
40
- ::CSVPlusPlus::CliFlag.new(
41
- '-c',
42
- '--create',
43
- "Create the sheet if it doesn't exist. It will use --sheet-name if specified",
44
- ->(options, _v) { options.create_if_not_exists = true }
45
- ),
46
- ::CSVPlusPlus::CliFlag.new(
47
- '-g SHEET_ID',
48
- '--google-sheet-id SHEET_ID',
49
- 'The id of the sheet - you can extract this from the URL: ' \
50
- 'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0',
51
- ->(options, v) { options.google_sheet_id = v }
52
- ),
53
- ::CSVPlusPlus::CliFlag.new(
54
- '-k',
55
- '--key-values KEY_VALUES',
56
- 'A comma-separated list of key=values which will be made available to the template',
57
- lambda do |options, v|
58
- options.key_values =
59
- begin
60
- [v.split('=')].to_h
61
- rescue ::StandardError
62
- {}
63
- end
64
- end
65
- ),
66
- ::CSVPlusPlus::CliFlag.new(
67
- '-n SHEET_NAME',
68
- '--sheet-name SHEET_NAME',
69
- 'The name of the sheet to apply the template to',
70
- ->(options, v) { options.sheet_name = v }
71
- ),
72
- ::CSVPlusPlus::CliFlag.new(
73
- '-o OUTPUT_FILE',
74
- '--output OUTPUT_FILE',
75
- 'The file to write to (must be .csv, .ods, .xls)',
76
- ->(options, v) { options.output_filename = v }
77
- ),
78
- ::CSVPlusPlus::CliFlag.new('-v', '--verbose', 'Enable verbose output', ->(options, _v) { options.verbose = true }),
79
- ::CSVPlusPlus::CliFlag.new(
80
- '-x OFFSET',
81
- '--offset-columns OFFSET',
82
- 'Apply the template offset by OFFSET cells',
83
- ->(options, v) { options.offset[0] = v }
84
- ),
85
- ::CSVPlusPlus::CliFlag.new(
86
- '-y OFFSET',
87
- '--offset-rows OFFSET',
88
- 'Apply the template offset by OFFSET rows',
89
- ->(options, v) { options.offset[1] = v }
34
+ FLAG_HANDLERS = ::T.let(
35
+ {
36
+ backup: ->(options, _v) { options.backup = true },
37
+ create: ->(options, _v) { options.create_if_not_exists = true },
38
+ 'key-values': lambda { |options, v|
39
+ options.key_values =
40
+ begin
41
+ [v.split('=')].to_h
42
+ rescue ::StandardError
43
+ {}
44
+ end
45
+ },
46
+ 'offset-columns': ->(options, v) { options.offset[0] = v },
47
+ 'offset-rows': ->(options, v) { options.offset[1] = v },
48
+ output: ->(options, v) { options.output_filename = ::Pathname.new(v) },
49
+ verbose: ->(options, _v) { options.verbose = true }
50
+ },
51
+ ::T::Hash[::Symbol, ::T.proc.params(options: ::CSVPlusPlus::Options::Options, v: ::String).void]
90
52
  )
91
- ].freeze
53
+ public_constant :FLAG_HANDLERS
54
+
55
+ SUPPORTED_CSVPP_FLAGS = ::T.let(
56
+ [
57
+ ::CSVPlusPlus::CLIFlag.new('-b', '--backup', 'Create a backup of the spreadsheet before applying changes.'),
58
+ ::CSVPlusPlus::CLIFlag.new(
59
+ '-c',
60
+ '--create',
61
+ "Create the sheet if it doesn't exist. It will use --sheet-name if specified"
62
+ ),
63
+ ::CSVPlusPlus::CLIFlag.new(
64
+ '-g SHEET_ID',
65
+ '--google-sheet-id SHEET_ID',
66
+ 'The id of the sheet - you can extract this from the URL: ' \
67
+ 'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0'
68
+ ),
69
+ ::CSVPlusPlus::CLIFlag.new(
70
+ '-k',
71
+ '--key-values KEY_VALUES',
72
+ 'A comma-separated list of key=values which will be made available to the template'
73
+ ),
74
+ ::CSVPlusPlus::CLIFlag.new(
75
+ '-n SHEET_NAME',
76
+ '--sheet-name SHEET_NAME',
77
+ 'The name of the sheet to apply the template to'
78
+ ),
79
+ ::CSVPlusPlus::CLIFlag.new(
80
+ '-o OUTPUT_FILE',
81
+ '--output OUTPUT_FILE',
82
+ 'The file to write to (must be .csv, .ods, .xls)'
83
+ ),
84
+ ::CSVPlusPlus::CLIFlag.new('-v', '--verbose', 'Enable verbose output'),
85
+ ::CSVPlusPlus::CLIFlag.new('-x OFFSET', '--offset-columns OFFSET', 'Apply the template offset by OFFSET cells'),
86
+ ::CSVPlusPlus::CLIFlag.new('-y OFFSET', '--offset-rows OFFSET', 'Apply the template offset by OFFSET rows')
87
+ ].freeze,
88
+ ::T::Array[::CSVPlusPlus::CLIFlag]
89
+ )
90
+ public_constant :SUPPORTED_CSVPP_FLAGS
91
+ end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -7,25 +8,49 @@ module CSVPlusPlus
7
8
  # attr_reader green_hex [String] The green value in hex ("FF", "00", "AF", etc)
8
9
  # attr_reader red_hex [String] The red value in hex ("FF", "00", "AF", etc)
9
10
  class Color
10
- attr_reader :red_hex, :green_hex, :blue_hex
11
+ extend ::T::Sig
12
+
13
+ sig { returns(::String) }
14
+ attr_reader :red_hex
15
+
16
+ sig { returns(::String) }
17
+ attr_reader :green_hex
18
+
19
+ sig { returns(::String) }
20
+ attr_reader :blue_hex
11
21
 
12
22
  HEX_STRING_REGEXP = /^#?([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/i
13
23
  public_constant :HEX_STRING_REGEXP
14
24
 
25
+ sig { params(hex_string: ::String).returns(::T::Boolean) }
26
+ # Is +hex_string+ a valid hexadecimal color code? This function will accept input like the 6-digit format: #FF00FF,
27
+ # 00AABB and the shorter 3-digit format: #FFF, 0FA.
28
+ #
29
+ # @param hex_string [::String] The string to see if it's valid hex string
30
+ #
15
31
  # @return [boolean]
16
32
  def self.valid_hex_string?(hex_string)
17
33
  !(hex_string.strip =~ ::CSVPlusPlus::Color::HEX_STRING_REGEXP).nil?
18
34
  end
19
35
 
36
+ sig { params(hex_string: ::String).void }
20
37
  # Create an instance from a string like "#FFF" or "#FFFFFF"
21
38
  #
22
39
  # @param hex_string [String] The hex string input to parse
40
+ # rubocop:disable Metrics/CyclomaticComplexity
23
41
  def initialize(hex_string)
24
- @red_hex, @green_hex, @blue_hex = hex_string.strip.match(::CSVPlusPlus::Color::HEX_STRING_REGEXP)
42
+ red_hex, green_hex, blue_hex = hex_string.strip.match(::CSVPlusPlus::Color::HEX_STRING_REGEXP)
25
43
  &.captures
26
44
  &.map { |s| s.length == 1 ? s + s : s }
45
+ raise(::CSVPlusPlus::Error::CompilerError, "Invalid color: #{hex_string}") unless red_hex && green_hex && blue_hex
46
+
47
+ @red_hex = ::T.let(red_hex, ::String)
48
+ @green_hex = ::T.let(green_hex, ::String)
49
+ @blue_hex = ::T.let(blue_hex, ::String)
27
50
  end
51
+ # rubocop:enable Metrics/CyclomaticComplexity
28
52
 
53
+ sig { returns(::Float) }
29
54
  # The percent (decimal between 0-1) of red
30
55
  #
31
56
  # @return [Numeric]
@@ -33,6 +58,7 @@ module CSVPlusPlus
33
58
  hex_to_percent(@red_hex)
34
59
  end
35
60
 
61
+ sig { returns(::Float) }
36
62
  # The percent (decimal between 0-1) of green
37
63
  #
38
64
  # @return [Numeric]
@@ -40,6 +66,7 @@ module CSVPlusPlus
40
66
  hex_to_percent(@green_hex)
41
67
  end
42
68
 
69
+ sig { returns(::Float) }
43
70
  # The percent (decimal between 0-1) of blue
44
71
  #
45
72
  # @return [Numeric]
@@ -47,6 +74,7 @@ module CSVPlusPlus
47
74
  hex_to_percent(@blue_hex)
48
75
  end
49
76
 
77
+ sig { returns(::String) }
50
78
  # Create a hex representation of the color (without a '#')
51
79
  #
52
80
  # @return [::String]
@@ -54,11 +82,7 @@ module CSVPlusPlus
54
82
  [@red_hex, @green_hex, @blue_hex].join
55
83
  end
56
84
 
57
- # @return [::String]
58
- def to_s
59
- "Color(r: #{@red_hex}, g: #{@green_hex}, b: #{@blue_hex})"
60
- end
61
-
85
+ sig { params(other: ::Object).returns(::T::Boolean) }
62
86
  # @return [boolean]
63
87
  def ==(other)
64
88
  other.is_a?(self.class) &&
@@ -69,6 +93,7 @@ module CSVPlusPlus
69
93
 
70
94
  private
71
95
 
96
+ sig { params(hex: ::String).returns(::Float) }
72
97
  def hex_to_percent(hex)
73
98
  hex.to_i(16) / 255.0
74
99
  end
@@ -1,59 +1,69 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative 'benchmarked_compiler'
4
- require_relative 'entities'
5
- require_relative 'parser/code_section.tab'
6
- require_relative 'runtime'
7
- require_relative 'scope'
8
-
9
4
  module CSVPlusPlus
10
5
  # Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+). Variable resolution is delegated
11
6
  # to the +Scope+
12
7
  #
13
8
  # @attr_reader options [Options] The +Options+ to compile with
14
9
  # @attr_reader runtime [Runtime] The runtime execution
15
- # @attr_reader scope [Scope] +Scope+ for variable resolution
10
+ # rubocop:disable Metrics/ClassLength
16
11
  class Compiler
17
- attr_reader :options, :runtime, :scope
12
+ extend ::T::Sig
13
+
14
+ sig { returns(::CSVPlusPlus::Options::Options) }
15
+ attr_reader :options
18
16
 
17
+ sig { returns(::CSVPlusPlus::Runtime::Runtime) }
18
+ attr_reader :runtime
19
+
20
+ sig do
21
+ params(
22
+ options: ::CSVPlusPlus::Options::Options,
23
+ runtime: ::CSVPlusPlus::Runtime::Runtime,
24
+ block: ::T.proc.params(arg0: ::CSVPlusPlus::Compiler).void
25
+ ).void
26
+ end
19
27
  # Create a compiler and make sure it gets cleaned up
20
28
  #
21
- # @param runtime [Runtime] The initial +Runtime+ for the compiler
22
29
  # @param options [Options]
23
- def self.with_compiler(runtime:, options:, &block)
24
- compiler = new(options:, runtime:)
30
+ # @param runtime [Runtime] The initial +Runtime+ for the compiler
31
+ def self.with_compiler(options:, runtime:, &block)
25
32
  if options.verbose
26
- ::CSVPlusPlus::BenchmarkedCompiler.with_benchmarks(compiler) do |c|
33
+ ::CSVPlusPlus::BenchmarkedCompiler.with_benchmarks(options:, runtime:) do |c|
27
34
  block.call(c)
28
35
  end
29
36
  else
30
- yield(compiler)
37
+ block.call(new(options:, runtime:))
31
38
  end
32
39
  ensure
33
- runtime.cleanup!
40
+ runtime.position.cleanup!
34
41
  end
35
42
 
36
- # @param runtime [Runtime]
43
+ sig { params(options: ::CSVPlusPlus::Options::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
37
44
  # @param options [Options]
38
- # @param scope [Scope, nil]
39
- def initialize(runtime:, options:, scope: nil)
45
+ # @param runtime [Runtime]
46
+ def initialize(options:, runtime:)
40
47
  @options = options
41
48
  @runtime = runtime
42
- @scope = scope || ::CSVPlusPlus::Scope.new(runtime:)
43
49
 
44
50
  # TODO: infer a type
45
51
  # allow user-supplied key/values to override anything global or from the code section
46
- @scope.def_variables(
52
+ @runtime.scope.def_variables(
47
53
  options.key_values.transform_values { |v| ::CSVPlusPlus::Entities::String.new(v.to_s) }
48
54
  )
49
55
  end
50
56
 
51
- # Write the compiled results
52
- def outputting!
53
- @runtime.start_at_csv!
54
- yield
57
+ sig { params(benchmark: ::Benchmark::Report).void }
58
+ # Attach a +Benchmark+ and a place to store timings to the compiler class.
59
+ #
60
+ # @param benchmark [Benchmark] A +Benchmark+ instance
61
+ def benchmark=(benchmark)
62
+ @benchmark = ::T.let(benchmark, ::T.nilable(::Benchmark::Report))
63
+ @timings = ::T.let([], ::T.nilable(::T::Array[::Benchmark::Tms]))
55
64
  end
56
65
 
66
+ sig { returns(::CSVPlusPlus::Template) }
57
67
  # Compile a template and return a +::CSVPlusPlus::Template+ instance ready to be written with a +Writer+
58
68
  #
59
69
  # @return [Template]
@@ -61,96 +71,118 @@ module CSVPlusPlus
61
71
  parse_code_section!
62
72
  rows = parse_csv_section!
63
73
 
64
- ::CSVPlusPlus::Template.new(rows:, scope: @scope).tap do |t|
65
- t.validate_infinite_expands(@runtime)
66
- expanding { t.expand_rows! }
74
+ ::CSVPlusPlus::Template.new(rows:, runtime: @runtime).tap do |t|
75
+ t.validate_infinite_expands
76
+ expanding! { t.expand_rows! }
77
+ bind_all_vars! { t.bind_all_vars!(@runtime) }
67
78
  resolve_all_cells!(t)
68
79
  end
69
80
  end
70
81
 
71
- # @return [String]
72
- def to_s
73
- "Compiler(options: #{@options}, runtime: #{@runtime}, scope: #{@scope})"
82
+ sig { params(block: ::T.proc.params(position: ::CSVPlusPlus::Runtime::Position).void).void }
83
+ # Write the compiled results
84
+ def outputting!(&block)
85
+ @runtime.start_at_csv! { block.call(@runtime.position) }
74
86
  end
75
87
 
76
88
  protected
77
89
 
78
- # Parses the input file and returns a +CodeSection+
79
- #
80
- # @return [CodeSection]
90
+ sig { void }
91
+ # Parses the input file and sets variables on +@runtime+ as necessary
81
92
  def parse_code_section!
82
- @runtime.start!
83
-
84
- # TODO: this flow can probably be refactored, it used to have more needs back when we had to
85
- # parse and save the code_section
86
- parsing_code_section do |input|
87
- csv_section = ::CSVPlusPlus::Parser::CodeSection.new(@scope).parse(input, @runtime)
88
- # TODO: call scope.resolve_static_variables?? or maybe it doesn't matter
89
-
90
- # return the csv_section to the caller because they're gonna re-write input with it
91
- next csv_section
93
+ @runtime.start! do
94
+ # TODO: this flow can probably be refactored, it used to have more needs back when we had to
95
+ # parse and save the code_section
96
+ parsing_code_section do |input|
97
+ csv_section = ::CSVPlusPlus::Parser::CodeSection.new(@runtime.scope).parse(input)
98
+
99
+ # return the csv_section to the caller because they're gonna re-write input with it
100
+ next csv_section
101
+ end
92
102
  end
93
- # @scope.code_section
94
103
  end
95
104
 
105
+ sig { returns(::T::Array[::CSVPlusPlus::Row]) }
96
106
  # Parse the CSV section and return an array of +Row+s
97
107
  #
98
108
  # @return [Array<Row>]
99
109
  def parse_csv_section!
100
- @runtime.start_at_csv!
101
- @runtime.map_rows(::CSV.new(runtime.input)) do |csv_row|
102
- parse_row(csv_row)
110
+ @runtime.start_at_csv! do
111
+ @runtime.position.map_lines(::CSV.new(::T.unsafe(@runtime.position.input))) do |csv_row|
112
+ parse_row(::T.cast(csv_row, ::T::Array[::String]))
113
+ end
103
114
  end
104
115
  ensure
105
116
  # we're done with the file and everything is in memory
106
- @runtime.cleanup!
117
+ @runtime.position.cleanup!
107
118
  end
108
119
 
120
+ sig do
121
+ params(template: ::CSVPlusPlus::Template)
122
+ .returns(::T::Array[::T::Array[::T.nilable(::CSVPlusPlus::Entities::Entity)]])
123
+ end
109
124
  # Iterates through each cell of each row and resolves it's variable and function references.
110
125
  #
111
126
  # @param template [Template]
127
+ #
112
128
  # @return [Array<Entity>]
113
129
  def resolve_all_cells!(template)
114
- @runtime.start_at_csv!
115
- @runtime.map_rows(template.rows, cells_too: true) do |cell|
116
- cell.ast = @scope.resolve_cell_value if cell.ast
130
+ @runtime.start_at_csv! do
131
+ @runtime.position.map_all_cells(template.rows) do |cell|
132
+ cell.ast = @runtime.resolve_cell_value(::T.must(cell.ast)) if cell.ast
133
+ end
117
134
  end
118
135
  end
119
136
 
137
+ sig { params(block: ::T.proc.void).void }
120
138
  # Expanding rows
121
- def expanding
122
- @runtime.start_at_csv!
123
- yield
139
+ def expanding!(&block)
140
+ @runtime.start_at_csv! { block.call }
141
+ end
142
+
143
+ sig { params(block: ::T.proc.void).void }
144
+ # Binding all [[var=]] directives
145
+ def bind_all_vars!(&block)
146
+ @runtime.start_at_csv! { block.call }
124
147
  end
125
148
 
126
149
  private
127
150
 
128
- def parsing_code_section
129
- csv_section = yield(@runtime.input.read)
130
- @runtime.rewrite_input!(csv_section)
151
+ sig { params(block: ::T.proc.params(arg0: ::String).returns(::String)).void }
152
+ def parsing_code_section(&block)
153
+ csv_section = block.call(::T.must(::T.must(@runtime.position.input).read))
154
+ @runtime.position.rewrite_input!(csv_section)
131
155
  end
132
156
 
157
+ sig { params(csv_row: ::T::Array[::T.nilable(::String)]).returns(::CSVPlusPlus::Row) }
133
158
  # Using the current +@runtime+ and the given +csv_row+ parse it into a +Row+ of +Cell+s
134
159
  # +csv_row+ should have already been run through a CSV parser and is an array of strings
135
160
  #
136
161
  # @param csv_row [Array<Array<String>>]
162
+ #
137
163
  # @return [Row]
138
164
  def parse_row(csv_row)
139
- row_modifier = ::CSVPlusPlus::ValidatedModifier.new(row_level: true)
165
+ row_modifier = ::CSVPlusPlus::Modifier.new(@options, row_level: true)
140
166
 
141
- cells = @runtime.map_row(csv_row) { |value, _cell_index| parse_cell(value, row_modifier) }
167
+ cells = @runtime.position.map_row(csv_row) { |value, _cell_index| parse_cell(value || '', row_modifier) }
142
168
 
143
- ::CSVPlusPlus::Row.new(@runtime.row_index, cells, row_modifier)
169
+ ::CSVPlusPlus::Row.new(cells:, index: @runtime.position.row_index, modifier: row_modifier)
144
170
  end
145
171
 
172
+ sig { params(value: ::String, row_modifier: ::CSVPlusPlus::Modifier::Modifier).returns(::CSVPlusPlus::Cell) }
146
173
  def parse_cell(value, row_modifier)
147
- cell_modifier = ::CSVPlusPlus::ValidatedModifier.new
148
- parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:, scope: @scope).parse(
149
- value,
150
- @runtime
151
- )
152
-
153
- ::CSVPlusPlus::Cell.parse(parsed_value, runtime:, modifier: cell_modifier)
174
+ cell_modifier = ::CSVPlusPlus::Modifier.new(@options)
175
+ parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:).parse(value)
176
+
177
+ ::CSVPlusPlus::Cell.new(
178
+ value: parsed_value,
179
+ row_index: @runtime.position.row_index,
180
+ index: @runtime.position.cell_index,
181
+ modifier: cell_modifier
182
+ ).tap do |c|
183
+ c.ast = ::CSVPlusPlus::Parser::CellValue.new.parse(parsed_value) unless parsed_value.nil?
184
+ end
154
185
  end
155
186
  end
187
+ # rubocop:enable Metrics/ClassLength
156
188
  end
@@ -1,65 +1,56 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Entities
5
6
  # Some helpful functions that can be mixed into a class to help building ASTs
6
7
  module ASTBuilder
8
+ extend ::T::Sig
9
+
10
+ sig do
11
+ params(
12
+ method_name: ::Symbol,
13
+ args: ::T.untyped,
14
+ kwargs: ::T.untyped,
15
+ block: ::T.untyped
16
+ ).returns(::CSVPlusPlus::Entities::Entity)
17
+ end
7
18
  # Let the current class have functions which can build a given entity by calling it's type. For example
8
19
  # +number(1)+, +variable(:foo)+
9
20
  #
10
21
  # @param method_name [Symbol] The +method_name+ to respond to
11
- # @param arguments [] The arguments to create the entity with
22
+ # @param args [Array] The arguments to create the entity with
23
+ # @param kwargs [Hash] The arguments to create the entity with
12
24
  #
13
25
  # @return [Entity, #super]
14
- def method_missing(method_name, *args, **kwargs, &)
15
- entity_class = ::CSVPlusPlus::Entities::TYPES[method_name.to_sym]
16
- return super unless entity_class
17
-
18
- entity_class.new(*args, **kwargs, &)
26
+ # rubocop:disable Naming/BlockForwarding
27
+ def method_missing(method_name, *args, **kwargs, &block)
28
+ ::CSVPlusPlus::Entities.const_get(snake_case_to_class_name(method_name)).new(*args, **kwargs, &block)
29
+ rescue ::NameError
30
+ super
19
31
  end
32
+ # rubocop:enable Naming/BlockForwarding
20
33
 
34
+ sig { params(method_name: ::Symbol, _args: ::T.untyped).returns(::T::Boolean) }
21
35
  # Let the current class have functions which can build a given entity by calling it's type. For example
22
36
  # +number(1)+, +variable(:foo)+
23
37
  #
24
38
  # @param method_name [Symbol] The +method_name+ to respond to
25
- # @param arguments [] The arguments to create the entity with
26
- #
27
- # @return [Boolean, #super]
28
- def respond_to_missing?(method_name, *_arguments)
29
- ::CSVPlusPlus::Entities::TYPES.include?(method_name.to_sym) || super
30
- end
31
-
32
- # Turns index-based/X,Y coordinates into a A1 format
39
+ # @param _args [::T.Untyped] The arguments to create the entity with
33
40
  #
34
- # @param row_index [Integer]
35
- # @param cell_index [Integer]
36
- #
37
- # @return [String]
38
- def ref(row_index: nil, cell_index: nil)
39
- return unless row_index || cell_index
40
-
41
- rowref = row_index ? (row_index + 1).to_s : ''
42
- cellref = cell_index ? cell_ref(cell_index) : ''
43
- cell_reference([cellref, rowref].join)
41
+ # @return [::T::Boolean, #super]
42
+ def respond_to_missing?(method_name, *_args)
43
+ ::CSVPlusPlus::Entities.const_get(snake_case_to_class_name(method_name))
44
+ true
45
+ rescue ::NameError
46
+ super
44
47
  end
45
48
 
46
49
  private
47
50
 
48
- ALPHA = ('A'..'Z').to_a.freeze
49
- private_constant :ALPHA
50
-
51
- def cell_ref(cell_index)
52
- c = cell_index.dup
53
- ref = ''
54
-
55
- while c >= 0
56
- # rubocop:disable Lint/ConstantResolution
57
- ref += ALPHA[c % 26]
58
- # rubocop:enable Lint/ConstantResolution
59
- c = (c / 26).floor - 1
60
- end
61
-
62
- ref.reverse
51
+ sig { params(method_name: ::Symbol).returns(::Symbol) }
52
+ def snake_case_to_class_name(method_name)
53
+ method_name.to_s.split('_').map(&:capitalize).join.to_sym
63
54
  end
64
55
  end
65
56
  end
@@ -1,30 +1,46 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './entity'
4
-
5
4
  module CSVPlusPlus
6
5
  module Entities
7
6
  # A boolean value
8
7
  #
9
8
  # @attr_reader value [true, false]
10
- class Boolean < Entity
9
+ class Boolean < ::CSVPlusPlus::Entities::Entity
10
+ extend ::T::Sig
11
+
12
+ sig { returns(::T::Boolean) }
11
13
  attr_reader :value
12
14
 
13
- # @param value [String, Boolean]
15
+ sig { params(value: ::T.any(::String, ::T::Boolean)).void }
16
+ # @param value [::String, boolean]
14
17
  def initialize(value)
15
- super(:boolean)
18
+ super()
16
19
  # TODO: probably can do a lot better in general on type validation
17
- @value = value.is_a?(::String) ? (value.downcase == 'true') : value
20
+ @value = ::T.let(value.is_a?(::String) ? (value.downcase == 'true') : value, ::T::Boolean)
18
21
  end
19
22
 
20
- # @return [String]
21
- def to_s
23
+ sig do
24
+ override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String)
25
+ end
26
+ # @param _position [Position]
27
+ #
28
+ # @return [::String]
29
+ def evaluate(_position)
22
30
  @value.to_s.upcase
23
31
  end
24
32
 
25
- # @return [boolean]
33
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
34
+ # @param other [Entity]
35
+ #
36
+ # @return [::T::Boolean]
26
37
  def ==(other)
27
- super && value == other.value
38
+ case other
39
+ when self.class
40
+ value == other.value
41
+ else
42
+ false
43
+ end
28
44
  end
29
45
  end
30
46
  end