csv_plus_plus 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -63
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +17 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +112 -0
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +44 -17
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +42 -11
  9. data/lib/csv_plus_plus/compiler.rb +178 -0
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +50 -0
  11. data/lib/csv_plus_plus/entities/boolean.rb +40 -0
  12. data/lib/csv_plus_plus/entities/builtins.rb +58 -0
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +231 -0
  14. data/lib/csv_plus_plus/entities/date.rb +63 -0
  15. data/lib/csv_plus_plus/entities/entity.rb +50 -0
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +45 -0
  18. data/lib/csv_plus_plus/entities/function_call.rb +50 -0
  19. data/lib/csv_plus_plus/entities/number.rb +48 -0
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -0
  21. data/lib/csv_plus_plus/entities/string.rb +42 -0
  22. data/lib/csv_plus_plus/entities/variable.rb +37 -0
  23. data/lib/csv_plus_plus/entities.rb +40 -0
  24. data/lib/csv_plus_plus/error/error.rb +20 -0
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +37 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +75 -0
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +69 -0
  28. data/lib/csv_plus_plus/error/syntax_error.rb +71 -0
  29. data/lib/csv_plus_plus/error/writer_error.rb +17 -0
  30. data/lib/csv_plus_plus/error.rb +10 -2
  31. data/lib/csv_plus_plus/google_api_client.rb +11 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +17 -6
  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 +18 -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 -150
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/{language → parser}/cell_value.tab.rb +25 -25
  46. data/lib/csv_plus_plus/{language → parser}/code_section.tab.rb +86 -95
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +478 -0
  48. data/lib/csv_plus_plus/row.rb +53 -15
  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 +42 -0
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +63 -36
  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 +7 -4
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +88 -45
  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 -33
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +41 -15
  69. metadata +44 -30
  70. data/lib/csv_plus_plus/code_section.rb +0 -101
  71. data/lib/csv_plus_plus/expand.rb +0 -18
  72. data/lib/csv_plus_plus/graph.rb +0 -62
  73. data/lib/csv_plus_plus/language/ast_builder.rb +0 -68
  74. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
  75. data/lib/csv_plus_plus/language/builtins.rb +0 -46
  76. data/lib/csv_plus_plus/language/compiler.rb +0 -152
  77. data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
  78. data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
  79. data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
  80. data/lib/csv_plus_plus/language/entities/function.rb +0 -35
  81. data/lib/csv_plus_plus/language/entities/function_call.rb +0 -37
  82. data/lib/csv_plus_plus/language/entities/number.rb +0 -36
  83. data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
  84. data/lib/csv_plus_plus/language/entities/string.rb +0 -31
  85. data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/language/entities.rb +0 -28
  87. data/lib/csv_plus_plus/language/references.rb +0 -70
  88. data/lib/csv_plus_plus/language/runtime.rb +0 -205
  89. data/lib/csv_plus_plus/language/scope.rb +0 -192
  90. data/lib/csv_plus_plus/language/syntax_error.rb +0 -66
  91. data/lib/csv_plus_plus/modifier.tab.rb +0 -907
  92. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -56
  93. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -0,0 +1,126 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Runtime
