keycase 1.0.2 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e6f493b80f7e3db6e7ef62af37c153849e924bc8bdb297fc112758f9d41147e
4
- data.tar.gz: 6b45dfb8ec00157c8e098fb6e9b227206d1aa6804d0b8408afa6c03d983cc853
3
+ metadata.gz: 9269ac3f6277ae7eec6635c9bb3a5187522ba0d2d7fb25544d7f1e4aa9fb76f6
4
+ data.tar.gz: a4ba9ae8a5305896cb5f10775a19f77e7b7ea93d7699c54f628d4e93bc5d84ad
5
5
  SHA512:
6
- metadata.gz: a1f3a2dd6e435edb695b3de39c0fbc13bf746ab9bcf55a2206f8e8cbdc484b862d8aae3813352107ae855a2ba3b4f1cc505daca6a7ab17eff19fc29435d9b30f
7
- data.tar.gz: 37480590ce74499c8ccc9c3fc55611187f311b966ecb48da8de70b26ffa2fab0af89bc04ef50e27de88b60a5430b037552bdca5b3ed4e4dee68201e517781f99
6
+ metadata.gz: c3c1c1d9292ddcbf6105612cf83c6015701729dcd4735237426726bbd38ba73c69f637600d24d420957c386499818997a4d89402e6eadc4a32fcd089e30e47b5
7
+ data.tar.gz: bf20fa1ce9b135f12a080c01bb2fcf55906cd66027273b31c29346a818c070d56471695d2a1b68c0fdd21cea5ca56f4d172410c3c0283d245386d43dac3d18a6
data/README.md CHANGED
@@ -66,10 +66,37 @@ irb --context-mode=1
66
66
 
67
67
  ## Development
68
68
 
69
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
69
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests with the local Ruby version defined in `mise.toml` (currently Ruby 3.4.9). You can also run `bin/console` for an interactive prompt that will allow you to experiment.
70
70
 
71
71
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
72
72
 
73
+ Tasks are defined in `mise.toml`. They run **RuboCop, and RSpec inside Docker** (via Docker Compose), so Docker must be available. Use these tasks for syntax checks and tests across supported Ruby versions.
74
+
75
+ Run **RuboCop** on every supported Ruby image (2.3 through 4.0):
76
+
77
+ ```sh
78
+ mise run -j 1 rubocop
79
+ ```
80
+
81
+ Run **RSpec** the same way:
82
+
83
+ ```sh
84
+ mise run -j 1 rspec
85
+ ```
86
+
87
+ These commands execute the version-specific tasks in order (`rubocop23` … `rubocop40`, `rspec23` … `rspec40`). To run against a **single** Ruby version, use the matching task name, for example:
88
+
89
+ ```sh
90
+ mise run rubocop34
91
+ mise run rspec34
92
+ ```
93
+
94
+ To list all tasks and descriptions:
95
+
96
+ ```sh
97
+ mise tasks
98
+ ```
99
+
73
100
  ## Contributing
74
101
 
75
102
  Bug reports and pull requests are welcome on GitHub at <https://github.com/naoigcat/ruby-keycase>.
data/keycase.gemspec CHANGED
@@ -9,22 +9,22 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ["17925623+naoigcat@users.noreply.github.com"]
10
10
 
11
11
  spec.summary = "Converts the case of strings, symbols, and keys of hash."
12
- spec.description = <<~DESCRIPTION
12
+ spec.description = <<-DESCRIPTION
13
13
  This gem converts the case of strings, symbols, and keys of hash recursively.
14
14
  The convertible cases are camelCase, PascalCase, snake_case, etc.
15
15
  DESCRIPTION
16
16
  spec.homepage = "https://github.com/naoigcat/ruby-keycase"
17
17
  spec.license = "MIT"
18
- spec.required_ruby_version = Gem::Requirement.new(">= 2.0.0")
18
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
19
19
 
20
20
  # Specify which files should be added to the gem when it is released.
