csv_plus_plus 0.1.3 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -3
- data/docs/CHANGELOG.md +18 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +3 -3
- data/lib/csv_plus_plus/cell.rb +1 -35
- data/lib/csv_plus_plus/cli.rb +43 -80
- data/lib/csv_plus_plus/cli_flag.rb +77 -70
- data/lib/csv_plus_plus/color.rb +1 -1
- data/lib/csv_plus_plus/compiler.rb +31 -21
- data/lib/csv_plus_plus/entities/ast_builder.rb +11 -4
- data/lib/csv_plus_plus/entities/boolean.rb +16 -9
- data/lib/csv_plus_plus/entities/builtins.rb +68 -40
- data/lib/csv_plus_plus/entities/date.rb +14 -11
- data/lib/csv_plus_plus/entities/entity.rb +11 -29
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +18 -31
- data/lib/csv_plus_plus/entities/function.rb +22 -11
- data/lib/csv_plus_plus/entities/function_call.rb +35 -11
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +15 -10
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +36 -23
- data/lib/csv_plus_plus/entities/string.rb +13 -10
- data/lib/csv_plus_plus/entities.rb +2 -18
- 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 +18 -5
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -13
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +10 -36
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +6 -32
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +1 -1
- data/lib/csv_plus_plus/error.rb +4 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +18 -8
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +53 -17
- data/lib/csv_plus_plus/lexer.rb +40 -1
- data/lib/csv_plus_plus/modifier/data_validation.rb +1 -1
- data/lib/csv_plus_plus/modifier/expand.rb +17 -0
- data/lib/csv_plus_plus/modifier.rb +6 -1
- 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 +102 -0
- data/lib/csv_plus_plus/options.rb +22 -110
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +65 -66
- data/lib/csv_plus_plus/parser/code_section.tab.rb +92 -84
- data/lib/csv_plus_plus/parser/modifier.tab.rb +40 -30
- 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/runtime/graph.rb +6 -6
- data/lib/csv_plus_plus/runtime/{position_tracker.rb → position.rb} +16 -5
- data/lib/csv_plus_plus/runtime/references.rb +32 -27
- data/lib/csv_plus_plus/runtime/runtime.rb +73 -67
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +9 -9
- data/lib/csv_plus_plus/source_code.rb +14 -9
- data/lib/csv_plus_plus/template.rb +17 -12
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/csv.rb +32 -5
- data/lib/csv_plus_plus/writer/excel.rb +19 -6
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -14
- data/lib/csv_plus_plus/writer/google_sheets.rb +23 -129
- data/lib/csv_plus_plus/writer/{google_sheet_builder.rb → google_sheets_builder.rb} +39 -55
- data/lib/csv_plus_plus/writer/merger.rb +56 -0
- data/lib/csv_plus_plus/writer/open_document.rb +16 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +68 -43
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +58 -19
- data/lib/csv_plus_plus.rb +26 -14
- metadata +43 -18
- data/lib/csv_plus_plus/entities/cell_reference.rb +0 -231
- data/lib/csv_plus_plus/entities/variable.rb +0 -37
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -71
- data/lib/csv_plus_plus/google_options.rb +0 -32
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -89
- data/lib/csv_plus_plus/runtime/can_define_references.rb +0 -87
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +0 -209
- data/lib/csv_plus_plus/writer/base_writer.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c29d586e678385367fe7a4a67fa66f4f0d111ceb1ace46125daaecbdd0bf7b03
|
4
|
+
data.tar.gz: aa13e49605c6448aa61cbf6d64ac39f3cfd9d8f84cb20370d8129ab5f1dd6f4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6736fa3154f3ad44a4b3737a7d6e3b6571f6ba34226a8be4191a6cce3a53b1bcd09057b25bbc96da0ad01cc8c397e7b78df379b48fd510f881a810e8754aec65
|
7
|
+
data.tar.gz: 8160d63e5bda5417619d6a6e4e2a73b5cee744fc26b356d2c36efe3c56b93e2cfda8ed5cb128758079463542d0306d634c4804e02b7e4c56c14d9b98b0f3c1db
|
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# Deprecated!!!
|
2
|
+
|
3
|
+
This version of csv++ is deprecated. For the current version please go to [github.com/patrickomatic/csv-plus-plus](https://github.com/patrickomatic/csv-plus-plus)
|
4
|
+
|
5
|
+
![main](https://github.com/patrickomatic/csv-plus-plus/actions/workflows/rspec.yml/badge.svg)
|
1
6
|
[![Ruby Style Guide](https://img.shields.io/badge/code_style-community-brightgreen.svg)](https://rubystyle.guide)
|
2
7
|
[![Gem Version](https://badge.fury.io/rb/csv_plus_plus.svg)](https://badge.fury.io/rb/csv_plus_plus)
|
3
8
|
|
@@ -19,8 +24,8 @@ quantity := celladjacent(D)
|
|
19
24
|
def profit() (price * quantity) - fees
|
20
25
|
|
21
26
|
---
|
22
|
-
![[format=bold/align=center]]Date,Ticker,Price,Quantity,
|
23
|
-
![[expand]],[[format=
|
27
|
+
![[format=bold/align=center]]Date ,Ticker ,Price ,Quantity ,Profit ,Fees
|
28
|
+
![[expand]] ,[[format=italic]] , , ,"=profit()" ,=fees
|
24
29
|
```
|
25
30
|
|
26
31
|
And can be compiled into a `.xlsx` file by:
|
@@ -45,7 +50,7 @@ or if you want the very latest changes, clone this repository and run:
|
|
45
50
|
|
46
51
|
## Examples
|
47
52
|
|
48
|
-
Take a look at the [examples](
|
53
|
+
Take a look at the [repository of examples](https://github.com/patrickomatic/csvpp-examples) repository for a bunch of example `.csvpp` files.
|
49
54
|
|
50
55
|
## CLI Arguments
|
51
56
|
|
@@ -58,7 +63,12 @@ Usage: csv++ [options]
|
|
58
63
|
-k, --key-values KEY_VALUES A comma-separated list of key=values which will be made available to the template
|
59
64
|
-n, --sheet-name SHEET_NAME The name of the sheet to apply the template to
|
60
65
|
-o, --output OUTPUT_FILE The file to write to (must be .csv, .ods, .xls)
|
66
|
+
-s, --safe Do not overwrite values in the spreadsheet being written to. The default is to overwrite
|
61
67
|
-v, --verbose Enable verbose output
|
62
68
|
-x, --offset-columns OFFSET Apply the template offset by OFFSET cells
|
63
69
|
-y, --offset-rows OFFSET Apply the template offset by OFFSET rows
|
64
70
|
```
|
71
|
+
|
72
|
+
## See Also:
|
73
|
+
|
74
|
+
* [Supported features by output format](./docs/feature_matrix.csvpp)
|
data/docs/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## main
|
2
|
+
|
3
|
+
- Add a `-s`/`--safe` flag which changes the merge strategy to not overwrite existing values. If the spreadsheet being written to has values where the csvpp template wants to write, they will be overwritten otherwise.
|
4
|
+
|
5
|
+
## v0.2.0
|
6
|
+
|
7
|
+
### **Breaking Changes**
|
8
|
+
|
9
|
+
- Removal of the $$ operator - to dereference variables you can just reference them by name and they will be resolved if they are defined. Otherwise they will be left alone in the output
|
10
|
+
|
11
|
+
### Non-breaking Changes
|
12
|
+
|
13
|
+
- Excel: fix the merging of existing values
|
14
|
+
- CSV: fix the merging of existing values
|
15
|
+
- Support merging in values from CSV (previously it would ignore/overwrite them)
|
16
|
+
- Allow for more generous spacing in the csv section (and reflect this in the examples)
|
17
|
+
- More type coverage
|
18
|
+
|
1
19
|
## v0.1.3
|
2
20
|
|
3
21
|
- Proper scoping of variables defined within an expand modifier
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
# A reference to a cell. Internally it is represented by a simple +cell_index+ and +row_index+ but there are
|
6
|
+
# functions for converting to and from A1-style formats. Supported formats are:
|
7
|
+
#
|
8
|
+
# * `1` - A reference to the entire first row
|
9
|
+
# * `A` - A reference to the entire first column
|
10
|
+
# * `A1` - A reference to the first cell (top left)
|
11
|
+
# * `A1:D10` - The range defined between A1 and D10
|
12
|
+
# * `Sheet1!B2` - Cell B2 on the sheet "Sheet1"
|
13
|
+
#
|
14
|
+
# @attr sheet_name [String, nil] The name of the sheet reference
|
15
|
+
# @attr_reader cell_index [Integer, nil] The cell index of the cell being referenced
|
16
|
+
# @attr_reader row_index [Integer, nil] The row index of the cell being referenced
|
17
|
+
# @attr_reader upper_cell_index [Integer, nil] If set, the cell reference is a range and this is the upper cell
|
18
|
+
# index of it
|
19
|
+
# @attr_reader upper_row_index [Integer, nil] If set, the cell reference is a range and this is the upper row index
|
20
|
+
# of in
|
21
|
+
# rubocop:disable Metrics/ClassLength
|
22
|
+
class A1Reference
|
23
|
+
extend ::T::Sig
|
24
|
+
|
25
|
+
sig { returns(::T.nilable(::String)) }
|
26
|
+
attr_accessor :sheet_name
|
27
|
+
|
28
|
+
sig { returns(::T.nilable(::Integer)) }
|
29
|
+
attr_reader :cell_index
|
30
|
+
|
31
|
+
sig { returns(::T.nilable(::Integer)) }
|
32
|
+
attr_reader :row_index
|
33
|
+
|
34
|
+
sig { returns(::T.nilable(::Integer)) }
|
35
|
+
attr_reader :upper_cell_index
|
36
|
+
|
37
|
+
sig { returns(::T.nilable(::Integer)) }
|
38
|
+
attr_reader :upper_row_index
|
39
|
+
|
40
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::Expand)) }
|
41
|
+
attr_reader :scoped_to_expand
|
42
|
+
|
43
|
+
# TODO: this is getting gross, maybe define an actual parser
|
44
|
+
A1_NOTATION_REGEXP = /
|
45
|
+
^
|
46
|
+
(?:
|
47
|
+
(?:
|
48
|
+
(?:'([^'\\]|\\.)*') # allow for a single-quoted sheet name
|
49
|
+
|
|
50
|
+
(\w+) # or if it's not quoted, just allow \w+
|
51
|
+
)
|
52
|
+
! # if a sheet name is specified, it's always followed by a !
|
53
|
+
)?
|
54
|
+
([a-zA-Z0-9]+) # the only part required - something alphanumeric
|
55
|
+
(?: :([a-zA-Z0-9]+))? # and they might make it a range
|
56
|
+
$
|
57
|
+
/x
|
58
|
+
public_constant :A1_NOTATION_REGEXP
|
59
|
+
|
60
|
+
ALPHA = ::T.let(('A'..'Z').to_a.freeze, ::T::Array[::String])
|
61
|
+
private_constant :ALPHA
|
62
|
+
|
63
|
+
sig { params(cell_reference_string: ::String).returns(::T::Boolean) }
|
64
|
+
# Does the given +cell_reference_string+ conform to a valid cell reference?
|
65
|
+
#
|
66
|
+
# {https://developers.google.com/sheets/api/guides/concepts}
|
67
|
+
#
|
68
|
+
# @param cell_reference_string [::String] The string to check if it is a valid cell reference (we assume it's in
|
69
|
+
# A1 notation but maybe can support R1C1)
|
70
|
+
#
|
71
|
+
# @return [::T::Boolean]
|
72
|
+
def self.valid_cell_reference?(cell_reference_string)
|
73
|
+
!(cell_reference_string =~ ::CSVPlusPlus::A1Reference::A1_NOTATION_REGEXP).nil?
|
74
|
+
end
|
75
|
+
|
76
|
+
sig do
|
77
|
+
params(
|
78
|
+
ref: ::T.nilable(::String),
|
79
|
+
cell_index: ::T.nilable(::Integer),
|
80
|
+
row_index: ::T.nilable(::Integer),
|
81
|
+
scoped_to_expand: ::T.nilable(::CSVPlusPlus::Modifier::Expand)
|
82
|
+
).void
|
83
|
+
end
|
84
|
+
# Either +ref+, +cell_index+ or +row_index+ must be specified. If +ref+ is supplied it will be parsed to calculate
|
85
|
+
# +row_index+ and +cell_index.
|
86
|
+
#
|
87
|
+
# @param ref [String, nil] A raw user-inputted reference
|
88
|
+
# @param cell_index [Integer, nil] The index of the cell being referenced.
|
89
|
+
# @param row_index [Integer, nil] The index of the row being referenced.
|
90
|
+
# @param scoped_to_expand [Expand] The [[expand]] that this cell reference will be scoped to. In other words, it
|
91
|
+
# will only be able to be resolved if the position is within the bounds of the expand (it can't be referenced
|
92
|
+
# outside of the expand.)
|
93
|
+
def initialize(ref: nil, cell_index: nil, row_index: nil, scoped_to_expand: nil)
|
94
|
+
raise(::ArgumentError, 'Must specify :cell_index or :row_index') unless ref || cell_index || row_index
|
95
|
+
|
96
|
+
@scoped_to_expand = scoped_to_expand
|
97
|
+
|
98
|
+
if ref
|
99
|
+
from_a1_ref!(ref)
|
100
|
+
else
|
101
|
+
@cell_index = ::T.let(cell_index, ::T.nilable(::Integer))
|
102
|
+
@row_index = ::T.let(row_index, ::T.nilable(::Integer))
|
103
|
+
|
104
|
+
@upper_cell_index = ::T.let(nil, ::T.nilable(::Integer))
|
105
|
+
@upper_row_index = ::T.let(nil, ::T.nilable(::Integer))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
110
|
+
# @param other [BasicObject]
|
111
|
+
#
|
112
|
+
# @return [boolean]
|
113
|
+
def ==(other)
|
114
|
+
case other
|
115
|
+
when self.class
|
116
|
+
@cell_index == other.cell_index && @row_index == other.row_index && @sheet_name == other.sheet_name \
|
117
|
+
&& @upper_cell_index == other.upper_cell_index && @upper_row_index == other.upper_row_index
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
sig { params(position: ::CSVPlusPlus::Runtime::Position).returns(::T.nilable(::String)) }
|
124
|
+
# Turns index-based/X,Y coordinates into a A1 format
|
125
|
+
#
|
126
|
+
# @param position [Position]
|
127
|
+
#
|
128
|
+
# @return [::String, nil]
|
129
|
+
def to_a1_ref(position)
|
130
|
+
row_index = position_row_index(position)
|
131
|
+
return unless row_index || @cell_index
|
132
|
+
|
133
|
+
rowref = row_index ? (row_index + 1).to_s : ''
|
134
|
+
cellref = @cell_index ? to_a1_cell_ref : ''
|
135
|
+
[cellref, rowref].join
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
sig { params(position: ::CSVPlusPlus::Runtime::Position).returns(::T.nilable(::Integer)) }
|
141
|
+
def position_row_index(position)
|
142
|
+
@scoped_to_expand ? position.row_index : @row_index
|
143
|
+
end
|
144
|
+
|
145
|
+
sig { returns(::String) }
|
146
|
+
# Turns a cell index into an A1 reference (just the "A" part - for example 0 == 'A', 1 == 'B', 2 == 'C', etc.)
|
147
|
+
#
|
148
|
+
# @return [::String]
|
149
|
+
def to_a1_cell_ref
|
150
|
+
c = @cell_index.dup
|
151
|
+
ref = ''
|
152
|
+
|
153
|
+
while c >= 0
|
154
|
+
# rubocop:disable Lint/ConstantResolution
|
155
|
+
ref += ::T.must(ALPHA[c % 26])
|
156
|
+
# rubocop:enable Lint/ConstantResolution
|
157
|
+
c = (c / 26).floor - 1
|
158
|
+
end
|
159
|
+
|
160
|
+
ref.reverse
|
161
|
+
end
|
162
|
+
|
163
|
+
sig { params(ref: ::String).void }
|
164
|
+
def from_a1_ref!(ref)
|
165
|
+
quoted_sheet_name, unquoted_sheet_name, lower_range, upper_range = ::T.must(
|
166
|
+
ref.strip.match(
|
167
|
+
::CSVPlusPlus::A1Reference::A1_NOTATION_REGEXP
|
168
|
+
)
|
169
|
+
).captures
|
170
|
+
|
171
|
+
@sheet_name = quoted_sheet_name || unquoted_sheet_name
|
172
|
+
|
173
|
+
parse_lower_range!(lower_range) if lower_range
|
174
|
+
parse_upper_range!(upper_range) if upper_range
|
175
|
+
end
|
176
|
+
|
177
|
+
sig { params(lower_range: ::String).void }
|
178
|
+
def parse_lower_range!(lower_range)
|
179
|
+
cell_ref, row_ref = ::T.must(lower_range.match(/^([a-zA-Z]+)?(\d+)?$/)).captures
|
180
|
+
@cell_index = from_a1_cell_ref!(cell_ref) if cell_ref
|
181
|
+
@row_index = Integer(row_ref, 10) - 1 if row_ref
|
182
|
+
end
|
183
|
+
|
184
|
+
sig { params(upper_range: ::String).void }
|
185
|
+
# TODO: make this less redundant with the above function
|
186
|
+
def parse_upper_range!(upper_range)
|
187
|
+
cell_ref, row_ref = ::T.must(upper_range.match(/^([a-zA-Z]+)?(\d+)?$/)).captures
|
188
|
+
@upper_cell_index = from_a1_cell_ref!(cell_ref) if cell_ref
|
189
|
+
@upper_row_index = Integer(row_ref, 10) - 1 if row_ref
|
190
|
+
end
|
191
|
+
|
192
|
+
sig { params(cell_ref: ::String).returns(::Integer) }
|
193
|
+
def from_a1_cell_ref!(cell_ref)
|
194
|
+
(cell_ref.upcase.chars.reduce(0) do |cell_index, letter|
|
195
|
+
# rubocop:disable Lint/ConstantResolution
|
196
|
+
(cell_index * 26) + ::T.must(ALPHA.find_index(letter)) + 1
|
197
|
+
# rubocop:enable Lint/ConstantResolution
|
198
|
+
end) - 1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
# rubocop:enable Metrics/ClassLength
|
202
|
+
end
|
@@ -18,7 +18,7 @@ module CSVPlusPlus
|
|
18
18
|
|
19
19
|
sig do
|
20
20
|
params(
|
21
|
-
options: ::CSVPlusPlus::Options,
|
21
|
+
options: ::CSVPlusPlus::Options::Options,
|
22
22
|
runtime: ::CSVPlusPlus::Runtime::Runtime,
|
23
23
|
block: ::T.proc.params(compiler: ::CSVPlusPlus::Compiler).void
|
24
24
|
).void
|
@@ -40,7 +40,7 @@ module CSVPlusPlus
|
|
40
40
|
sig do
|
41
41
|
params(
|
42
42
|
benchmark: ::Benchmark::Report,
|
43
|
-
options: ::CSVPlusPlus::Options,
|
43
|
+
options: ::CSVPlusPlus::Options::Options,
|
44
44
|
runtime: ::CSVPlusPlus::Runtime::Runtime
|
45
45
|
).void
|
46
46
|
end
|
@@ -52,7 +52,7 @@ module CSVPlusPlus
|
|
52
52
|
@timings = ::T.let([], ::T::Array[::Benchmark::Tms])
|
53
53
|
end
|
54
54
|
|
55
|
-
sig {
|
55
|
+
sig { params(block: ::T.proc.params(position: ::CSVPlusPlus::Runtime::Position).void).void }
|
56
56
|
# Time the Compiler#outputting! stage
|
57
57
|
# rubocop:disable Naming/BlockForwarding
|
58
58
|
def outputting!(&block)
|
data/lib/csv_plus_plus/cell.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
module CSVPlusPlus
|
5
5
|
# A cell of a template
|
6
6
|
#
|
7
|
-
# @attr ast [Entity]
|
7
|
+
# @attr ast [Entity, nil] The AST of the formula in the cell (if there is one)
|
8
8
|
# @attr row_index [Integer] The cell's row index (starts at 0)
|
9
9
|
# @attr_reader index [Integer] The cell's index (starts at 0)
|
10
10
|
# @attr_reader modifier [Modifier] The modifier for this cell
|
@@ -23,26 +23,6 @@ module CSVPlusPlus
|
|
23
23
|
sig { returns(::CSVPlusPlus::Modifier::Modifier) }
|
24
24
|
attr_reader :modifier
|
25
25
|
|
26
|
-
sig do
|
27
|
-
params(
|
28
|
-
value: ::T.nilable(::String),
|
29
|
-
runtime: ::CSVPlusPlus::Runtime::Runtime,
|
30
|
-
modifier: ::CSVPlusPlus::Modifier::Modifier
|
31
|
-
).returns(::CSVPlusPlus::Cell)
|
32
|
-
end
|
33
|
-
# Parse a +value+ into a Cell object.
|
34
|
-
#
|
35
|
-
# @param value [String] A string value which should already have been processed through a CSV parser
|
36
|
-
# @param runtime [Runtime]
|
37
|
-
# @param modifier [Modifier]
|
38
|
-
#
|
39
|
-
# @return [Cell]
|
40
|
-
def self.parse(value, runtime:, modifier:)
|
41
|
-
new(value:, row_index: runtime.row_index, index: runtime.cell_index, modifier:).tap do |c|
|
42
|
-
c.ast = ::T.unsafe(::CSVPlusPlus::Parser::CellValue.new).parse(value, runtime)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
26
|
sig do
|
47
27
|
params(
|
48
28
|
index: ::Integer,
|
@@ -66,24 +46,10 @@ module CSVPlusPlus
|
|
66
46
|
# The +@value+ (cleaned up some)
|
67
47
|
#
|
68
48
|
# @return [::String]
|
69
|
-
# TODO: is this used?
|
70
49
|
def value
|
71
50
|
stripped = @value&.strip
|
72
51
|
|
73
52
|
stripped&.empty? ? nil : stripped
|
74
53
|
end
|
75
|
-
|
76
|
-
sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T.nilable(::String)) }
|
77
|
-
# A compiled final representation of the cell. This can only happen after all cell have had variables and functions
|
78
|
-
# resolved.
|
79
|
-
#
|
80
|
-
# @param runtime [Runtime]
|
81
|
-
#
|
82
|
-
# @return [::String]
|
83
|
-
def evaluate(runtime)
|
84
|
-
return value unless @ast
|
85
|
-
|
86
|
-
"=#{@ast.evaluate(runtime)}"
|
87
|
-
end
|
88
54
|
end
|
89
55
|
end
|
data/lib/csv_plus_plus/cli.rb
CHANGED
@@ -2,111 +2,46 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module CSVPlusPlus
|
5
|
-
# Handle running the application with the
|
5
|
+
# Handle running the application with the supported +CLIFlag+s
|
6
6
|
#
|
7
|
-
# @attr options [Options
|
7
|
+
# @attr options [Options] The parsed CLI options
|
8
8
|
class CLI
|
9
9
|
extend ::T::Sig
|
10
10
|
|
11
|
-
sig { returns(::CSVPlusPlus::Options) }
|
11
|
+
sig { returns(::CSVPlusPlus::Options::Options) }
|
12
12
|
attr_accessor :options
|
13
13
|
|
14
|
+
sig { returns(::CSVPlusPlus::SourceCode) }
|
15
|
+
attr_accessor :source_code
|
16
|
+
|
14
17
|
sig { void }
|
15
18
|
# Handle CLI flags and launch the compiler
|
16
19
|
#
|
17
20
|
# @return [CLI]
|
18
21
|
def self.launch_compiler!
|
19
|
-
|
20
|
-
cli.main
|
22
|
+
new.main
|
21
23
|
rescue ::StandardError => e
|
22
|
-
|
24
|
+
warn(e.message)
|
23
25
|
exit(1)
|
24
26
|
end
|
25
27
|
|
26
28
|
sig { void }
|
27
29
|
# Initialize and parse the CLI flags provided to the program
|
28
30
|
def initialize
|
29
|
-
|
30
|
-
|
31
|
+
opts = parse_options
|
32
|
+
|
33
|
+
@source_code = ::T.let(::CSVPlusPlus::SourceCode.new(source_code_filename), ::CSVPlusPlus::SourceCode)
|
34
|
+
@options = ::T.let(apply_options(opts), ::CSVPlusPlus::Options::Options)
|
31
35
|
end
|
32
36
|
|
33
37
|
sig { void }
|
34
38
|
# Compile the given template using the given CLI flags
|
35
39
|
def main
|
36
|
-
::CSVPlusPlus.
|
37
|
-
end
|
38
|
-
|
39
|
-
sig { params(error: ::StandardError).void }
|
40
|
-
# Nicely handle a given error. How it's handled depends on if it's our error and if @options.verbose
|
41
|
-
#
|
42
|
-
# @param error [CSVPlusPlus::Error, Google::Apis::ClientError, StandardError]
|
43
|
-
def handle_error(error)
|
44
|
-
# make sure that we're on a newline (verbose mode might be in the middle of printing a benchmark)
|
45
|
-
puts("\n\n") if @options.verbose
|
46
|
-
|
47
|
-
case error
|
48
|
-
when ::CSVPlusPlus::Error::Error
|
49
|
-
handle_internal_error(error)
|
50
|
-
when ::Google::Apis::ClientError
|
51
|
-
handle_google_error(error)
|
52
|
-
else
|
53
|
-
unhandled_error(error)
|
54
|
-
end
|
40
|
+
::CSVPlusPlus.cli_compile(source_code, options)
|
55
41
|
end
|
56
42
|
|
57
43
|
private
|
58
44
|
|
59
|
-
sig { void }
|
60
|
-
# Handle the supplied command line options, setting +@options+ or throw an error if anything is invalid
|
61
|
-
def parse_options!
|
62
|
-
option_parser.parse!
|
63
|
-
validate_options
|
64
|
-
rescue ::OptionParser::InvalidOption => e
|
65
|
-
raise(::CSVPlusPlus::Error::Error, e.message)
|
66
|
-
end
|
67
|
-
|
68
|
-
sig { params(error: ::StandardError).void }
|
69
|
-
# An error was thrown that we weren't planning on
|
70
|
-
def unhandled_error(error)
|
71
|
-
warn(
|
72
|
-
<<~ERROR_MESSAGE)
|
73
|
-
An unexpected error was encountered. Please try running again with --verbose and
|
74
|
-
reporting the error at: https://github.com/patrickomatic/csv-plus-plus/issues/new'
|
75
|
-
ERROR_MESSAGE
|
76
|
-
|
77
|
-
return unless @options.verbose
|
78
|
-
|
79
|
-
warn(error.full_message)
|
80
|
-
warn("Cause: #{error.cause}") if error.cause
|
81
|
-
end
|
82
|
-
|
83
|
-
sig { params(error: ::CSVPlusPlus::Error::Error).void }
|
84
|
-
def handle_internal_error(error)
|
85
|
-
case error
|
86
|
-
when ::CSVPlusPlus::Error::SyntaxError
|
87
|
-
warn(@options.verbose ? error.to_verbose_trace : error.to_trace)
|
88
|
-
else
|
89
|
-
warn(error.message)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
sig { params(error: ::Google::Apis::ClientError).void }
|
94
|
-
def handle_google_error(error)
|
95
|
-
warn("Error making Google Sheets API request: #{error.message}")
|
96
|
-
return unless @options.verbose
|
97
|
-
|
98
|
-
warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
|
99
|
-
end
|
100
|
-
|
101
|
-
sig { void }
|
102
|
-
def validate_options
|
103
|
-
error_message = @options.validate
|
104
|
-
return if error_message.nil?
|
105
|
-
|
106
|
-
puts(option_parser)
|
107
|
-
raise(::CSVPlusPlus::Error::Error, error_message)
|
108
|
-
end
|
109
|
-
|
110
45
|
sig { returns(::OptionParser) }
|
111
46
|
def option_parser
|
112
47
|
::OptionParser.new do |parser|
|
@@ -115,10 +50,38 @@ module CSVPlusPlus
|
|
115
50
|
exit
|
116
51
|
end
|
117
52
|
|
118
|
-
::SUPPORTED_CSVPP_FLAGS.each do |f|
|
119
|
-
parser.on(f.short_flag, f.long_flag, f.description)
|
53
|
+
::CSVPlusPlus::SUPPORTED_CSVPP_FLAGS.each do |f|
|
54
|
+
parser.on(f.short_flag, f.long_flag, f.description)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(opts: ::T::Hash[::Symbol, ::String]).returns(::CSVPlusPlus::Options::Options) }
|
60
|
+
def apply_options(opts)
|
61
|
+
::CSVPlusPlus::Options.from_cli_flags(opts, source_code.filename).tap do |options|
|
62
|
+
opts.each do |key, value|
|
63
|
+
::T.must(::CSVPlusPlus::FLAG_HANDLERS[key]).call(options, value) if ::CSVPlusPlus::FLAG_HANDLERS.key?(key)
|
120
64
|
end
|
121
65
|
end
|
122
66
|
end
|
67
|
+
|
68
|
+
sig { returns(::T::Hash[::Symbol, ::String]) }
|
69
|
+
def parse_options
|
70
|
+
{}.tap do |opts|
|
71
|
+
option_parser.parse!(into: opts)
|
72
|
+
end
|
73
|
+
rescue ::OptionParser::InvalidOption => e
|
74
|
+
puts(option_parser)
|
75
|
+
raise(::CSVPlusPlus::Error::CLIError, e.message)
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { returns(::String) }
|
79
|
+
# NOTE: this must be called after #parse_options, since #parse_options modifiers +ARGV+
|
80
|
+
def source_code_filename
|
81
|
+
::ARGV.pop || raise(
|
82
|
+
::CSVPlusPlus::Error::CLIError,
|
83
|
+
'You must specify a source (.csvpp) file to compile as the last argument'
|
84
|
+
)
|
85
|
+
end
|
123
86
|
end
|
124
87
|
end
|