6
+ # The runtime state of the compiler (the current +line_number+/+row_index+, +cell+ being processed, etc) for parsing
7
+ # a given file. We take multiple runs through the input file for parsing so it's really convenient to have a
8
+ # central place for these things to be managed.
9
+ #
10
+ # @attr_reader filename [String, nil] The filename that the input came from (mostly used for debugging since
11
+ # +filename+ can be +nil+ if it's read from stdin.
12
+ #
13
+ # @attr cell [Cell] The current cell being processed
14
+ # @attr cell_index [Integer] The index of the current cell being processed (starts at 0)
15
+ # @attr row_index [Integer] The index of the current row being processed (starts at 0)
16
+ # @attr line_number [Integer] The line number of the original csvpp template (starts at 1)
17
+ class Runtime
18
+ extend ::T::Sig
19
+
20
+ include ::CSVPlusPlus::Runtime::CanDefineReferences
21
+ include ::CSVPlusPlus::Runtime::CanResolveReferences
22
+ include ::CSVPlusPlus::Runtime::PositionTracker
23
+
24
+ sig { returns(::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Function]) }
25
+ attr_reader :functions
26
+
27
+ sig { returns(::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Entity]) }
28
+ attr_reader :variables
29
+
30
+ sig { returns(::CSVPlusPlus::SourceCode) }
31
+ attr_reader :source_code
32
+
33
+ sig do
34
+ params(
35
+ source_code: ::CSVPlusPlus::SourceCode,
36
+ functions: ::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Function],
37
+ variables: ::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Entity]
38
+ ).void
39
+ end
40
+ # @param source_code [SourceCode] The source code being compiled
41
+ # @param functions [Hash<Symbol, Function>] Pre-defined functions
42
+ # @param variables [Hash<Symbol, Entity>] Pre-defined variables
43
+ def initialize(source_code:, functions: {}, variables: {})
44
+ @functions = functions
45
+ @variables = variables
46
+ @source_code = source_code
47
+
48
+ rewrite_input!(source_code.input)
49
+ end
50
+
51
+ sig { params(fn_id: ::Symbol).returns(::T::Boolean) }
52
+ # Is +fn_id+ a builtin function?
53
+ #
54
+ # @param fn_id [Symbol] The Function#id to check if it's a runtime variable
55
+ #
56
+ # @return [T::Boolean]
57
+ def builtin_function?(fn_id)
58
+ ::CSVPlusPlus::Entities::Builtins::FUNCTIONS.key?(fn_id)
59
+ end
60
+
61
+ sig { params(var_id: ::Symbol).returns(::T::Boolean) }
62
+ # Is +var_id+ a builtin variable?
63
+ #
64
+ # @param var_id [Symbol] The Variable#id to check if it's a runtime variable
65
+ #
66
+ # @return [T::Boolean]
67
+ def builtin_variable?(var_id)
68
+ ::CSVPlusPlus::Entities::Builtins::VARIABLES.key?(var_id)
69
+ end
70
+
71
+ sig { returns(::T::Boolean) }
72
+ # Is the parser currently inside of the code section? (includes the `---`)
73
+ #
74
+ # @return [T::Boolean]
75
+ def parsing_code_section?
76
+ source_code.in_code_section?(line_number)
77
+ end
78
+
79
+ sig { returns(::T::Boolean) }
80
+ # Is the parser currently inside of the CSV section?
81
+ #
82
+ # @return [T::Boolean]
83
+ def parsing_csv_section?
84
+ source_code.in_csv_section?(line_number)
85
+ end
86
+
87
+ sig do
88
+ params(message: ::String, bad_input: ::String, wrapped_error: ::T.nilable(::StandardError))
89
+ .returns(::T.noreturn)
90
+ end
91
+ # Called when an error is encoutered during parsing formulas (whether in the code section or a cell). It will
92
+ # construct a useful error with the current +@row/@cell_index+, +@line_number+ and +@filename+
93
+ #
94
+ # @param message [::String] A message relevant to why this error is being raised.
95
+ # @param bad_input [::String] The offending input that caused this error to be thrown.
96
+ # @param wrapped_error [StandardError, nil] The underlying error that was raised (if it's not from our own logic)
97
+ def raise_formula_syntax_error(message, bad_input, wrapped_error: nil)
98
+ raise(::CSVPlusPlus::Error::FormulaSyntaxError.new(message, bad_input, self, wrapped_error:))
99
+ end
100
+
101
+ sig do
102
+ params(message: ::String, bad_input: ::String, wrapped_error: ::T.nilable(::StandardError))
103
+ .returns(::T.noreturn)
104
+ end
105
+ # Called when an error is encountered while parsing a modifier.
106
+ #
107
+ # @param message [::String] A message relevant to why this error is being raised.
108
+ # @param bad_input [::String] The offending input that caused this error to be thrown.
109
+ # @param wrapped_error [StandardError, nil] The underlying error that was raised (if it's not from our own logic)
110
+ def raise_modifier_syntax_error(message, bad_input, wrapped_error: nil)
111
+ raise(::CSVPlusPlus::Error::ModifierSyntaxError.new(self, bad_input:, message:, wrapped_error:))
112
+ end
113
+
114
+ sig do
115
+ type_parameters(:R).params(block: ::T.proc.returns(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
116
+ end
117
+ # Reset the runtime state starting at the CSV section
118
+ # rubocop:disable Naming/BlockForwarding
119
+ def start_at_csv!(&block)
120
+ self.line_number = source_code.length_of_code_section + 1
121
+ start!(&block)
122
+ end
123
+ # rubocop:enable Naming/BlockForwarding
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative './runtime/can_define_references'
5
+ require_relative './runtime/can_resolve_references'
6
+ require_relative './runtime/graph'
7
+ require_relative './runtime/position_tracker'
8
+ require_relative './runtime/references'
9
+ require_relative './runtime/runtime'
10
+
11
+ module CSVPlusPlus
12
+ # All functionality needed to keep track of the runtime AKA execution context. This module has a lot of
13
+ # reponsibilities:
14
+ #
15
+ # - variables and function resolution and scoping
16
+ # - variable & function definitions
17
+ # - keeping track of the runtime state (the current cell being processed)
18
+ # - rewriting the input file that's being parsed
19
+ #
20
+ module Runtime
21
+ extend ::T::Sig
22
+
23
+ sig do
24
+ params(
25
+ source_code: ::CSVPlusPlus::SourceCode,
26
+ functions: ::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Function],
27
+ variables: ::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Entity]
28
+ ).returns(::CSVPlusPlus::Runtime::Runtime)
29
+ end
30
+ # Initialize a runtime instance with all the functionality we need. A runtime is one-to-one with a file being
31
+ # compiled.
32
+ #
33
+ # @param source_code [SourceCode] The csv++ source code to be compiled
34
+ # @param functions [Hash<Symbol, Function>] Pre-defined functions
35
+ # @param variables [Hash<Symbol, Entity>] Pre-defined variables
36
+ #
37
+ # @return [Runtime::Runtime]
38
+ def self.new(source_code:, functions: {}, variables: {})
39
+ ::CSVPlusPlus::Runtime::Runtime.new(source_code:, functions:, variables:)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ # Information about the unparsed source code
6
+ class SourceCode
7
+ extend ::T::Sig
8
+
9
+ sig { returns(::String) }
10
+ attr_reader :input
11
+
12
+ sig { returns(::String) }
13
+ attr_reader :filename
14
+
15
+ sig { returns(::Integer) }
16
+ attr_reader :length_of_csv_section
17
+
18
+ sig { returns(::Integer) }
19
+ attr_reader :length_of_code_section
20
+
21
+ sig { returns(::Integer) }
22
+ attr_reader :length_of_file
23
+
24
+ sig { params(input: ::String, filename: ::T.nilable(::String)).void }
25
+ # @param input [::String] The source code being parsed
26
+ # @param filename [::String, nil] The name of the file the source came from. If not set we assume it came
27
+ # from stdin
28
+ def initialize(input:, filename: nil)
29
+ @input = input
30
+ @filename = ::T.let(filename || 'stdin', ::String)
31
+
32
+ lines = input.split(/[\r\n]/)
33
+ @length_of_file = ::T.let(lines.length, ::Integer)
34
+ @length_of_code_section = ::T.let(count_code_section_lines(lines), ::Integer)
35
+ @length_of_csv_section = ::T.let(@length_of_file - @length_of_code_section, ::Integer)
36
+ end
37
+
38
+ sig { params(line_number: ::Integer).returns(::T::Boolean) }
39
+ # Does the given +line_number+ land in the code section of the file? (which includes the --- separator)
40
+ #
41
+ # @param line_number [Integer]
42
+ #
43
+ # @return [T::Boolean]
44
+ def in_code_section?(line_number)
45
+ line_number <= @length_of_code_section
46
+ end
47
+
48
+ sig { params(line_number: ::Integer).returns(::T::Boolean) }
49
+ # Does the given +line_number+ land in the CSV section of the file?
50
+ #
51
+ # @param line_number [Integer]
52
+ #
53
+ # @return [T::Boolean]
54
+ def in_csv_section?(line_number)
55
+ line_number > @length_of_code_section
56
+ end
57
+
58
+ private
59
+
60
+ sig { params(lines: ::T::Array[::String]).returns(::Integer) }
61
+ def count_code_section_lines(lines)
62
+ eoc = ::CSVPlusPlus::Lexer::END_OF_CODE_SECTION
63
+ lines.include?(eoc) ? (lines.take_while { |l| l != eoc }).length + 1 : 0
64
+ end
65
+ end
66
+ end
@@ -1,42 +1,70 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
- # Contains the flow and data from a code section and CSV section
5
+ # Contains the data from a parsed csvpp template.
5
6
  #