21
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.require_paths = ["lib"]
22
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ `git ls-files -z`.split("\x0").select do |f|
25
+ f.match(%r{^lib/|^LICENSE\.md$|^README\.md$|^keycase\.gemspec$})
26
+ end
24
27
  end
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
- spec.require_paths = ["lib"]
28
28
 
29
29
  spec.metadata["homepage_uri"] = spec.homepage
30
30
  spec.metadata["source_code_uri"] = spec.homepage
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
5
+ require_relative "recursive_transform/engine"
6
+
3
7
  module Keycase
4
8
  module CamelCase
5
9
  refine Object do
@@ -7,16 +11,18 @@ module Keycase
7
11
  self
8
12
  end
9
13
 
10
- def with_camel_case_keys
14
+ def with_camel_case_keys(_options = {})
11
15
  self
12
16
  end
13
17
  end
14
18
 
15
19
  refine String do
16
20
  def to_camel_case
17
- gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
21
+ gsub(/(?<=[A-Z])(?=[A-Z][a-z])/) do |_|
22
+ "_"
23
+ end.gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
18
24
  "_"
19
- end.gsub(/(?<=\b|\W|_)[0-9A-Za-z]+(?=\b|\W|_)/) do |matched| # rubocop:disable Style/SymbolProc
25
+ end.gsub(/(?<=\b|\W|_)[0-9A-Za-z]+(?=\b|\W|_)/) do |matched|
20
26
  matched.capitalize
21
27
  end.sub(/^(?:\W|_)*([A-Z]+(?=[A-Z][0-9A-Za-z]|\d|$)|[A-Z][a-z])/) do |_|
22
28
  Regexp.last_match(1).downcase
@@ -33,17 +39,27 @@ module Keycase
33
39
  end
34
40
 
35
41
  refine Array do
36
- def with_camel_case_keys
37
- map do |value| # rubocop:disable Style/SymbolProc
38
- value.with_camel_case_keys
42
+ def with_camel_case_keys(options = {})
43
+ Keycase::RecursiveTransform::Engine.transform_array(
44
+ self,
45
+ ::Set.new,
46
+ 0,
47
+ options[:max_depth]
48
+ ) do |key|
49
+ key.to_camel_case
39
50
  end
40
51
  end
41
52
  end
42
53
 
43
54
  refine Hash do
44
- def with_camel_case_keys
45
- each_with_object({}) do |(key, value), memo|
46
- memo[key.to_camel_case] = value.with_camel_case_keys
55
+ def with_camel_case_keys(options = {})
56
+ Keycase::RecursiveTransform::Engine.transform_hash(
57
+ self,
58
+ ::Set.new,
59
+ 0,
60
+ options[:max_depth]
61
+ ) do |key|
62
+ key.to_camel_case
47
63
  end
48
64
  end
49
65
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
5
+ require_relative "recursive_transform/engine"
6
+
3
7
  module Keycase
4
8
  module KebabCase
5
9
  refine Object do
@@ -7,14 +11,16 @@ module Keycase
7
11
  self
8
12
  end
9
13
 
10
- def with_kebab_case_keys
14
+ def with_kebab_case_keys(_options = {})
11
15
  self
12
16
  end
13
17
  end
14
18
 
15
19
  refine String do
16
20
  def to_kebab_case
17
- gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
21
+ gsub(/(?<=[A-Z])(?=[A-Z][a-z])/) do |_|
22
+ "-"
23
+ end.gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
18
24
  "-"
19
25
  end.gsub(/(?<=\b|\W|_)[0-9A-Za-z]+(?=\b|\W|_)/) do |matched|
20
26
  "-#{matched.downcase}"
@@ -29,17 +35,27 @@ module Keycase
29
35
  end
30
36
 
31
37
  refine Array do
32
- def with_kebab_case_keys
33
- map do |value| # rubocop:disable Style/SymbolProc
34
- value.with_kebab_case_keys
38
+ def with_kebab_case_keys(options = {})
39
+ Keycase::RecursiveTransform::Engine.transform_array(
40
+ self,
41
+ ::Set.new,
42
+ 0,
43
+ options[:max_depth]
44
+ ) do |key|
45
+ key.to_kebab_case
35
46
  end
