csv_plus_plus 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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