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.
- checksums.yaml +4 -4
- data/README.md +18 -63
- data/{CHANGELOG.md → docs/CHANGELOG.md} +17 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +112 -0
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +44 -17
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +42 -11
- data/lib/csv_plus_plus/compiler.rb +178 -0
- data/lib/csv_plus_plus/entities/ast_builder.rb +50 -0
- data/lib/csv_plus_plus/entities/boolean.rb +40 -0
- data/lib/csv_plus_plus/entities/builtins.rb +58 -0
- data/lib/csv_plus_plus/entities/cell_reference.rb +231 -0
- data/lib/csv_plus_plus/entities/date.rb +63 -0
- data/lib/csv_plus_plus/entities/entity.rb +50 -0
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +45 -0
- data/lib/csv_plus_plus/entities/function_call.rb +50 -0
- data/lib/csv_plus_plus/entities/number.rb +48 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +43 -0
- data/lib/csv_plus_plus/entities/string.rb +42 -0
- data/lib/csv_plus_plus/entities/variable.rb +37 -0
- data/lib/csv_plus_plus/entities.rb +40 -0
- data/lib/csv_plus_plus/error/error.rb +20 -0
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +37 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +75 -0
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +69 -0
- data/lib/csv_plus_plus/error/syntax_error.rb +71 -0
- data/lib/csv_plus_plus/error/writer_error.rb +17 -0
- data/lib/csv_plus_plus/error.rb +10 -2
- data/lib/csv_plus_plus/google_api_client.rb +11 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +17 -6
- data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
- data/lib/csv_plus_plus/lexer.rb +24 -0
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +18 -0
- data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
- data/lib/csv_plus_plus/modifier/expand.rb +61 -0
- data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
- data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
- data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
- data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
- data/lib/csv_plus_plus/modifier.rb +82 -150
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/{language → parser}/cell_value.tab.rb +25 -25
- data/lib/csv_plus_plus/{language → parser}/code_section.tab.rb +86 -95
- data/lib/csv_plus_plus/parser/modifier.tab.rb +478 -0
- data/lib/csv_plus_plus/row.rb +53 -15
- data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
- data/lib/csv_plus_plus/runtime/references.rb +110 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
- data/lib/csv_plus_plus/runtime.rb +42 -0
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +63 -36
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
- data/lib/csv_plus_plus/writer/csv.rb +11 -9
- data/lib/csv_plus_plus/writer/excel.rb +9 -2
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +7 -4
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +88 -45
- data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
- data/lib/csv_plus_plus/writer/open_document.rb +6 -1
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -33
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +41 -15
- metadata +44 -30
- data/lib/csv_plus_plus/code_section.rb +0 -101
- data/lib/csv_plus_plus/expand.rb +0 -18
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/language/ast_builder.rb +0 -68
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
- data/lib/csv_plus_plus/language/builtins.rb +0 -46
- data/lib/csv_plus_plus/language/compiler.rb +0 -152
- data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
- data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
- data/lib/csv_plus_plus/language/entities/function.rb +0 -35
- data/lib/csv_plus_plus/language/entities/function_call.rb +0 -37
- data/lib/csv_plus_plus/language/entities/number.rb +0 -36
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
- data/lib/csv_plus_plus/language/entities/string.rb +0 -31
- data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/language/entities.rb +0 -28
- data/lib/csv_plus_plus/language/references.rb +0 -70
- data/lib/csv_plus_plus/language/runtime.rb +0 -205
- data/lib/csv_plus_plus/language/scope.rb +0 -192
- data/lib/csv_plus_plus/language/syntax_error.rb +0 -66
- data/lib/csv_plus_plus/modifier.tab.rb +0 -907
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -56
- 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
|
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
|
-
|
10
|
+
extend ::T::Sig
|
10
11
|
|
11
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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.
|
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
|
-
|
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
|
-
#{@
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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,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
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
31
|
+
@runtime = runtime
|
15
32
|
end
|
16
33
|
|
17
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
|
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(
|
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)
|