36
47
  end
37
48
  end
38
49
 
39
50
  refine Hash do
40
- def with_kebab_case_keys
41
- each_with_object({}) do |(key, value), memo|
42
- memo[key.to_kebab_case] = value.with_kebab_case_keys
51
+ def with_kebab_case_keys(options = {})
52
+ Keycase::RecursiveTransform::Engine.transform_hash(
53
+ self,
54
+ ::Set.new,
55
+ 0,
56
+ options[:max_depth]
57
+ ) do |key|
58
+ key.to_kebab_case
43
59
  end
44
60
  end
45
61
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
5
+ require_relative "recursive_transform/engine"
6
+
3
7
  module Keycase
4
8
  module PascalCase
5
9
  refine Object do
@@ -7,16 +11,18 @@ module Keycase
7
11
  self
8
12
  end
9
13
 
10
- def with_pascal_case_keys
14
+ def with_pascal_case_keys(_options = {})
11
15
  self
12
16
  end
13
17
  end
14
18
 
15
19
  refine String do
16
20
  def to_pascal_case
17
- gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
21
+ gsub(/(?<=[A-Z])(?=[A-Z][a-z])/) do |_|
22
+ "_"
23
+ end.gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
18
24
  "_"
19
- end.gsub(/(?<=\b|\W|_)[0-9A-Za-z]+(?=\b|\W|_)/) do |matched| # rubocop:disable Style/SymbolProc
25
+ end.gsub(/(?<=\b|\W|_)[0-9A-Za-z]+(?=\b|\W|_)/) do |matched|
20
26
  matched.capitalize
21
27
  end.sub(/^(?:\W|_)*([A-Z]+(?=[A-Z][0-9A-Za-z]|\d|$)|[A-Z][a-z])/) do |_|
22
28
  Regexp.last_match(1).capitalize
@@ -33,17 +39,27 @@ module Keycase
33
39
  end
34
40
 
35
41
  refine Array do
36
- def with_pascal_case_keys
37
- map do |value| # rubocop:disable Style/SymbolProc
38
- value.with_pascal_case_keys
42
+ def with_pascal_case_keys(options = {})
43
+ Keycase::RecursiveTransform::Engine.transform_array(
44
+ self,
45
+ ::Set.new,
46
+ 0,
47
+ options[:max_depth]
48
+ ) do |key|
49
+ key.to_pascal_case
39
50
  end
40
51
  end
41
52
  end
42
53
 
43
54
  refine Hash do
44
- def with_pascal_case_keys
45
- each_with_object({}) do |(key, value), memo|
46
- memo[key.to_pascal_case] = value.with_pascal_case_keys
55
+ def with_pascal_case_keys(options = {})
56
+ Keycase::RecursiveTransform::Engine.transform_hash(
57
+ self,
58
+ ::Set.new,
59
+ 0,
60
+ options[:max_depth]
61
+ ) do |key|
62
+ key.to_pascal_case
47
63
  end
48
64
  end