6
- # @attr_reader code_section [CodeSection] The +CodeSection+ containing the functions and variables defined herein
7
7
  # @attr_reader rows [Array<Row>] The +Row+s that comprise this +Template+
8
+ # @attr_reader runtime [Runtime] The +Runtime+ containing all function and variable references
8
9
  class Template
9
- attr_reader :code_section, :rows
10
+ extend ::T::Sig
10
11
 
11
- # @param code_section [CodeSection] The +CodeSection+ containing the functions and variables
12
+ sig { returns(::T::Array[::CSVPlusPlus::Row]) }
13
+ attr_reader :rows
14
+
15
+ sig { returns(::CSVPlusPlus::Runtime::Runtime) }
16
+ attr_reader :runtime
17
+
18
+ sig { params(rows: ::T::Array[::CSVPlusPlus::Row], runtime: ::CSVPlusPlus::Runtime::Runtime).void }
12
19
  # @param rows [Array<Row>] The +Row+s that comprise this +Template+
13
- def initialize(code_section:, rows:)
14
- @code_section = code_section
20
+ # @param runtime [Runtime] The +Runtime+ containing all function and variable references
21
+ def initialize(rows:, runtime:)
15
22
  @rows = rows
23
+ @runtime = runtime
16
24
  end
17
25
 
