csv_plus_plus 0.1.1 → 0.1.3
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 +18 -63
- data/{CHANGELOG.md → docs/CHANGELOG.md} +17 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +112 -0
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +44 -17
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +42 -11
- data/lib/csv_plus_plus/compiler.rb +178 -0
- data/lib/csv_plus_plus/entities/ast_builder.rb +50 -0
- data/lib/csv_plus_plus/entities/boolean.rb +40 -0
- data/lib/csv_plus_plus/entities/builtins.rb +58 -0
- data/lib/csv_plus_plus/entities/cell_reference.rb +231 -0
- data/lib/csv_plus_plus/entities/date.rb +63 -0
- data/lib/csv_plus_plus/entities/entity.rb +50 -0
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +45 -0
- data/lib/csv_plus_plus/entities/function_call.rb +50 -0
- data/lib/csv_plus_plus/entities/number.rb +48 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +43 -0
- data/lib/csv_plus_plus/entities/string.rb +42 -0
- data/lib/csv_plus_plus/entities/variable.rb +37 -0
- data/lib/csv_plus_plus/entities.rb +40 -0
- data/lib/csv_plus_plus/error/error.rb +20 -0
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +37 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +75 -0
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +69 -0
- data/lib/csv_plus_plus/error/syntax_error.rb +71 -0
- data/lib/csv_plus_plus/error/writer_error.rb +17 -0
- data/lib/csv_plus_plus/error.rb +10 -2
- data/lib/csv_plus_plus/google_api_client.rb +11 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +17 -6
- data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
- data/lib/csv_plus_plus/lexer.rb +24 -0
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +18 -0
- data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
- data/lib/csv_plus_plus/modifier/expand.rb +61 -0
- data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
- data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
- data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
- data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
- data/lib/csv_plus_plus/modifier.rb +82 -150
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/{language → parser}/cell_value.tab.rb +25 -25
- data/lib/csv_plus_plus/{language → parser}/code_section.tab.rb +86 -95
- data/lib/csv_plus_plus/parser/modifier.tab.rb +478 -0
- data/lib/csv_plus_plus/row.rb +53 -15
- data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
- data/lib/csv_plus_plus/runtime/references.rb +110 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
- data/lib/csv_plus_plus/runtime.rb +42 -0
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +63 -36
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
- data/lib/csv_plus_plus/writer/csv.rb +11 -9
- data/lib/csv_plus_plus/writer/excel.rb +9 -2
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +7 -4
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +88 -45
- data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
- data/lib/csv_plus_plus/writer/open_document.rb +6 -1
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -33
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +41 -15
- metadata +44 -30
- data/lib/csv_plus_plus/code_section.rb +0 -101
- data/lib/csv_plus_plus/expand.rb +0 -18
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/language/ast_builder.rb +0 -68
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
- data/lib/csv_plus_plus/language/builtins.rb +0 -46
- data/lib/csv_plus_plus/language/compiler.rb +0 -152
- data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
- data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
- data/lib/csv_plus_plus/language/entities/function.rb +0 -35
- data/lib/csv_plus_plus/language/entities/function_call.rb +0 -37
- data/lib/csv_plus_plus/language/entities/number.rb +0 -36
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
- data/lib/csv_plus_plus/language/entities/string.rb +0 -31
- data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/language/entities.rb +0 -28
- data/lib/csv_plus_plus/language/references.rb +0 -70
- data/lib/csv_plus_plus/language/runtime.rb +0 -205
- data/lib/csv_plus_plus/language/scope.rb +0 -192
- data/lib/csv_plus_plus/language/syntax_error.rb +0 -66
- data/lib/csv_plus_plus/modifier.tab.rb +0 -907
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -56
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abee03db0f21f9d8c5d3cfb4ec81dbd748568f878458f355cab6db5fc458a509
|
|
4
|
+
data.tar.gz: 6c4e95fa1c780a2d009eaeb58fd48b28c2dd96826b22be02f84d5fc8a06ad31e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59f2cb756bdfd20f95f252aa1c07fb4b994def21f07d8bc4e5a976b22f0b439fb950e1723fdb34753baf23527e5950d244f1386e9231c6318b35de3974305ba4
|
|
7
|
+
data.tar.gz: 38b6d6d960aa17978adf2ee1724333754e7cdc86b8e41fa58522357448db8448d5441fb5961d3758af94d617b94b21b197b5e4d2d4eb2dba0cbc39da586c4413
|
data/README.md
CHANGED
|
@@ -9,101 +9,56 @@ A tool that allows you to programatically author spreadsheets in your favorite t
|
|
|
9
9
|
|
|
10
10
|
A `csvpp` file consists of a (optional) code section and a CSV section separated by `---`. In the code section you can define variables and functions that can be used in the CSV below it. For example:
|
|
11
11
|
|
|
12
|
+
###### **`mystocks.csvpp`**
|
|
12
13
|
```
|
|
13
14
|
fees := 0.50 # my broker charges $0.50 a trade
|
|
14
15
|
|
|
15
|
-
price :=
|
|
16
|
-
quantity :=
|
|
16
|
+
price := celladjacent(C)
|
|
17
|
+
quantity := celladjacent(D)
|
|
17
18
|
|
|
18
19
|
def profit() (price * quantity) - fees
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
![[format=bold/align=center]]Date,Ticker,Price,Quantity,Total,Fees
|
|
22
|
-
![[expand]],[[format=bold]],,,"=
|
|
23
|
+
![[expand]],[[format=bold]],,,"=profit()",$$fees
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Variables can be defined in the code section by giving a name (a combination of letters, numbers and underscores ) the expression `:=` and followed with a value.
|
|
28
|
-
|
|
29
|
-
### Built-in Variables
|
|
30
|
-
|
|
31
|
-
* `$$rownum` - The current row number. The first row of the spreadsheet starts at 1. Can be used anywhere and it's value will evaluate to the current row being processed.
|
|
32
|
-
|
|
33
|
-
## Functions
|
|
34
|
-
|
|
35
|
-
### Built-in Functions
|
|
36
|
-
* `cellref(CELL)` - Returns a reference to the `CELL` relative to the current row. If the current `$$rownum` is `2`, then `CELLREF("C")` returns a reference to cell `C2`.
|
|
37
|
-
|
|
38
|
-
## Modifiers
|
|
39
|
-
|
|
40
|
-
Modifiers can change the formatting of a cell or row, apply validation, change alignment, etc. All of the normal rules of CSV apply, with the addition that each cell can have modifiers (specified in `[[`/`]]` for cells and `![[`/`]]` for rows):
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
foo,[[...]]bar,baz
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
specifying formatting or various other modifiers to the cell. Additionally a row can start with:
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
![[...]]foo,bar,baz
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
which will apply that modifier to all cells in the row.
|
|
53
|
-
|
|
54
|
-
### Examples
|
|
55
|
-
|
|
56
|
-
* Align the second cell left, align the last cell to the center and make it bold and italicized:
|
|
26
|
+
And can be compiled into a `.xlsx` file by:
|
|
57
27
|
|
|
58
28
|
```
|
|
59
|
-
|
|
29
|
+
$ csv++ -n 'My Stock Tracker' -o mystocks.xlsx mystocks.csvpp
|
|
60
30
|
```
|
|
61
31
|
|
|
62
|
-
|
|
32
|
+
See the [Language Reference](./docs/LANGUAGE_REFERENCE.md) for a full explanation of features.
|
|
63
33
|
|
|
64
|
-
|
|
65
|
-
![[align=center/format=underline]]Date,Amount,Quantity,Price
|
|
66
|
-
```
|
|
34
|
+
## Installing
|
|
67
35
|
|
|
68
|
-
|
|
36
|
+
Just install it via rubygems (homebrew and debian packages are in the works):
|
|
69
37
|
|
|
70
|
-
|
|
71
|
-
![[align=center/format=bold]]Date,Price,Quantity,Profit
|
|
72
|
-
![[expand=1:]],,,"=MULTIPLY(cellref(B), cellref(C))"
|
|
73
|
-
```
|
|
38
|
+
`$ gem install csv_plus_plus`
|
|
74
39
|
|
|
75
|
-
|
|
40
|
+
or if you want the very latest changes, clone this repository and run:
|
|
76
41
|
|
|
77
|
-
|
|
42
|
+
`$ rake gem:install`
|
|
78
43
|
|
|
79
|
-
|
|
44
|
+
### [Setting Up Google Sheets](./docs/README_GOOGLE_SHEETS.md)
|
|
80
45
|
|
|
81
|
-
|
|
46
|
+
## Examples
|
|
82
47
|
|
|
83
|
-
|
|
84
|
-
* "Share" the spreadsheet with the email associated with the service account
|
|
48
|
+
Take a look at the [examples](./examples/) directory for a bunch of example `.csvpp` files.
|
|
85
49
|
|
|
86
50
|
## CLI Arguments
|
|
87
51
|
|
|
88
52
|
```
|
|
89
53
|
Usage: csv++ [options]
|
|
54
|
+
-h, --help Show help information
|
|
90
55
|
-b, --backup Create a backup of the spreadsheet before applying changes.
|
|
91
|
-
-g, --google-sheet-id SHEET_ID The id of the sheet - you can extract this from the URL: https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0
|
|
92
56
|
-c, --create Create the sheet if it doesn't exist. It will use --sheet-name if specified
|
|
57
|
+
-g, --google-sheet-id SHEET_ID The id of the sheet - you can extract this from the URL: https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0
|
|
93
58
|
-k, --key-values KEY_VALUES A comma-separated list of key=values which will be made available to the template
|
|
94
59
|
-n, --sheet-name SHEET_NAME The name of the sheet to apply the template to
|
|
60
|
+
-o, --output OUTPUT_FILE The file to write to (must be .csv, .ods, .xls)
|
|
95
61
|
-v, --verbose Enable verbose output
|
|
96
62
|
-x, --offset-columns OFFSET Apply the template offset by OFFSET cells
|
|
97
63
|
-y, --offset-rows OFFSET Apply the template offset by OFFSET rows
|
|
98
|
-
-h, --help Show help information
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Usage Examples
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
# apply my_taxes_template.csvpp to an existing Google Sheet with name "Taxes 2022"
|
|
105
|
-
$ csv++ --sheet-name "Taxes 2022" --sheet-id "[...]" my_taxes_template.csvpp
|
|
106
|
-
|
|
107
|
-
# take input from stdin, supply a variable ($$rate = 1) and apply to the "Stocks" spreadsheet
|
|
108
|
-
$ cat stocks.csvpp | csv++ -k "rate=1" -n "Stocks" -i "[...]"
|
|
109
64
|
```
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## v0.1.3
|
|
2
|
+
|
|
3
|
+
- Proper scoping of variables defined within an expand modifier
|
|
4
|
+
- Types via Sorbet
|
|
5
|
+
- Fix formula insertion on Excel
|
|
6
|
+
- Fix modifier string quoting
|
|
7
|
+
- Fix broken Yard doc generation
|
|
8
|
+
- Fix: multiple modifiers on the same row weren't being handled
|
|
9
|
+
|
|
10
|
+
## v0.1.2
|
|
11
|
+
|
|
12
|
+
- var=... modifier which allows binding a variable to a cell
|
|
13
|
+
- Improved error handling and messages
|
|
14
|
+
- Moving in a direction that allows for the context-dependent aspects of modifiers
|
|
15
|
+
- Fixes a bug with creating a new excel spreadsheet
|
|
16
|
+
- Docs & tests
|
|
17
|
+
|
|
1
18
|
## v0.1.1
|
|
2
19
|
|
|
3
20
|
- Better support for the various infix operators (+,-,/,*,^,%,=,<,etc)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module CSVPlusPlus
|
|
5
|
+
# Extend a +Compiler+ class and add benchmark timings
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader timings [Array<Benchmark::Tms>] +Benchmark+ timings that have been accumulated by each step of
|
|
8
|
+
# compilation
|
|
9
|
+
# @attr_reader benchmark [Benchmark::Report] A +Benchmark+ instance
|
|
10
|
+
class BenchmarkedCompiler < ::CSVPlusPlus::Compiler
|
|
11
|
+
extend ::T::Sig
|
|
12
|
+
|
|
13
|
+
sig { returns(::Benchmark::Report) }
|
|
14
|
+
attr_reader :benchmark
|
|
15
|
+
|
|
16
|
+
sig { returns(::T::Array[::Benchmark::Tms]) }
|
|
17
|
+
attr_reader :timings
|
|
18
|
+
|
|
19
|
+
sig do
|
|
20
|
+
params(
|
|
21
|
+
options: ::CSVPlusPlus::Options,
|
|
22
|
+
runtime: ::CSVPlusPlus::Runtime::Runtime,
|
|
23
|
+
block: ::T.proc.params(compiler: ::CSVPlusPlus::Compiler).void
|
|
24
|
+
).void
|
|
25
|
+
end
|
|
26
|
+
# Instantiate a +::Compiler+ that can benchmark (time) it's stages. For better or worse, the only way that they
|
|
27
|
+
# Benchmark library exposes it's +::Benchmark::Report+ is via a block, so this code also has to wrap with one
|
|
28
|
+
#
|
|
29
|
+
# @param options [Options]
|
|
30
|
+
# @param runtime [Runtime]
|
|
31
|
+
def self.with_benchmarks(options:, runtime:, &block)
|
|
32
|
+
::Benchmark.benchmark(::Benchmark::CAPTION, 25, ::Benchmark::FORMAT, '> Total') do |x|
|
|
33
|
+
# compiler.extend(self)
|
|
34
|
+
compiler = new(benchmark: x, options:, runtime:)
|
|
35
|
+
block.call(compiler)
|
|
36
|
+
[compiler.timings.reduce(:+)]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig do
|
|
41
|
+
params(
|
|
42
|
+
benchmark: ::Benchmark::Report,
|
|
43
|
+
options: ::CSVPlusPlus::Options,
|
|
44
|
+
runtime: ::CSVPlusPlus::Runtime::Runtime
|
|
45
|
+
).void
|
|
46
|
+
end
|
|
47
|
+
# @param benchmark [::Benchmark::Report]
|
|
48
|
+
def initialize(benchmark:, options:, runtime:)
|
|
49
|
+
super(options:, runtime:)
|
|
50
|
+
|
|
51
|
+
@benchmark = ::T.let(benchmark, ::Benchmark::Report)
|
|
52
|
+
@timings = ::T.let([], ::T::Array[::Benchmark::Tms])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { override.params(block: ::T.proc.params(runtime: ::CSVPlusPlus::Runtime::Runtime).void).void }
|
|
56
|
+
# Time the Compiler#outputting! stage
|
|
57
|
+
# rubocop:disable Naming/BlockForwarding
|
|
58
|
+
def outputting!(&block)
|
|
59
|
+
time_stage('Writing the spreadsheet') { super(&block) }
|
|
60
|
+
end
|
|
61
|
+
# rubocop:enable Naming/BlockForwarding
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
|
|
65
|
+
sig { override.void }
|
|
66
|
+
def parse_code_section!
|
|
67
|
+
time_stage('Parsing code section') { super }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sig { override.returns(::T::Array[::CSVPlusPlus::Row]) }
|
|
71
|
+
def parse_csv_section!
|
|
72
|
+
time_stage('Parsing CSV section') { super }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { override.params(block: ::T.proc.void).void }
|
|
76
|
+
# rubocop:disable Naming/BlockForwarding
|
|
77
|
+
def expanding!(&block)
|
|
78
|
+
time_stage('Expanding rows') { super(&block) }
|
|
79
|
+
end
|
|
80
|
+
# rubocop:enable Naming/BlockForwarding
|
|
81
|
+
|
|
82
|
+
sig { override.params(block: ::T.proc.void).void }
|
|
83
|
+
# rubocop:disable Naming/BlockForwarding
|
|
84
|
+
def bind_all_vars!(&block)
|
|
85
|
+
time_stage('Binding [[var=]]') { super(&block) }
|
|
86
|
+
end
|
|
87
|
+
# rubocop:enable Naming/BlockForwarding
|
|
88
|
+
|
|
89
|
+
sig do
|
|
90
|
+
override
|
|
91
|
+
.params(template: ::CSVPlusPlus::Template)
|
|
92
|
+
.returns(::T::Array[::T::Array[::CSVPlusPlus::Entities::Entity]])
|
|
93
|
+
end
|
|
94
|
+
def resolve_all_cells!(template)
|
|
95
|
+
time_stage('Resolving each cell') { super(template) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
sig do
|
|
101
|
+
type_parameters(:R).params(
|
|
102
|
+
stage: ::String,
|
|
103
|
+
block: ::T.proc.returns(::T.type_parameter(:R))
|
|
104
|
+
).returns(::T.nilable(::T.type_parameter(:R)))
|
|
105
|
+
end
|
|
106
|
+
def time_stage(stage, &block)
|
|
107
|
+
ret = ::T.let(nil, ::T.nilable(::T.type_parameter(:R)))
|
|
108
|
+
@timings << ::T.unsafe(@benchmark.report(stage) { ret = block.call })
|
|
109
|
+
ret
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/csv_plus_plus/cell.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require_relative './language/cell_value.tab'
|
|
4
|
-
require_relative './modifier'
|
|
5
|
-
|
|
6
4
|
module CSVPlusPlus
|
|
7
5
|
# A cell of a template
|
|
8
6
|
#
|
|
@@ -11,9 +9,27 @@ module CSVPlusPlus
|
|
|
11
9
|
# @attr_reader index [Integer] The cell's index (starts at 0)
|
|
12
10
|
# @attr_reader modifier [Modifier] The modifier for this cell
|
|
13
11
|
class Cell
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
extend ::T::Sig
|
|
13
|
+
|
|
14
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Entities::Entity)) }
|
|
15
|
+
attr_accessor :ast
|
|
16
|
+
|
|
17
|
+
sig { returns(::Integer) }
|
|
18
|
+
attr_accessor :row_index
|
|
19
|
+
|
|
20
|
+
sig { returns(::Integer) }
|
|
21
|
+
attr_reader :index
|
|
16
22
|
|
|
23
|
+
sig { returns(::CSVPlusPlus::Modifier::Modifier) }
|
|
24
|
+
attr_reader :modifier
|
|
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
|
|
17
33
|
# Parse a +value+ into a Cell object.
|
|
18
34
|
#
|
|
19
35
|
# @param value [String] A string value which should already have been processed through a CSV parser
|
|
@@ -23,45 +39,51 @@ module CSVPlusPlus
|
|
|
23
39
|
# @return [Cell]
|
|
24
40
|
def self.parse(value, runtime:, modifier:)
|
|
25
41
|
new(value:, row_index: runtime.row_index, index: runtime.cell_index, modifier:).tap do |c|
|
|
26
|
-
c.ast = ::CSVPlusPlus::
|
|
42
|
+
c.ast = ::T.unsafe(::CSVPlusPlus::Parser::CellValue.new).parse(value, runtime)
|
|
27
43
|
end
|
|
28
44
|
end
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
sig do
|
|
47
|
+
params(
|
|
48
|
+
index: ::Integer,
|
|
49
|
+
modifier: ::CSVPlusPlus::Modifier::Modifier,
|
|
50
|
+
row_index: ::Integer,
|
|
51
|
+
value: ::T.nilable(::String)
|
|
52
|
+
).void
|
|
53
|
+
end
|
|
31
54
|
# @param index [Integer] The cell's index (starts at 0)
|
|
32
|
-
# @param value [String] A string value which should already have been processed through a CSV parser
|
|
33
55
|
# @param modifier [Modifier] A modifier to apply to this cell
|
|
34
|
-
|
|
56
|
+
# @param row_index [Integer] The cell's row index (starts at 0)
|
|
57
|
+
# @param value [String] A string value which should already have been processed through a CSV parser
|
|
58
|
+
def initialize(index:, modifier:, row_index:, value:)
|
|
35
59
|
@value = value
|
|
36
60
|
@modifier = modifier
|
|
37
61
|
@index = index
|
|
38
62
|
@row_index = row_index
|
|
39
63
|
end
|
|
40
64
|
|
|
65
|
+
sig { returns(::T.nilable(::String)) }
|
|
41
66
|
# The +@value+ (cleaned up some)
|
|
42
67
|
#
|
|
43
|
-
# @return [String]
|
|
68
|
+
# @return [::String]
|
|
69
|
+
# TODO: is this used?
|
|
44
70
|
def value
|
|
45
|
-
|
|
71
|
+
stripped = @value&.strip
|
|
46
72
|
|
|
47
|
-
|
|
73
|
+
stripped&.empty? ? nil : stripped
|
|
48
74
|
end
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# A compiled final representation of the cell. This can only happen after all cell have had
|
|
56
|
-
# variables and functions resolved.
|
|
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]
|
|
57
81
|
#
|
|
58
|
-
# @return [String]
|
|
59
|
-
def
|
|
82
|
+
# @return [::String]
|
|
83
|
+
def evaluate(runtime)
|
|
60
84
|
return value unless @ast
|
|
61
85
|
|
|
62
|
-
|
|
63
|
-
# this at the top will recursively print the tree (as a well-formatted spreadsheet formula)
|
|
64
|
-
"=#{@ast}"
|
|
86
|
+
"=#{@ast.evaluate(runtime)}"
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
end
|
data/lib/csv_plus_plus/cli.rb
CHANGED
|
@@ -1,71 +1,96 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require 'optparse'
|
|
4
|
-
|
|
5
4
|
module CSVPlusPlus
|
|
6
5
|
# Handle running the application with the given CLI flags
|
|
7
6
|
#
|
|
8
7
|
# @attr options [Options, nil] The parsed CLI options
|
|
9
8
|
class CLI
|
|
9
|
+
extend ::T::Sig
|
|
10
|
+
|
|
11
|
+
sig { returns(::CSVPlusPlus::Options) }
|
|
10
12
|
attr_accessor :options
|
|
11
13
|
|
|
14
|
+
sig { void }
|
|
12
15
|
# Handle CLI flags and launch the compiler
|
|
13
16
|
#
|
|
14
17
|
# @return [CLI]
|
|
15
18
|
def self.launch_compiler!
|
|
16
19
|
cli = new
|
|
17
|
-
cli.parse_options!
|
|
18
20
|
cli.main
|
|
19
21
|
rescue ::StandardError => e
|
|
20
|
-
cli.handle_error(e)
|
|
22
|
+
::T.must(cli).handle_error(e)
|
|
21
23
|
exit(1)
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
sig { void }
|
|
27
|
+
# Initialize and parse the CLI flags provided to the program
|
|
28
|
+
def initialize
|
|
29
|
+
@options = ::T.let(::CSVPlusPlus::Options.new, ::CSVPlusPlus::Options)
|
|
30
|
+
parse_options!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { void }
|
|
24
34
|
# Compile the given template using the given CLI flags
|
|
25
35
|
def main
|
|
26
|
-
parse_options! unless @options
|
|
27
36
|
::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, @options)
|
|
28
37
|
end
|
|
29
38
|
|
|
39
|
+
sig { params(error: ::StandardError).void }
|
|
30
40
|
# Nicely handle a given error. How it's handled depends on if it's our error and if @options.verbose
|
|
31
41
|
#
|
|
32
42
|
# @param error [CSVPlusPlus::Error, Google::Apis::ClientError, StandardError]
|
|
33
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
|
+
|
|
34
47
|
case error
|
|
35
|
-
when ::CSVPlusPlus::Error
|
|
48
|
+
when ::CSVPlusPlus::Error::Error
|
|
36
49
|
handle_internal_error(error)
|
|
37
50
|
when ::Google::Apis::ClientError
|
|
38
51
|
handle_google_error(error)
|
|
39
52
|
else
|
|
40
|
-
|
|
41
|
-
warn(error.message)
|
|
53
|
+
unhandled_error(error)
|
|
42
54
|
end
|
|
43
55
|
end
|
|
44
56
|
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
sig { void }
|
|
45
60
|
# Handle the supplied command line options, setting +@options+ or throw an error if anything is invalid
|
|
46
61
|
def parse_options!
|
|
47
|
-
@options = ::CSVPlusPlus::Options.new
|
|
48
62
|
option_parser.parse!
|
|
49
63
|
validate_options
|
|
50
64
|
rescue ::OptionParser::InvalidOption => e
|
|
51
|
-
raise(::CSVPlusPlus::Error, e.message)
|
|
65
|
+
raise(::CSVPlusPlus::Error::Error, e.message)
|
|
52
66
|
end
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
58
76
|
|
|
59
|
-
|
|
77
|
+
return unless @options.verbose
|
|
60
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 }
|
|
61
84
|
def handle_internal_error(error)
|
|
62
|
-
|
|
85
|
+
case error
|
|
86
|
+
when ::CSVPlusPlus::Error::SyntaxError
|
|
63
87
|
warn(@options.verbose ? error.to_verbose_trace : error.to_trace)
|
|
64
88
|
else
|
|
65
89
|
warn(error.message)
|
|
66
90
|
end
|
|
67
91
|
end
|
|
68
92
|
|
|
93
|
+
sig { params(error: ::Google::Apis::ClientError).void }
|
|
69
94
|
def handle_google_error(error)
|
|
70
95
|
warn("Error making Google Sheets API request: #{error.message}")
|
|
71
96
|
return unless @options.verbose
|
|
@@ -73,14 +98,16 @@ module CSVPlusPlus
|
|
|
73
98
|
warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
|
|
74
99
|
end
|
|
75
100
|
|
|
101
|
+
sig { void }
|
|
76
102
|
def validate_options
|
|
77
103
|
error_message = @options.validate
|
|
78
104
|
return if error_message.nil?
|
|
79
105
|
|
|
80
106
|
puts(option_parser)
|
|
81
|
-
raise(::CSVPlusPlus::Error, error_message)
|
|
107
|
+
raise(::CSVPlusPlus::Error::Error, error_message)
|
|
82
108
|
end
|
|
83
109
|
|
|
110
|
+
sig { returns(::OptionParser) }
|
|
84
111
|
def option_parser
|
|
85
112
|
::OptionParser.new do |parser|
|
|
86
113
|
parser.on('-h', '--help', 'Show help information') do
|
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,19 +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
|
|
11
12
|
|
|
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
|
|
21
|
+
|
|
22
|
+
HEX_STRING_REGEXP = /^#?([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/i
|
|
23
|
+
public_constant :HEX_STRING_REGEXP
|
|
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
|
+
#
|
|
31
|
+
# @return [boolean]
|
|
32
|
+
def self.valid_hex_string?(hex_string)
|
|
33
|
+
!(hex_string.strip =~ ::CSVPlusPlus::Color::HEX_STRING_REGEXP).nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { params(hex_string: ::String).void }
|
|
37
|
+
# Create an instance from a string like "#FFF" or "#FFFFFF"
|
|
13
38
|
#
|
|
14
39
|
# @param hex_string [String] The hex string input to parse
|
|
40
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
15
41
|
def initialize(hex_string)
|
|
16
|
-
|
|
17
|
-
.gsub(/^#?/, '')
|
|
18
|
-
.match(/([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/i)
|
|
42
|
+
red_hex, green_hex, blue_hex = hex_string.strip.match(::CSVPlusPlus::Color::HEX_STRING_REGEXP)
|
|
19
43
|
&.captures
|
|
20
44
|
&.map { |s| s.length == 1 ? s + s : s }
|
|
45
|
+
raise(::CSVPlusPlus::Error::Error, "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)
|
|
21
50
|
end
|
|
51
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
22
52
|
|
|
53
|
+
sig { returns(::Float) }
|
|
23
54
|
# The percent (decimal between 0-1) of red
|
|
24
55
|
#
|
|
25
56
|
# @return [Numeric]
|
|
@@ -27,6 +58,7 @@ module CSVPlusPlus
|
|
|
27
58
|
hex_to_percent(@red_hex)
|
|
28
59
|
end
|
|
29
60
|
|
|
61
|
+
sig { returns(::Float) }
|
|
30
62
|
# The percent (decimal between 0-1) of green
|
|
31
63
|
#
|
|
32
64
|
# @return [Numeric]
|
|
@@ -34,6 +66,7 @@ module CSVPlusPlus
|
|
|
34
66
|
hex_to_percent(@green_hex)
|
|
35
67
|
end
|
|
36
68
|
|
|
69
|
+
sig { returns(::Float) }
|
|
37
70
|
# The percent (decimal between 0-1) of blue
|
|
38
71
|
#
|
|
39
72
|
# @return [Numeric]
|
|
@@ -41,18 +74,15 @@ module CSVPlusPlus
|
|
|
41
74
|
hex_to_percent(@blue_hex)
|
|
42
75
|
end
|
|
43
76
|
|
|
77
|
+
sig { returns(::String) }
|
|
44
78
|
# Create a hex representation of the color (without a '#')
|
|
45
79
|
#
|
|
46
|
-
# @return [String]
|
|
80
|
+
# @return [::String]
|
|
47
81
|
def to_hex
|
|
48
82
|
[@red_hex, @green_hex, @blue_hex].join
|
|
49
83
|
end
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
def to_s
|
|
53
|
-
"Color(r: #{@red_hex}, g: #{@green_hex}, b: #{@blue_hex})"
|
|
54
|
-
end
|
|
55
|
-
|
|
85
|
+
sig { params(other: ::Object).returns(::T::Boolean) }
|
|
56
86
|
# @return [boolean]
|
|
57
87
|
def ==(other)
|
|
58
88
|
other.is_a?(self.class) &&
|
|
@@ -63,6 +93,7 @@ module CSVPlusPlus
|
|
|
63
93
|
|
|
64
94
|
private
|
|
65
95
|
|
|
96
|
+
sig { params(hex: ::String).returns(::Float) }
|
|
66
97
|
def hex_to_percent(hex)
|
|
67
98
|
hex.to_i(16) / 255.0
|
|
68
99
|
end
|