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.
- checksums.yaml +4 -4
- data/README.md +9 -5
- data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +29 -41
- data/lib/csv_plus_plus/cli.rb +53 -80
- data/lib/csv_plus_plus/cli_flag.rb +71 -71
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +98 -66
- data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
- data/lib/csv_plus_plus/entities/boolean.rb +26 -10
- data/lib/csv_plus_plus/entities/builtins.rb +66 -24
- data/lib/csv_plus_plus/entities/date.rb +42 -6
- data/lib/csv_plus_plus/entities/entity.rb +17 -69
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
- data/lib/csv_plus_plus/entities/function.rb +34 -11
- data/lib/csv_plus_plus/entities/function_call.rb +49 -10
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +30 -11
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
- data/lib/csv_plus_plus/entities/string.rb +23 -7
- data/lib/csv_plus_plus/entities.rb +7 -16
- data/lib/csv_plus_plus/error/cli_error.rb +17 -0
- data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
- data/lib/csv_plus_plus/error/error.rb +25 -2
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +5 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +25 -10
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
- data/lib/csv_plus_plus/lexer.rb +64 -1
- 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 +78 -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 +89 -160
- data/lib/csv_plus_plus/options/file_options.rb +49 -0
- data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
- data/lib/csv_plus_plus/options/options.rb +97 -0
- data/lib/csv_plus_plus/options.rb +34 -77
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
- data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
- data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
- data/lib/csv_plus_plus/reader/csv.rb +50 -0
- data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
- data/lib/csv_plus_plus/reader/reader.rb +27 -0
- data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
- data/lib/csv_plus_plus/reader.rb +14 -0
- data/lib/csv_plus_plus/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position.rb +242 -0
- data/lib/csv_plus_plus/runtime/references.rb +115 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +71 -0
- data/lib/csv_plus_plus/template.rb +71 -39
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/csv.rb +37 -8
- data/lib/csv_plus_plus/writer/excel.rb +25 -5
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
- data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
- data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
- data/lib/csv_plus_plus/writer/merger.rb +31 -0
- data/lib/csv_plus_plus/writer/open_document.rb +21 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +79 -10
- data/lib/csv_plus_plus.rb +47 -18
- metadata +50 -21
- 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/entities/cell_reference.rb +0 -60
- data/lib/csv_plus_plus/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/google_options.rb +0 -27
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
- 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/base_writer.rb +0 -20
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- 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
|
13
|
-
|
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
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
data/lib/csv_plus_plus/color.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
16
11
|
class Compiler
|
17
|
-
|
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
|
-
|
24
|
-
|
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(
|
33
|
+
::CSVPlusPlus::BenchmarkedCompiler.with_benchmarks(options:, runtime:) do |c|
|
27
34
|
block.call(c)
|
28
35
|
end
|
29
36
|
else
|
30
|
-
|
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
|
-
|
43
|
+
sig { params(options: ::CSVPlusPlus::Options::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
37
44
|
# @param options [Options]
|
38
|
-
# @param
|
39
|
-
def initialize(
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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:,
|
65
|
-
t.validate_infinite_expands
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
102
|
-
|
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
|
-
|
116
|
-
|
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
|
-
|
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
|
-
|
129
|
-
|
130
|
-
@runtime.
|
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::
|
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,
|
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::
|
148
|
-
parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
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
|
-
# @
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
15
|
+
sig { params(value: ::T.any(::String, ::T::Boolean)).void }
|
16
|
+
# @param value [::String, boolean]
|
14
17
|
def initialize(value)
|
15
|
-
super(
|
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
|
-
|
21
|
-
|
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
|
-
|
33
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
34
|
+
# @param other [Entity]
|
35
|
+
#
|
36
|
+
# @return [::T::Boolean]
|
26
37
|
def ==(other)
|
27
|
-
|
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
|