csv_plus_plus 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|