csv_plus_plus 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +23 -13
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +82 -60
- data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
- data/lib/csv_plus_plus/entities/boolean.rb +18 -9
- data/lib/csv_plus_plus/entities/builtins.rb +23 -9
- data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
- data/lib/csv_plus_plus/entities/date.rb +38 -5
- data/lib/csv_plus_plus/entities/entity.rb +27 -61
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +23 -11
- data/lib/csv_plus_plus/entities/function_call.rb +24 -9
- data/lib/csv_plus_plus/entities/number.rb +24 -10
- data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
- data/lib/csv_plus_plus/entities/string.rb +19 -6
- data/lib/csv_plus_plus/entities/variable.rb +16 -4
- data/lib/csv_plus_plus/entities.rb +20 -13
- data/lib/csv_plus_plus/error/error.rb +11 -1
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
- data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +1 -0
- data/lib/csv_plus_plus/google_api_client.rb +7 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
- 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 +1 -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 -158
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
- data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
- data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
- data/lib/csv_plus_plus/row.rb +53 -12
- 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 +34 -191
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +62 -35
- 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 +1 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
- 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 -30
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +29 -12
- metadata +18 -14
- data/lib/csv_plus_plus/can_define_references.rb +0 -88
- data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
- data/lib/csv_plus_plus/data_validation.rb +0 -138
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/references.rb +0 -68
- data/lib/csv_plus_plus/scope.rb +0 -196
- data/lib/csv_plus_plus/validated_modifier.rb +0 -164
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
data/lib/csv_plus_plus/scope.rb
DELETED
@@ -1,196 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'can_define_references'
|
4
|
-
require_relative 'entities'
|
5
|
-
require_relative 'graph'
|
6
|
-
require_relative 'references'
|
7
|
-
|
8
|
-
module CSVPlusPlus
|
9
|
-
# A class representing the scope of the current Template and responsible for resolving variables
|
10
|
-
#
|
11
|
-
# @attr_reader functions [Hash<Symbol, Function>] The currently functions defined
|
12
|
-
# @attr_reader runtime [Runtime] The compiler's current runtime
|
13
|
-
# @attr_reader variables [Hash<Symbol, Entity>] The currently defined variables
|
14
|
-
#
|
15
|
-
# rubocop:disable Metrics/ClassLength
|
16
|
-
class Scope
|
17
|
-
include ::CSVPlusPlus::CanDefineReferences
|
18
|
-
# TODO: split out a CanResolveReferences
|
19
|
-
|
20
|
-
attr_reader :functions, :runtime, :variables
|
21
|
-
|
22
|
-
# @param runtime [Runtime]
|
23
|
-
def initialize(runtime:, functions: {}, variables: {})
|
24
|
-
@runtime = runtime
|
25
|
-
@functions = functions
|
26
|
-
@variables = variables
|
27
|
-
end
|
28
|
-
|
29
|
-
# Resolve all values in the ast of the current cell being processed
|
30
|
-
#
|
31
|
-
# @return [Entity]
|
32
|
-
def resolve_cell_value
|
33
|
-
return unless (ast = @runtime.cell&.ast)
|
34
|
-
|
35
|
-
last_round = nil
|
36
|
-
loop do
|
37
|
-
refs = ::CSVPlusPlus::References.extract(ast, self)
|
38
|
-
return ast if refs.empty?
|
39
|
-
|
40
|
-
# TODO: throw an error here instead I think - basically we did a round and didn't make progress
|
41
|
-
return ast if last_round == refs
|
42
|
-
|
43
|
-
ast = resolve_functions(resolve_variables(ast, refs.variables), refs.functions)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Bind +var_id+ to the current cell (based on where +@runtime+ is currently pointing).
|
48
|
-
#
|
49
|
-
# @param var_id [Symbol] The name of the variable to bind the cell reference to
|
50
|
-
#
|
51
|
-
# @return [CellReference]
|
52
|
-
def bind_variable_to_cell(var_id)
|
53
|
-
::CSVPlusPlus::Entities::CellReference.from_index(
|
54
|
-
cell_index: runtime.cell_index,
|
55
|
-
row_index: runtime.row_index
|
56
|
-
).tap do |cell_reference|
|
57
|
-
def_variable(var_id, cell_reference)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# @return [String]
|
62
|
-
def to_s
|
63
|
-
"Scope(functions: #{@functions}, runtime: #{@runtime}, variables: #{@variables})"
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
# Resolve all variable references defined statically in the code section
|
69
|
-
# TODO: experiment with getting rid of this - does it even play correctly with runtime vars?
|
70
|
-
def resolve_static_variables!
|
71
|
-
last_var_dependencies = {}
|
72
|
-
loop do
|
73
|
-
var_dependencies, resolution_order = variable_resolution_order(only_static_vars(variables))
|
74
|
-
return if var_dependencies == last_var_dependencies
|
75
|
-
|
76
|
-
# TODO: make the contract better here
|
77
|
-
@variables = resolve_dependencies(var_dependencies, resolution_order, variables)
|
78
|
-
last_var_dependencies = var_dependencies.clone
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def only_static_vars(var_dependencies)
|
83
|
-
var_dependencies.reject { |k| @runtime.runtime_variable?(k) }
|
84
|
-
end
|
85
|
-
|
86
|
-
def resolve_functions(ast, refs)
|
87
|
-
refs.reduce(ast.dup) do |acc, elem|
|
88
|
-
function_replace(acc, elem.id, resolve_function(elem.id))
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def resolve_variables(ast, refs)
|
93
|
-
refs.reduce(ast.dup) do |acc, elem|
|
94
|
-
variable_replace(acc, elem.id, resolve_variable(elem.id))
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Make a copy of the AST represented by +node+ and replace +fn_id+ with +replacement+ throughout
|
99
|
-
# rubocop:disable Metrics/MethodLength
|
100
|
-
def function_replace(node, fn_id, replacement)
|
101
|
-
if node.function_call? && node.id == fn_id
|
102
|
-
call_function_or_runtime_value(replacement, node)
|
103
|
-
elsif node.function_call?
|
104
|
-
# not our function, but continue our depth first search on it
|
105
|
-
::CSVPlusPlus::Entities::FunctionCall.new(
|
106
|
-
node.id,
|
107
|
-
node.arguments.map { |n| function_replace(n, fn_id, replacement) },
|
108
|
-
infix: node.infix
|
109
|
-
)
|
110
|
-
else
|
111
|
-
node
|
112
|
-
end
|
113
|
-
end
|
114
|
-
# rubocop:enable Metrics/MethodLength
|
115
|
-
|
116
|
-
def resolve_function(fn_id)
|
117
|
-
id = fn_id.to_sym
|
118
|
-
return functions[id] if defined_function?(id)
|
119
|
-
|
120
|
-
::CSVPlusPlus::Entities::Builtins::FUNCTIONS[id]
|
121
|
-
end
|
122
|
-
|
123
|
-
def call_function_or_runtime_value(function_or_runtime_value, function_call)
|
124
|
-
if function_or_runtime_value.function?
|
125
|
-
call_function(function_or_runtime_value, function_call)
|
126
|
-
else
|
127
|
-
function_or_runtime_value.resolve_fn.call(@runtime, function_call.arguments)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def call_function(function, function_call)
|
132
|
-
i = 0
|
133
|
-
function.arguments.reduce(function.body.dup) do |ast, argument|
|
134
|
-
variable_replace(ast, argument, function_call.arguments[i]).tap do
|
135
|
-
i += 1
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Make a copy of the AST represented by +node+ and replace +var_id+ with +replacement+ throughout
|
141
|
-
def variable_replace(node, var_id, replacement)
|
142
|
-
if node.function_call?
|
143
|
-
arguments = node.arguments.map { |n| variable_replace(n, var_id, replacement) }
|
144
|
-
# TODO: refactor these places where we copy functions... it's brittle with the kwargs
|
145
|
-
::CSVPlusPlus::Entities::FunctionCall.new(node.id, arguments, infix: node.infix)
|
146
|
-
elsif node.variable? && node.id == var_id
|
147
|
-
replacement
|
148
|
-
else
|
149
|
-
node
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def resolve_variable(var_id)
|
154
|
-
id = var_id.to_sym
|
155
|
-
return variables[id] if defined_variable?(id)
|
156
|
-
|
157
|
-
# this will throw a syntax error if it doesn't exist (which is what we want)
|
158
|
-
@runtime.runtime_value(id)
|
159
|
-
end
|
160
|
-
|
161
|
-
def check_unbound_vars(dependencies, variables)
|
162
|
-
unbound_vars = dependencies.values.flatten - variables.keys
|
163
|
-
return if unbound_vars.empty?
|
164
|
-
|
165
|
-
@runtime.raise_formula_syntax_error('Undefined variables', unbound_vars.map(&:to_s).join(', '))
|
166
|
-
end
|
167
|
-
|
168
|
-
def variable_resolution_order(variables)
|
169
|
-
# we have a hash of variables => ASTs but they might have references to each other, so
|
170
|
-
# we need to interpolate them first (before interpolating the cell values)
|
171
|
-
var_dependencies = ::CSVPlusPlus::Graph.dependency_graph(variables, @runtime)
|
172
|
-
# are there any references that we don't have variables for? (undefined variable)
|
173
|
-
check_unbound_vars(var_dependencies, variables)
|
174
|
-
|
175
|
-
# a topological sort will give us the order of dependencies
|
176
|
-
[var_dependencies, ::CSVPlusPlus::Graph.topological_sort(var_dependencies)]
|
177
|
-
# TODO: don't expose this exception directly to the caller
|
178
|
-
rescue ::TSort::Cyclic
|
179
|
-
@runtime.raise_formula_syntax_error('Cyclic variable dependency detected', var_refs.keys)
|
180
|
-
end
|
181
|
-
|
182
|
-
def resolve_dependencies(var_dependencies, resolution_order, variables)
|
183
|
-
{}.tap do |resolved_vars|
|
184
|
-
# for each var and each dependency it has, build up and mutate resolved_vars
|
185
|
-
resolution_order.each do |var|
|
186
|
-
resolved_vars[var] = variables[var].dup
|
187
|
-
|
188
|
-
var_dependencies[var].each do |dependency|
|
189
|
-
resolved_vars[var] = variable_replace(resolved_vars[var], dependency, variables[dependency])
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
# rubocop:enable Metrics/ClassLength
|
196
|
-
end
|
@@ -1,164 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'data_validation'
|
4
|
-
require_relative 'modifier'
|
5
|
-
|
6
|
-
module CSVPlusPlus
|
7
|
-
# Validates and coerces modifier values as they are parsed.
|
8
|
-
#
|
9
|
-
# Previously this logic was handled in the parser's grammar, but with the introduction of variable binding, the
|
10
|
-
# grammar is no longer context free so we need the parser to be a little looser on what it accepts and validate it
|
11
|
-
# here. Having this layer is also nice because we can provide better error messages to the user for what went
|
12
|
-
# wrong during the parse.
|
13
|
-
class ValidatedModifier < ::CSVPlusPlus::Modifier
|
14
|
-
# Validates that +border+ is 'all', 'top', 'bottom', 'left' or 'right'.
|
15
|
-
#
|
16
|
-
# @param value [String] The unvalidated user input
|
17
|
-
def border=(value)
|
18
|
-
super(one_of(:border, value, %i[all top bottom left right]))
|
19
|
-
end
|
20
|
-
|
21
|
-
# Validates that +bordercolor+ is a hex color.
|
22
|
-
#
|
23
|
-
# @param value [String] The unvalidated user input
|
24
|
-
def bordercolor=(value)
|
25
|
-
super(color_value(:bordercolor, value))
|
26
|
-
end
|
27
|
-
|
28
|
-
# Validates that +borderstyle+ is 'dashed', 'dotted', 'double', 'solid', 'solid_medium' or 'solid_thick'.
|
29
|
-
#
|
30
|
-
# @param value [String] The unvalidated user input
|
31
|
-
def borderstyle=(value)
|
32
|
-
super(one_of(:borderstyle, value, %i[dashed dotted double solid solid_medium solid_thick]))
|
33
|
-
end
|
34
|
-
|
35
|
-
# Validates that +color+ is a hex color.
|
36
|
-
#
|
37
|
-
# @param value [String] The unvalidated user input
|
38
|
-
def color=(value)
|
39
|
-
super(color_value(:color, value))
|
40
|
-
end
|
41
|
-
|
42
|
-
# Validates that +expand+ is a positive integer.
|
43
|
-
#
|
44
|
-
# @param value [String] The unvalidated user input
|
45
|
-
def expand=(value)
|
46
|
-
super(::CSVPlusPlus::Expand.new(positive_integer(:expand, value)))
|
47
|
-
end
|
48
|
-
|
49
|
-
# Validates that +fontcolor+ is a hex color.
|
50
|
-
#
|
51
|
-
# @param value [String] The unvalidated user input
|
52
|
-
def fontcolor=(value)
|
53
|
-
super(color_value(:fontcolor, value))
|
54
|
-
end
|
55
|
-
|
56
|
-
# Validates that +fontcolor+ is a hex color.
|
57
|
-
def fontfamily=(value)
|
58
|
-
super(matches_regexp(:fontfamily, unquote(value), /^[\w\s]+$/, 'It is not a valid font family.'))
|
59
|
-
end
|
60
|
-
|
61
|
-
# Validates that +fontsize+ is a positive integer
|
62
|
-
#
|
63
|
-
# @param value [String] The unvalidated user input
|
64
|
-
def fontsize=(value)
|
65
|
-
super(positive_integer(:fontsize, value))
|
66
|
-
end
|
67
|
-
|
68
|
-
# Validates that +format+ is 'bold', 'italic', 'strikethrough' or 'underline'.
|
69
|
-
#
|
70
|
-
# @param value [String] The unvalidated user input
|
71
|
-
def format=(value)
|
72
|
-
super(one_of(:format, value, %i[bold italic strikethrough underline]))
|
73
|
-
end
|
74
|
-
|
75
|
-
# Validates that +halign+ is 'left', 'center' or 'right'.
|
76
|
-
#
|
77
|
-
# @param value [String] The unvalidated user input
|
78
|
-
def halign=(value)
|
79
|
-
super(one_of(:halign, value, %i[left center right]))
|
80
|
-
end
|
81
|
-
|
82
|
-
# Validates that +note+ is a quoted string.
|
83
|
-
#
|
84
|
-
# @param value [String] The unvalidated user input
|
85
|
-
|
86
|
-
# Validates that +numberformat+ is 'currency', 'date', 'date_time', 'number', 'percent', 'text', 'time' or
|
87
|
-
# 'scientific'.
|
88
|
-
#
|
89
|
-
# @param value [String] The unvalidated user input
|
90
|
-
def numberformat=(value)
|
91
|
-
super(one_of(:nubmerformat, value, %i[currency date date_time number percent text time scientific]))
|
92
|
-
end
|
93
|
-
|
94
|
-
# Validates that +valign+ is 'top', 'center' or 'bottom'.
|
95
|
-
#
|
96
|
-
# @param value [String] The unvalidated user input
|
97
|
-
def valign=(value)
|
98
|
-
super(one_of(:valign, value, %i[top center bottom]))
|
99
|
-
end
|
100
|
-
|
101
|
-
# Validates that the conditional validating rules are well-formed.
|
102
|
-
#
|
103
|
-
# Pretty much based off of the Google Sheets API spec here:
|
104
|
-
#
|
105
|
-
# @param value [String] The unvalidated user input
|
106
|
-
def validation=(value)
|
107
|
-
super(a_data_validation(:validation, value))
|
108
|
-
end
|
109
|
-
|
110
|
-
# Validates +variable+ is a valid variable identifier.
|
111
|
-
#
|
112
|
-
# @param value [String] The unvalidated user input
|
113
|
-
def var=(value)
|
114
|
-
# TODO: I need a shared definition of what a variable can be (I guess the :ID token)
|
115
|
-
super(matches_regexp(:var, value, /^\w+$/, 'It must be a sequence of letters, numbers and _.').to_sym)
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
# XXX centralize this :(((
|
121
|
-
def unquote(str)
|
122
|
-
# TODO: I'm pretty sure this isn't sufficient and we need to deal with the backslashes
|
123
|
-
str.gsub(/^['\s]*|['\s]*$/, '')
|
124
|
-
end
|
125
|
-
|
126
|
-
def a_data_validation(modifier, value)
|
127
|
-
data_validation = ::CSVPlusPlus::DataValidation.new(value)
|
128
|
-
return data_validation unless data_validation.valid?
|
129
|
-
|
130
|
-
raise_error(modifier, value, message: data_validation.invalid_reason)
|
131
|
-
end
|
132
|
-
|
133
|
-
def color_value(modifier, value)
|
134
|
-
unless ::CSVPlusPlus::Color.valid_hex_string?(value)
|
135
|
-
raise_error(modifier, value, message: 'It must be a 3 or 6 digit hex code.')
|
136
|
-
end
|
137
|
-
|
138
|
-
::CSVPlusPlus::Color.new(value)
|
139
|
-
end
|
140
|
-
|
141
|
-
def matches_regexp(modifier, value, regexp, message)
|
142
|
-
raise_error(modifier, value, message:) unless value =~ regexp
|
143
|
-
value
|
144
|
-
end
|
145
|
-
|
146
|
-
def one_of(modifier, value, choices)
|
147
|
-
value.downcase.to_sym.tap do |v|
|
148
|
-
raise_error(modifier, value, choices:) unless choices.include?(v)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def positive_integer(modifier, value)
|
153
|
-
Integer(value, 10).tap do |i|
|
154
|
-
raise_error(modifier, value, message: 'It must be positive and greater than 0.') unless i.positive?
|
155
|
-
end
|
156
|
-
rescue ::ArgumentError
|
157
|
-
raise_error(modifier, value, message: 'It must be a valid (whole) number.')
|
158
|
-
end
|
159
|
-
|
160
|
-
def raise_error(modifier, bad_input, choices: nil, message: nil)
|
161
|
-
raise(::CSVPlusPlus::Error::ModifierValidationError.new(modifier, bad_input, choices:, message:))
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Writer
|
5
|
-
# Decorate a Modifier so it can be written to the Google Sheets API
|
6
|
-
class GoogleSheetModifier < ::SimpleDelegator
|
7
|
-
# Format the border for Google Sheets
|
8
|
-
#
|
9
|
-
# @return [Google::Apis::SheetsV4::Border]
|
10
|
-
def border
|
11
|
-
# TODO: allow different border styles per side
|
12
|
-
::Google::Apis::SheetsV4::Border.new(
|
13
|
-
color: bordercolor&.to_s || '#000000',
|
14
|
-
style: borderstyle&.to_s || 'solid'
|
15
|
-
)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Format the color for Google Sheets
|
19
|
-
#
|
20
|
-
# @return [Google::Apis::SheetsV4::Color]
|
21
|
-
def color
|
22
|
-
google_sheets_color(super) if super
|
23
|
-
end
|
24
|
-
|
25
|
-
# Format the fontcolor for Google Sheets
|
26
|
-
#
|
27
|
-
# @return [Google::Apis::SheetsV4::Color]
|
28
|
-
def fontcolor
|
29
|
-
google_sheets_color(super) if super
|
30
|
-
end
|
31
|
-
|
32
|
-
# Format the halign for Google Sheets
|
33
|
-
#
|
34
|
-
# @return [String]
|
35
|
-
def halign
|
36
|
-
super&.to_s&.upcase
|
37
|
-
end
|
38
|
-
|
39
|
-
# Format the numberformat for Google Sheets
|
40
|
-
#
|
41
|
-
# @return [::Google::Apis::SheetsV4::NumberFormat]
|
42
|
-
def numberformat
|
43
|
-
::Google::Apis::SheetsV4::NumberFormat.new(type: super) if super
|
44
|
-
end
|
45
|
-
|
46
|
-
# Builds a SheetsV4::TextFormat with the underlying Modifier
|
47
|
-
#
|
48
|
-
# @return [::Google::Apis::SheetsV4::TextFormat]
|
49
|
-
def text_format
|
50
|
-
::Google::Apis::SheetsV4::TextFormat.new(
|
51
|
-
bold: formatted?(:bold) || nil,
|
52
|
-
italic: formatted?(:italic) || nil,
|
53
|
-
strikethrough: formatted?(:strikethrough) || nil,
|
54
|
-
underline: formatted?(:underline) || nil,
|
55
|
-
font_family: fontfamily,
|
56
|
-
font_size: fontsize,
|
57
|
-
foreground_color: fontcolor
|
58
|
-
)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Format the valign for Google Sheets
|
62
|
-
def valign
|
63
|
-
super&.to_s&.upcase
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def google_sheets_color(color)
|
69
|
-
::Google::Apis::SheetsV4::Color.new(
|
70
|
-
red: color.red_percent,
|
71
|
-
green: color.green_percent,
|
72
|
-
blue: color.blue_percent
|
73
|
-
)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Writer
|
5
|
-
# Build a RubyXL-decorated Modifier class adds some support for Excel
|
6
|
-
class RubyXLModifier < ::SimpleDelegator
|
7
|
-
# https://www.rubydoc.info/gems/rubyXL/RubyXL/NumberFormats
|
8
|
-
# https://support.microsoft.com/en-us/office/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68
|
9
|
-
NUM_FMT_IDS = {
|
10
|
-
currency: 5,
|
11
|
-
date: 14,
|
12
|
-
date_time: 22,
|
13
|
-
number: 1,
|
14
|
-
percent: 9,
|
15
|
-
text: 49,
|
16
|
-
time: 21,
|
17
|
-
scientific: 48
|
18
|
-
}.freeze
|
19
|
-
private_constant :NUM_FMT_IDS
|
20
|
-
|
21
|
-
# https://www.rubydoc.info/gems/rubyXL/2.3.0/RubyXL
|
22
|
-
# ST_BorderStyle = %w{ none thin medium dashed dotted thick double hair mediumDashed dashDot mediumDashDot
|
23
|
-
# dashDotDot slantDashDot }
|
24
|
-
BORDER_STYLES = {
|
25
|
-
dashed: 'dashed',
|
26
|
-
dotted: 'dotted',
|
27
|
-
double: 'double',
|
28
|
-
solid: 'thin',
|
29
|
-
solid_medium: 'medium',
|
30
|
-
solid_thick: 'thick'
|
31
|
-
}.freeze
|
32
|
-
private_constant :BORDER_STYLES
|
33
|
-
|
34
|
-
# The excel-specific border weight
|
35
|
-
#
|
36
|
-
# @return [Integer]
|
37
|
-
def border_weight
|
38
|
-
return unless borderstyle
|
39
|
-
|
40
|
-
# rubocop:disable Lint/ConstantResolution
|
41
|
-
BORDER_STYLES[borderstyle.to_sym]
|
42
|
-
# rubocop:enable Lint/ConstantResolution
|
43
|
-
end
|
44
|
-
|
45
|
-
# The excel-specific number format code
|
46
|
-
#
|
47
|
-
# @return [String]
|
48
|
-
def number_format_code
|
49
|
-
return unless numberformat
|
50
|
-
|
51
|
-
::RubyXL::NumberFormats::DEFAULT_NUMBER_FORMATS.find_by_format_id(
|
52
|
-
# rubocop:disable Lint/ConstantResolution
|
53
|
-
NUM_FMT_IDS[numberformat.to_sym]
|
54
|
-
# rubocop:enable Lint/ConstantResolution
|
55
|
-
).format_code
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|