csv_plus_plus 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +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
|