49
65
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+
5
+ module Keycase
6
+ module RecursiveTransform
7
+ module Engine
8
+ class << self
9
+ def transform_hash(hash, visiting, depth, max_depth, &key_converter)
10
+ check_depth!(depth, max_depth)
11
+
12
+ oid = hash.object_id
13
+ raise CircularStructureError, "Keycase detected a circular reference in a Hash" if visiting.include?(oid)
14
+
15
+ visiting.add(oid)
16
+ begin
17
+ hash.each_with_object({}) do |(key, value), memo|
18
+ new_key = key_converter.call(key)
19
+ if memo.key?(new_key)
20
+ message = "Keycase detected a key collision: #{key.inspect} converted to " \
21
+ "#{new_key.inspect}, which already exists in the transformed hash"
22
+ raise KeyCollisionError, message
23
+ end
24
+
25
+ memo[new_key] = transform_value(
26
+ value,
27
+ visiting,
28
+ depth + 1,
29
+ max_depth,
30
+ &key_converter
31
+ )
32
+ end
33
+ ensure
34
+ visiting.delete(oid)
35
+ end
36
+ end
37
+
38
+ def transform_array(array, visiting, depth, max_depth, &key_converter)
39
+ check_depth!(depth, max_depth)
40
+
41
+ oid = array.object_id
42
+ raise CircularStructureError, "Keycase detected a circular reference in an Array" if visiting.include?(oid)
43
+
44
+ visiting.add(oid)
45
+ begin
46
+ array.map do |element|
47
+ transform_value(
48
+ element,
49
+ visiting,
50
+ depth + 1,
51
+ max_depth,
52
+ &key_converter
53
+ )
54
+ end
55
+ ensure
56
+ visiting.delete(oid)
57
+ end
58
+ end
59
+
60
+ def transform_value(value, visiting, depth, max_depth, &key_converter)
61
+ case value
62
+ when Hash
63
+ transform_hash(value, visiting, depth, max_depth, &key_converter)
64
+ when Array
65
+ transform_array(value, visiting, depth, max_depth, &key_converter)
66
+ else
67
+ value
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def check_depth!(depth, max_depth)
74
+ return if max_depth.nil?
75
+
76
+ raise StructureTooDeepError, "Keycase nesting exceeds max_depth (#{max_depth})" if depth > max_depth
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keycase
4
+ # Signals that recursive key conversion cannot finish because the input graph loops.
5
+ class CircularStructureError < StandardError; end
6
+
7
+ # Signals that conversion would overwrite data by mapping multiple source keys to one key.
8
+ class KeyCollisionError < StandardError; end
9
+
10
+ # Signals that the caller's depth limit rejected input that is too deeply nested.
11
+ class StructureTooDeepError < StandardError; end
12
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
5
+ require_relative "recursive_transform/engine"
6
+
3
7
  module Keycase
4
8
  module SnakeCase
5
9
  refine Object do
@@ -7,14 +11,16 @@ module Keycase
7
11
  self
8
12
  end
9
13
 
10
- def with_snake_case_keys
14
+ def with_snake_case_keys(_options = {})
11
15
  self
12
16
  end
13
17
  end
14
18
 
15
19
  refine String do
16
20
  def to_snake_case
17
- gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
21
+ gsub(/(?<=[A-Z])(?=[A-Z][a-z])/) do |_|
22
+ "_"
23
+ end.gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
18
24
  "_"
19
25
  end.gsub(/(?<=\b|\W|_)[0-9A-Za-z]+(?=\b|\W|_)/) do |matched|
20
26
  "_#{matched.downcase}"
@@ -29,17 +35,27 @@ module Keycase
29
35
  end
30
36
 
31
37
  refine Array do
32
- def with_snake_case_keys
33
- map do |value| # rubocop:disable Style/SymbolProc
34
- value.with_snake_case_keys
38
+ def with_snake_case_keys(options = {})
39
+ Keycase::RecursiveTransform::Engine.transform_array(
40
+ self,
41
+ ::Set.new,
42
+ 0,
43
+ options[:max_depth]
44
+ ) do |key|
45
+ key.to_snake_case
35
46
  end
36
47
  end
37
48
  end
38
49
 
39
50
  refine Hash do
40
- def with_snake_case_keys
41
- each_with_object({}) do |(key, value), memo|
42
- memo[key.to_snake_case] = value.with_snake_case_keys
51
+ def with_snake_case_keys(options = {})
52
+ Keycase::RecursiveTransform::Engine.transform_hash(
53
+ self,
54
+ ::Set.new,
55
+ 0,
56
+ options[:max_depth]
57
+ ) do |key|
58
+ key.to_snake_case
43
59
  end
44
60
  end
45
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Keycase
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/keycase.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "keycase/version"
4
+ require "keycase/recursive_transform/errors"
5
+ require "keycase/recursive_transform/engine"
4
6
  require "keycase/camel_case"
5
7
  require "keycase/kebab_case"
6
8
  require "keycase/pascal_case"