18
- # @return [String]
19
- def to_s
20
- "Template(rows: #{@rows})"
26
+ sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).void }
27
+ # Only run after expanding all rows, now we can bind all [[var=]] modifiers to a variable. There are two distinct
28
+ # types of variable bindings here:
29
+ #
30
+ # * Binding to a cell: for this we just make a +CellReference+ to the cell itself (A1, B4, etc)
31
+ # * Binding to a cell within an expand: the variable can only be resolved within that expand and needs to be
32
+ # relative to it's row (it can't be an absolute cell reference like above)
33
+ #
34
+ # @param runtime [Runtime] The current runtime
35
+ def bind_all_vars!(runtime)
36
+ runtime.map_rows(@rows) do |row|
37
+ # rubocop:disable Style/MissingElse
38
+ if row.unexpanded?
39
+ # rubocop:enable Style/MissingElse
40
+ raise(::CSVPlusPlus::Error::Error, 'Template#expand_rows! must be called before Template#bind_all_vars!')
41
+ end
42
+
43
+ runtime.map_row(row.cells) do |cell|
44
+ bind_vars(cell, row.modifier.expand)
45
+ end
46
+ end
21
47
  end
22
48
 
23
- # Apply any expand= modifiers to the parsed template
49
+ sig { returns(::T::Array[::CSVPlusPlus::Row]) }
50
+ # Apply expand= (adding rows to the results) modifiers to the parsed template. This happens in towards the end of
51
+ # compilation because expanding rows will change the relative rownums as rows are added, and variables can't be
52
+ # bound until the rows have been assigned their final rownums.
24
53
  #
25
54
  # @return [Array<Row>]
26
55
  def expand_rows!
27
- expanded_rows = []
28
- row_index = 0
29
- expand_rows(
30
- lambda do |new_row|
31
- new_row.index = row_index
32
- expanded_rows << new_row
33
- row_index += 1
56
+ # TODO: make it so that an infinite expand will not overwrite the rows below it, but instead merge with them
57
+ @rows =
58
+ rows.reduce([]) do |expanded_rows, row|
59
+ if row.modifier.expand
60
+ row.expand_rows(starts_at: expanded_rows.length, into: expanded_rows)
61
+ else
62
+ expanded_rows << row.tap { |r| r.index = expanded_rows.length }
63
+ end
34
64
  end
35
- )
36
-
37
- @rows = expanded_rows
38
65
  end
