csv_plus_plus 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +23 -13
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +32 -7
  9. data/lib/csv_plus_plus/compiler.rb +82 -60
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
  11. data/lib/csv_plus_plus/entities/boolean.rb +18 -9
  12. data/lib/csv_plus_plus/entities/builtins.rb +23 -9
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
  14. data/lib/csv_plus_plus/entities/date.rb +38 -5
  15. data/lib/csv_plus_plus/entities/entity.rb +27 -61
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +23 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +24 -9
  19. data/lib/csv_plus_plus/entities/number.rb +24 -10
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
  21. data/lib/csv_plus_plus/entities/string.rb +19 -6
  22. data/lib/csv_plus_plus/entities/variable.rb +16 -4
  23. data/lib/csv_plus_plus/entities.rb +20 -13
  24. data/lib/csv_plus_plus/error/error.rb +11 -1
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
  28. data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
  29. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  30. data/lib/csv_plus_plus/error.rb +1 -0
  31. data/lib/csv_plus_plus/google_api_client.rb +7 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
  34. data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
  35. data/lib/csv_plus_plus/lexer.rb +24 -0
  36. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  37. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  38. data/lib/csv_plus_plus/modifier/expand.rb +61 -0
  39. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  40. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  41. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  42. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  43. data/lib/csv_plus_plus/modifier.rb +82 -158
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
  46. data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
  48. data/lib/csv_plus_plus/row.rb +53 -12
  49. data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
  50. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
  51. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  52. data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
  53. data/lib/csv_plus_plus/runtime/references.rb +110 -0
  54. data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
  55. data/lib/csv_plus_plus/runtime.rb +34 -191
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +62 -35
  58. data/lib/csv_plus_plus/version.rb +2 -1
  59. data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
  60. data/lib/csv_plus_plus/writer/csv.rb +11 -9
  61. data/lib/csv_plus_plus/writer/excel.rb +9 -2
  62. data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
  64. data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
  65. data/lib/csv_plus_plus/writer/open_document.rb +6 -1
  66. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +29 -12
  69. metadata +18 -14
  70. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  71. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  72. data/lib/csv_plus_plus/data_validation.rb +0 -138
  73. data/lib/csv_plus_plus/expand.rb +0 -20
  74. data/lib/csv_plus_plus/graph.rb +0 -62
  75. data/lib/csv_plus_plus/references.rb +0 -68
  76. data/lib/csv_plus_plus/scope.rb +0 -196
  77. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  78. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  79. 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: d0fb79a1f7a00d60f77289518fb9c6ccac6b059024d5cf66a3f083ec92e77def
4
- data.tar.gz: a35796b982b01171636e27b1d1a9d19d6d414249b0e4634947d5a754cb240146
3
+ metadata.gz: abee03db0f21f9d8c5d3cfb4ec81dbd748568f878458f355cab6db5fc458a509
4
+ data.tar.gz: 6c4e95fa1c780a2d009eaeb58fd48b28c2dd96826b22be02f84d5fc8a06ad31e
5
5
  SHA512:
6
- metadata.gz: 45dd804f7889d65ac5f5c63ccc131d774e7ea24097bfe9449af63a48345ca0b9f16ea9e84c17a73151c9ee8d9d90ce4b01d67b7ced817e831e66781d2e7141e0
7
- data.tar.gz: 21cda263af6d05d5ee396a9d00b4c1c78f1a043d91787dcf0d822e1adb6a97a2326da836cdc3bbc3c78e43e5c342c1bdb9da966884f1d9d8df364e574f334ded
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 the repository and run:
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
- module BenchmarkedCompiler
10
- attr_reader :benchmark, :timings
9
+ # @attr_reader benchmark [Benchmark::Report] A +Benchmark+ instance
10
+ class BenchmarkedCompiler < ::CSVPlusPlus::Compiler
11
+ extend ::T::Sig
11
12
 
12
- # Wrap a +Compiler+ with our instance methods that add benchmarks
13
- def self.with_benchmarks(compiler, &block)
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
- block.call(compiler)
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
- # @param benchmark [Benchmark] A +Benchmark+ instance
26
- def benchmark=(benchmark)
27
- @benchmark = benchmark
28
- @timings = []
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
- def outputting!
33
- time_stage('Writing the spreadsheet') { super }
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
- def expanding
47
- time_stage('Expanding rows') { super }
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
@@ -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
- attr_accessor :ast, :row_index
15
- attr_reader :index, :modifier
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
- # @param row_index [Integer] The cell's row index (starts at 0)
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
- def initialize(row_index:, index:, value:, modifier:)
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
- return if @value.nil? || @value.strip.empty?
71
+ stripped = @value&.strip
46
72
 
47
- @value.strip
73
+ stripped&.empty? ? nil : stripped
48
74
  end
49
75
 
50
- # @return [String]
51
- def to_s
52
- "Cell(index: #{@index}, row_index: #{@row_index}, value: #{@value}, modifier: #{@modifier})"
53
- end
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 to_csv
82
+ # @return [::String]
83
+ def evaluate(runtime)
60
84
  return value unless @ast
61
85
 
62
- # This looks really simple but we're relying on each node of the AST to define #to_s such that calling
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
@@ -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
- # @return [::String]
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
@@ -1,7 +1,6 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './google_options'
4
-
5
4
  module CSVPlusPlus
6
5
  # Individual CLI flags that a user can supply
7
6
  #
@@ -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
- attr_reader :red_hex, :green_hex, :blue_hex
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
- @red_hex, @green_hex, @blue_hex = hex_string.strip.match(::CSVPlusPlus::Color::HEX_STRING_REGEXP)
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
- # @return [::String]
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