csv_plus_plus 0.1.2 → 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 +1 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +23 -13
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +82 -60
- data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
- data/lib/csv_plus_plus/entities/boolean.rb +18 -9
- data/lib/csv_plus_plus/entities/builtins.rb +23 -9
- data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
- data/lib/csv_plus_plus/entities/date.rb +38 -5
- data/lib/csv_plus_plus/entities/entity.rb +27 -61
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +23 -11
- data/lib/csv_plus_plus/entities/function_call.rb +24 -9
- data/lib/csv_plus_plus/entities/number.rb +24 -10
- data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
- data/lib/csv_plus_plus/entities/string.rb +19 -6
- data/lib/csv_plus_plus/entities/variable.rb +16 -4
- data/lib/csv_plus_plus/entities.rb +20 -13
- data/lib/csv_plus_plus/error/error.rb +11 -1
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
- data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +1 -0
- data/lib/csv_plus_plus/google_api_client.rb +7 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
- 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 +1 -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 -158
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
- data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
- data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
- data/lib/csv_plus_plus/row.rb +53 -12
- 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 +34 -191
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +62 -35
- 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 +1 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
- 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 -30
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +29 -12
- metadata +18 -14
- 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/expand.rb +0 -20
- data/lib/csv_plus_plus/graph.rb +0 -62
- 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/google_sheet_modifier.rb +0 -77
- 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
@@ -29,7 +29,6 @@ And can be compiled into a `.xlsx` file by:
|
|
29
29
|
$ csv++ -n 'My Stock Tracker' -o mystocks.xlsx mystocks.csvpp
|
30
30
|
```
|
31
31
|
|
32
|
-
|
33
32
|
See the [Language Reference](./docs/LANGUAGE_REFERENCE.md) for a full explanation of features.
|
34
33
|
|
35
34
|
## Installing
|
@@ -38,7 +37,7 @@ Just install it via rubygems (homebrew and debian packages are in the works):
|
|
38
37
|
|
39
38
|
`$ gem install csv_plus_plus`
|
40
39
|
|
41
|
-
or if you want the very latest changes, clone
|
40
|
+
or if you want the very latest changes, clone this repository and run:
|
42
41
|
|
43
42
|
`$ rake gem:install`
|
44
43
|
|
@@ -1,3 +1,12 @@
|
|
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
|
+
|
1
10
|
## v0.1.2
|
2
11
|
|
3
12
|
- var=... modifier which allows binding a variable to a cell
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -5,57 +6,106 @@ module CSVPlusPlus
|
|
5
6
|
#
|
6
7
|
# @attr_reader timings [Array<Benchmark::Tms>] +Benchmark+ timings that have been accumulated by each step of
|
7
8
|
# compilation
|
8
|
-
# @attr_reader benchmark [Benchmark] A +Benchmark+ instance
|
9
|
-
|
10
|
-
|
9
|
+
# @attr_reader benchmark [Benchmark::Report] A +Benchmark+ instance
|
10
|
+
class BenchmarkedCompiler < ::CSVPlusPlus::Compiler
|
11
|
+
extend ::T::Sig
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
::Benchmark.benchmark(::Benchmark::CAPTION, 25, ::Benchmark::FORMAT, '> Total') do |x|
|
15
|
-
# compiler = new(options:, runtime:, benchmark: x)
|
16
|
-
compiler.extend(self)
|
17
|
-
compiler.benchmark = x
|
13
|
+
sig { returns(::Benchmark::Report) }
|
14
|
+
attr_reader :benchmark
|
18
15
|
|
19
|
-
|
16
|
+
sig { returns(::T::Array[::Benchmark::Tms]) }
|
17
|
+
attr_reader :timings
|
20
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)
|
21
36
|
[compiler.timings.reduce(:+)]
|
22
37
|
end
|
23
38
|
end
|
24
39
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
40
|
+
sig do
|
41
|
+
params(
|
42
|
+
benchmark: ::Benchmark::Report,
|
43
|
+
options: ::CSVPlusPlus::Options,
|
44
|
+
runtime: ::CSVPlusPlus::Runtime::Runtime
|
45
|
+
).void
|
29
46
|
end
|
47
|
+
# @param benchmark [::Benchmark::Report]
|
48
|
+
def initialize(benchmark:, options:, runtime:)
|
49
|
+
super(options:, runtime:)
|
30
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 }
|
31
56
|
# Time the Compiler#outputting! stage
|
32
|
-
|
33
|
-
|
57
|
+
# rubocop:disable Naming/BlockForwarding
|
58
|
+
def outputting!(&block)
|
59
|
+
time_stage('Writing the spreadsheet') { super(&block) }
|
34
60
|
end
|
61
|
+
# rubocop:enable Naming/BlockForwarding
|
35
62
|
|
36
63
|
protected
|
37
64
|
|
65
|
+
sig { override.void }
|
38
66
|
def parse_code_section!
|
39
67
|
time_stage('Parsing code section') { super }
|
40
68
|
end
|
41
69
|
|
70
|
+
sig { override.returns(::T::Array[::CSVPlusPlus::Row]) }
|
42
71
|
def parse_csv_section!
|
43
72
|
time_stage('Parsing CSV section') { super }
|
44
73
|
end
|
45
74
|
|
46
|
-
|
47
|
-
|
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) }
|
48
86
|
end
|
87
|
+
# rubocop:enable Naming/BlockForwarding
|
49
88
|
|
89
|
+
sig do
|
90
|
+
override
|
91
|
+
.params(template: ::CSVPlusPlus::Template)
|
92
|
+
.returns(::T::Array[::T::Array[::CSVPlusPlus::Entities::Entity]])
|
93
|
+
end
|
50
94
|
def resolve_all_cells!(template)
|
51
95
|
time_stage('Resolving each cell') { super(template) }
|
52
96
|
end
|
53
97
|
|
54
98
|
private
|
55
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
|
56
106
|
def time_stage(stage, &block)
|
57
|
-
ret = nil
|
58
|
-
@timings << @benchmark.report(stage) { ret = block.call }
|
107
|
+
ret = ::T.let(nil, ::T.nilable(::T.type_parameter(:R)))
|
108
|
+
@timings << ::T.unsafe(@benchmark.report(stage) { ret = block.call })
|
59
109
|
ret
|
60
110
|
end
|
61
111
|
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 'modifier'
|
4
|
-
require_relative 'parser/cell_value.tab'
|
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::Parser::CellValue.new.parse(value, runtime)
|
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,32 +1,42 @@
|
|
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]
|
@@ -44,22 +54,18 @@ module CSVPlusPlus
|
|
44
54
|
end
|
45
55
|
end
|
46
56
|
|
57
|
+
private
|
58
|
+
|
59
|
+
sig { void }
|
47
60
|
# Handle the supplied command line options, setting +@options+ or throw an error if anything is invalid
|
48
61
|
def parse_options!
|
49
|
-
@options = ::CSVPlusPlus::Options.new
|
50
62
|
option_parser.parse!
|
51
63
|
validate_options
|
52
64
|
rescue ::OptionParser::InvalidOption => e
|
53
65
|
raise(::CSVPlusPlus::Error::Error, e.message)
|
54
66
|
end
|
55
67
|
|
56
|
-
|
57
|
-
def to_s
|
58
|
-
"CLI(options: #{options})"
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
68
|
+
sig { params(error: ::StandardError).void }
|
63
69
|
# An error was thrown that we weren't planning on
|
64
70
|
def unhandled_error(error)
|
65
71
|
warn(
|
@@ -74,6 +80,7 @@ module CSVPlusPlus
|
|
74
80
|
warn("Cause: #{error.cause}") if error.cause
|
75
81
|
end
|
76
82
|
|
83
|
+
sig { params(error: ::CSVPlusPlus::Error::Error).void }
|
77
84
|
def handle_internal_error(error)
|
78
85
|
case error
|
79
86
|
when ::CSVPlusPlus::Error::SyntaxError
|
@@ -83,6 +90,7 @@ module CSVPlusPlus
|
|
83
90
|
end
|
84
91
|
end
|
85
92
|
|
93
|
+
sig { params(error: ::Google::Apis::ClientError).void }
|
86
94
|
def handle_google_error(error)
|
87
95
|
warn("Error making Google Sheets API request: #{error.message}")
|
88
96
|
return unless @options.verbose
|
@@ -90,6 +98,7 @@ module CSVPlusPlus
|
|
90
98
|
warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
|
91
99
|
end
|
92
100
|
|
101
|
+
sig { void }
|
93
102
|
def validate_options
|
94
103
|
error_message = @options.validate
|
95
104
|
return if error_message.nil?
|
@@ -98,6 +107,7 @@ module CSVPlusPlus
|
|
98
107
|
raise(::CSVPlusPlus::Error::Error, error_message)
|
99
108
|
end
|
100
109
|
|
110
|
+
sig { returns(::OptionParser) }
|
101
111
|
def option_parser
|
102
112
|
::OptionParser.new do |parser|
|
103
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,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::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)
|
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
|