metadata CHANGED
@@ -1,38 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keycase
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - naoigcat
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2022-12-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description: |
14
- This gem converts the case of strings, symbols, and keys of hash recursively.
15
- The convertible cases are camelCase, PascalCase, snake_case, etc.
12
+ description: |2
13
+ This gem converts the case of strings, symbols, and keys of hash recursively.
14
+ The convertible cases are camelCase, PascalCase, snake_case, etc.
16
15
  email:
17
16
  - 17925623+naoigcat@users.noreply.github.com
18
17
  executables: []
19
18
  extensions: []
20
19
  extra_rdoc_files: []
21
20
  files:
22
- - ".gitignore"
23
- - ".rspec"
24
- - ".rubocop.yml"
25
- - Gemfile
26
21
  - LICENSE.md
27
22
  - README.md
28
- - Rakefile
29
- - bin/console
30
- - bin/setup
31
23
  - keycase.gemspec
32
24
  - lib/keycase.rb
33
25
  - lib/keycase/camel_case.rb
34
26
  - lib/keycase/kebab_case.rb
35
27
  - lib/keycase/pascal_case.rb
28
+ - lib/keycase/recursive_transform/engine.rb
29
+ - lib/keycase/recursive_transform/errors.rb
36
30
  - lib/keycase/snake_case.rb
37
31
  - lib/keycase/version.rb
38
32
  homepage: https://github.com/naoigcat/ruby-keycase
@@ -42,7 +36,6 @@ metadata:
42
36
  homepage_uri: https://github.com/naoigcat/ruby-keycase
43
37
  source_code_uri: https://github.com/naoigcat/ruby-keycase
44
38
  rubygems_mfa_required: 'true'
45
- post_install_message:
46
39
  rdoc_options: []
47
40
  require_paths:
48
41
  - lib
@@ -50,15 +43,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
43
  requirements:
51
44
  - - ">="
52
45
  - !ruby/object:Gem::Version
53
- version: 2.0.0
46
+ version: 2.3.0
54
47
  required_rubygems_version: !ruby/object:Gem::Requirement
55
48
  requirements:
56
49
  - - ">="
57
50
  - !ruby/object:Gem::Version
58
51
  version: '0'
59
52
  requirements: []
60
- rubygems_version: 3.3.23
61
- signing_key:
53
+ rubygems_version: 3.6.9
62
54
  specification_version: 4
63
55
  summary: Converts the case of strings, symbols, and keys of hash.
64
56
  test_files: []
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
-
13
- # bundler
14
- Gemfile.lock
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,39 +0,0 @@
1
- require:
2
- - rubocop-rake
3
- - rubocop-rspec
4
-
5
- AllCops:
6
- TargetRubyVersion: 2.7
7
- DisplayCopNames: true
8
- DisplayStyleGuide: true
9
- NewCops: enable
10
-
11
- Gemspec/RequiredRubyVersion:
12
- Enabled: false
13
-
14
- Metrics/BlockLength:
15
- Enabled: false
16
-
17
- Metrics/MethodLength:
18
- Enabled: false
19
-
20
- Naming/VariableNumber:
21
- Enabled: false
22
-
23
- Style/Documentation:
24
- Enabled: false
25
-
26
- Style/MultilineBlockChain:
27
- Enabled: false
28
-
29
- Style/StringLiterals:
30
- EnforcedStyle: double_quotes
31
-
32
- Style/StringLiteralsInInterpolation:
33
- EnforcedStyle: double_quotes
34
-
35
- RSpec/ExampleLength:
36
- Enabled: false
37
-
38
- RSpec/MultipleExpectations:
39
- Enabled: false
data/Gemfile DELETED
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
7
- gem "rake", "~> 12.0"
8
- gem "rspec", "~> 3.0"
9
- gem "rubocop"
10
- gem "rubocop-rake"
11
- gem "rubocop-rspec"
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
- require "rubocop/rake_task"
6
-
7
- RSpec::Core::RakeTask.new(:spec)
8
- RuboCop::RakeTask.new
9
-
10
- task default: :spec
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "keycase"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here