39
66
 
67
+ sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).void }
40
68
  # Make sure that the template has a valid amount of infinite expand modifiers
41
69
  #
42
70
  # @param runtime [Runtime] The compiler's current runtime
@@ -44,19 +72,20 @@ module CSVPlusPlus
44
72
  infinite_expand_rows = @rows.filter { |r| r.modifier.expand&.infinite? }
45
73
  return unless infinite_expand_rows.length > 1
46
74
 
47
- runtime.raise_syntax_error(
75
+ runtime.raise_modifier_syntax_error(
48
76
  'You can only have one infinite expand= (on all others you must specify an amount)',
49
- infinite_expand_rows[1]
77
+ infinite_expand_rows[1].to_s
50
78
  )
51
79
  end
52
80
 
53
- # Provide a summary of the state of the template (and it's +@code_section+)
81
+ sig { returns(::String) }
82
+ # Provide a summary of the state of the template (and it's +@runtime+)
54
83
  #
55
- # @return [String]
84
+ # @return [::String]
56
85
  def verbose_summary
57
86
  # TODO: we can probably include way more stats in here
58
87
  <<~SUMMARY
59
- #{@code_section.verbose_summary}
88
+ #{@runtime.verbose_summary}
60
89
 
61
90
  > #{@rows.length} rows to be written
62
91
  SUMMARY
@@ -64,17 +93,15 @@ module CSVPlusPlus
64
93
 
65
94
  private
66
95
 
67
- def expand_rows(push_row_fn)
68
- # TODO: make it so that an infinite expand will not overwrite the rows below it, but
69
- # instead merge with them
70
- rows.each do |row|
71
- if row.modifier.expand
72
- row.expand_amount.times do
73
- push_row_fn.call(row.deep_clone)
74
- end
75
- else
76
- push_row_fn.call(row)
77
- end
96
+ sig { params(cell: ::CSVPlusPlus::Cell, expand: ::T.nilable(::CSVPlusPlus::Modifier::Expand)).void }
97
+ def bind_vars(cell, expand)
98
+ var = cell.modifier.var
99
+ return unless var
100
+
101
+ if expand
102
+ @runtime.bind_variable_in_expand(var, expand)
103
+ else
104
+ @runtime.bind_variable_to_cell(var)
78
105
  end
79
106
  end
80
107
  end
@@ -1,6 +1,7 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
- VERSION = '0.1.1'
5
+ VERSION = '0.1.3'
5
6
  public_constant :VERSION
6
7
  end
@@ -1,20 +1,45 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Writer
5
6
  # Some shared functionality that all Writers should build on
7
+ #
8
+ # @attr_reader options [Options] The supplied options - some of which are relevant for our writer instance
9
+ # @attr_reader runtime [Runtime] The current runtime - needed to resolve variables and display useful error messages
6
10
  class BaseWriter
7
- attr_accessor :options
11
+ extend ::T::Sig
12
+ extend ::T::Helpers
13
+
14
+ abstract!
15
+
16
+ sig { returns(::CSVPlusPlus::Options) }
17
+ attr_reader :options
18
+
19
+ sig { returns(::CSVPlusPlus::Runtime::Runtime) }
20
+ attr_reader :runtime
8
21
 
9
22
  protected
10
23
 
11
- # Open a CSV outputter to +filename+
12
- def initialize(options)
24
+ sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
25
+ # Open a CSV outputter to the +output_filename+ specified by the +Options+
26
+ #
27
+ # @param options [Options] The supplied options.
28
+ # @param runtime [Runtime] The current runtime.
29
+ def initialize(options, runtime)
13
30
  @options = options
14
- load_requires
31
+ @runtime = runtime
15
32
  end
16
33
 
17
- def load_requires; end
34
+ sig { abstract.params(template: ::CSVPlusPlus::Template).void }
35
+ # Write the given +template+.
36
+ #
37
+ # @param template [Template]
38
+ def write(template); end
39
+
40
+ sig { abstract.void }
41
+ # Write a backup of the current spreadsheet.
42
+ def write_backup; end
18
43
  end
19
44
  end
20
45
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './file_backer_upper'
@@ -6,28 +7,29 @@ module CSVPlusPlus
6
7
  module Writer
7
8
  # A class that can output a +Template+ to CSV
8
9
  class CSV < ::CSVPlusPlus::Writer::BaseWriter
10
+ extend ::T::Sig
11
+
9
12
  include ::CSVPlusPlus::Writer::FileBackerUpper
10
13
 
11
- # write a +template+ to CSV
14
+ sig { override.params(template: ::CSVPlusPlus::Template).void }
15
+ # Write a +template+ to CSV
16
+ #
17
+ # @param template [Template] The template to use as input to be written. It should have been compiled by calling
18
+ # Compiler#compile_template
12
19
  def write(template)
13
20
  # TODO: also read it and merge the results
14
21
  ::CSV.open(@options.output_filename, 'wb') do |csv|
15
- template.rows.each do |row|
22
+ @runtime.map_rows(template.rows) do |row|
16
23
  csv << build_row(row)
17
24
  end
18
25
  end
19
26
  end
20
27
 
21
- protected
22
-
23
- def load_requires
24
- require('csv')
25
- end
26
-
27
28
  private
28
29
 
30
+ sig { params(row: ::CSVPlusPlus::Row).returns(::T::Array[::T.nilable(::String)]) }
29
31
  def build_row(row)
30
- row.cells.map(&:to_csv)
32
+ @runtime.map_row(row.cells) { |cell, _i| cell.evaluate(@runtime) }
31
33
  end
32
34
  end
33
35
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require_relative './file_backer_upper'
@@ -7,13 +8,19 @@ module CSVPlusPlus
7
8
  module Writer
8
9
  # A class that can output a +Template+ to an Excel file
9
10
  class Excel < ::CSVPlusPlus::Writer::BaseWriter
11
+ extend ::T::Sig
12
+
10
13
  include ::CSVPlusPlus::Writer::FileBackerUpper
11
14
 
12
- # write the +template+ to an Excel file
15
+ sig { override.params(template: ::CSVPlusPlus::Template).void }
16
+ # Write the +template+ to an Excel file
17
+ #
18
+ # @param template [Template] The template to write
13
19
  def write(template)
14
20
  ::CSVPlusPlus::Writer::RubyXLBuilder.new(
15
- input_filename: @options.output_filename,
21
+ input_filename: ::T.must(@options.output_filename),
16
22
  rows: template.rows,
23
+ runtime: @runtime,
17
24
  sheet_name: @options.sheet_name
18
25
  ).build_workbook.write(@options.output_filename)
19
26
  end
@@ -1,8 +1,6 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
- require 'fileutils'
4
- require 'pathname'
5
-
6
4
  module CSVPlusPlus
7
5
  module Writer
8
6
  # A module that can be mixed into any Writer that needs to back up it's @output_filename (all of them except Google
@@ -26,6 +24,7 @@ module CSVPlusPlus
26
24
 
27
25
  private
28
26
 
27
+ # rubocop:disable Metrics/MethodLength
29
28
  def attempt_backups
30
29
  attempted =
31
30
  # rubocop:disable Lint/ConstantResolution
@@ -39,8 +38,12 @@ module CSVPlusPlus
39
38
  return backed_up_to
40
39
  end
41
40
 
42
- raise(::CSVPlusPlus::Error, "Unable to write backup file despite trying these: #{attempted.join(', ')}")
41
+ raise(
42
+ ::CSVPlusPlus::Error::WriterError,
43
+ "Unable to write backup file despite trying these: #{attempted.join(', ')}"
44
+ )
43
45
  end
46
+ # rubocop:enable Metrics/MethodLength
44
47
 
45
48
  def backup(filename)
46
49
  return if ::File.exist?(filename)