private_please 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -5
  4. data/CHANGELOG +4 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +58 -98
  8. data/Rakefile +6 -2
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/lib/private_please/config.rb +15 -0
  12. data/lib/private_please/methods_calls_tracker.rb +44 -0
  13. data/lib/private_please/reporting/simple_text.rb +43 -0
  14. data/lib/private_please/reporting/templates/simple.txt.erb +11 -0
  15. data/lib/private_please/storage/called_methods_memory_store.rb +20 -0
  16. data/lib/private_please/tracking/debug/trace_point_data_logger.rb +81 -0
  17. data/lib/private_please/tracking/method_details.rb +27 -0
  18. data/lib/private_please/tracking/result.rb +25 -0
  19. data/lib/private_please/tracking/trace_point_details.rb +41 -0
  20. data/lib/private_please/tracking/trace_point_processor.rb +71 -0
  21. data/lib/private_please/utils/ruby_utils.rb +44 -0
  22. data/lib/private_please/utils/source_file_utils.rb +20 -0
  23. data/lib/private_please/utils/two_level_stack.rb +13 -0
  24. data/lib/private_please/version.rb +2 -1
  25. data/lib/private_please.rb +27 -34
  26. data/private_please.gemspec +32 -25
  27. metadata +106 -185
  28. data/Guardfile +0 -8
  29. data/LICENSE +0 -22
  30. data/TODO +0 -20
  31. data/bin/pp_ruby +0 -62
  32. data/bin/ruby_pp +0 -62
  33. data/doc/dev_notes.txt +0 -32
  34. data/doc/fixtures/complex.rb +0 -103
  35. data/doc/fixtures/empty_class.rb +0 -7
  36. data/doc/fixtures/fixture_helper.rb +0 -8
  37. data/doc/fixtures/sample.rb +0 -57
  38. data/lib/private_please/candidate.rb +0 -49
  39. data/lib/private_please/report/table.rb +0 -44
  40. data/lib/private_please/report.rb +0 -6
  41. data/lib/private_please/reporter/base.rb +0 -14
  42. data/lib/private_please/reporter/data_compiler.rb +0 -82
  43. data/lib/private_please/reporter/helpers/options_helpers.rb +0 -37
  44. data/lib/private_please/reporter/helpers/text_table_helpers.rb +0 -9
  45. data/lib/private_please/reporter/simple_text.rb +0 -18
  46. data/lib/private_please/reporter/templates/simple.txt.erb +0 -80
  47. data/lib/private_please/reporter.rb +0 -8
  48. data/lib/private_please/ruby_backports.rb +0 -22
  49. data/lib/private_please/storage/calls_store.rb +0 -41
  50. data/lib/private_please/storage/candidates_store.rb +0 -52
  51. data/lib/private_please/storage/methods_names.rb +0 -10
  52. data/lib/private_please/storage/methods_names_bucket.rb +0 -69
  53. data/lib/private_please/storage.rb +0 -8
  54. data/lib/private_please/tracking/extension.rb +0 -12
  55. data/lib/private_please/tracking/instrumentor.rb +0 -49
  56. data/lib/private_please/tracking/instruments_all_methods_below.rb +0 -28
  57. data/lib/private_please/tracking/instruments_automatically_all_methods_in_all_classes.rb +0 -68
  58. data/lib/private_please/tracking/line_change_tracker.rb +0 -26
  59. data/lib/private_please/tracking/load_utils/gem_utils.rb +0 -49
  60. data/lib/private_please/tracking/load_utils/standard_lib_utils.rb +0 -38
  61. data/lib/private_please/tracking/load_utils.rb +0 -32
  62. data/lib/private_please/tracking/utils.rb +0 -34
  63. data/lib/private_please/tracking.rb +0 -49
  64. data/sample.rb +0 -68
  65. data/spec/01_tracking_candidate_methods/excluding_gems_and_standard_libraries_spec.rb +0 -31
  66. data/spec/01_tracking_candidate_methods/explicitely_with_the_private_please_command_spec.rb +0 -118
  67. data/spec/01_tracking_candidate_methods/load_utils_spec.rb +0 -40
  68. data/spec/01_tracking_candidate_methods/systematically_in_auto_mode_spec.rb +0 -185
  69. data/spec/03_logging_calls_on_candidate_methods_spec.rb +0 -37
  70. data/spec/04_instrumented_program_activity_observation_result_spec.rb +0 -86
  71. data/spec/05_reporting/report_table_spec.rb +0 -51
  72. data/spec/06_at_exit_spec.rb +0 -18
  73. data/spec/_helpers/assert_helpers.rb +0 -76
  74. data/spec/fixtures/bug_22.rb +0 -17
  75. data/spec/fixtures/bug_30.rb +0 -6
  76. data/spec/fixtures/issue_25.rb +0 -12
  77. data/spec/fixtures/sample_class_for_report.rb +0 -56
  78. data/spec/fixtures/sample_class_with_all_calls_combinations.rb +0 -69
  79. data/spec/sandbox/Gemfile +0 -4
  80. data/spec/sandbox/Gemfile.lock +0 -14
  81. data/spec/sandbox/README.txt +0 -26
  82. data/spec/sandbox/normal.rb +0 -7
  83. data/spec/sandbox/normal_prepended_with_require.rb +0 -8
  84. data/spec/spec_helper.rb +0 -38
  85. data/spec/units/calls_store_spec.rb +0 -15
  86. data/spec/units/candidates_store_spec.rb +0 -55
  87. data/spec/units/reporter/data_compiler_spec.rb +0 -12
  88. data/spec/units/reporter/fixtures/simple_case_1.rb +0 -30
  89. data/spec/units/reporter/fixtures/simple_case_2.rb +0 -55
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cfbf97349bffd46dd59b21ce1623036ea7166cf2
4
+ data.tar.gz: 8b4c879e00f34783fc9c858a47e168733f122043
5
+ SHA512:
6
+ metadata.gz: 87e54637f8b8c0cf74130345f1fe3fe99250fb0491ccdd6b572887887a9ed43f480bf260c2b6fa4af8df709db86d727d74de66ae720ba7aaeffcaa4bb3e10f6f
7
+ data.tar.gz: df4179f71fb104bc4a7c7fd3714b443ec47e51c2c3d33dc1e9a49ec983eb90335c3efe77f3b5ce19b5c244ebf75a2a234e28164e3bb08981d9392a32965051e6
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 1.8.7
1
+ 2.3
data/.travis.yml CHANGED
@@ -1,10 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.8.7"
4
- - "1.9.3"
5
3
  - "2.0.0"
6
- - jruby-18mode # JRuby in 1.8 mode
7
- - jruby-19mode # JRuby in 1.9 mode
4
+ - "2.1.x"
5
+ - "2.2.x"
8
6
 
9
7
  # uncomment this line if your project needs to run something other than `rake`:
10
- script: bundle exec rspec
8
+ script: bundle exec rspec
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ v0.1.0
2
+ - require Ruby 2
3
+ - use a new algorithm (via TracePoint)
4
+
1
5
  v0.0.5
2
6
  - required gems/standard libraries are no longer tracked.
3
7
 
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in private_please.gemspec
4
4
  gemspec
5
+
6
+ gem 'rubocop', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Alain Ravet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,123 +1,83 @@
1
- [![Build Status](https://travis-ci.org/alainravet/private_please.png?branch=master)](https://travis-ci.org/alainravet/private_please) -
2
- [![Code Climate](https://codeclimate.com/github/alainravet/private_please.png)](https://codeclimate.com/github/alainravet/private_please)
3
- tested with Ruby (1.8.7, 1.9.3, and 2.0.0) and JRuby(1.8 and 1.9 mode) - see [.travis.yml](.travis.yml)
1
+ -[![Build Status](https://travis-ci.org/alainravet/private_please.png?branch=master)](https://travis-ci.org/alainravet/private_please) -
2
+ -[![Code Climate](https://codeclimate.com/github/alainravet/private_please.png)](https://codeclimate.com/github/alainravet/private_please)
3
+ -[![Coverage Status](https://coveralls.io/repos/alainravet/private_please/badge.png)](https://coveralls.io/r/alainravet/private_please)
4
+ tested with Ruby 2.0.0..2.3.0 - see [.travis.yml](.travis.yml)
5
+ # PrivatePlease
6
+
7
+ This tool locates public or protected methods that can be made private.
8
+ After you have instrumented the tests suite (see below), it watches the code as the tests are executed and identifies non-private methods that are only called privately.
9
+ As the technique used is tracing, the execution is slowed down substantially (ex: 300%)
10
+
11
+ ## Usage
12
+
13
+ Add this to the top of `spec_helper.rb`:
4
14
 
5
- ## TL;DR :
6
- Given this code
7
15
  ```ruby
8
- # file : big_code
9
- class BigCode
10
- def long_method # the only method that should be public
11
- _part_1
12
- _part_2
13
- end
14
-
15
- # PROBLEM : Those 2 methods were extracted and should be private
16
- def _part_1
17
- # ....
18
- end
19
- def _part_2
20
- # ....
21
- end
22
- end
23
- BigCode.new.long_method
24
- ```
25
- When you run this command
16
+ require 'private_please'
17
+ PrivatePlease.start_tracking
18
+ at_exit { puts PrivatePlease.report }
19
+ ...
20
+
26
21
  ```
27
- $ pp_ruby big_code
28
- ^^^
22
+
23
+ Optionally, you can `exclude_dir` the tests code, local gems, or any source dir that could touched when running the tests.
24
+ ```ruby
25
+ SPECS_DIR = File.dirname(__FILE__)
26
+ require 'private_please'
27
+ PrivatePlease.exclude_dir SPECS_DIR # don't analyze the tests
28
+ PrivatePlease.exclude_dir '/dev/my_local_gem' # specified via path: in Gemfile
29
+ PrivatePlease.exclude_dir '/Applications/RubyMine.app/Contents/rb/testing'
30
+ PrivatePlease.start_tracking
31
+ at_exit { puts PrivatePlease.report.gsub(Rails.root.to_s, '') }
32
+ ...
33
+
29
34
  ```
30
- Then the methods usage is tracked while the program runs and this findings report is output:
35
+
36
+ ### Example of result :
37
+
31
38
  ```
39
+ 265 examples, 0 failures, 2 pending
40
+
32
41
  ====================================================================================
33
- = PrivatePlease report : =
42
+ = Privatazable methods : =
34
43
  ====================================================================================
35
- BigCode
44
+ /app/controllers/application_controller.rb
45
+ 21 ApplicationController::MissingAvatars#must_remind_user_to_setup_avatar?
46
+ 75 ApplicationController#allow?
47
+ /app/controllers/home_controller.rb
48
+ 15 HomeController#redirect_to_default_page_for_signed_in_users
49
+ /app/controllers/mentor_profiles_controller.rb
50
+ 4 MentorProfilesController#after_create_success
36
51
 
37
- * Good candidates : can be made private :
38
- ------------------------------------------
39
- #_part_2
40
- #_part_1
41
-
42
- ====================================================================================
43
52
  ```
44
-
45
53
  ## Installation
46
- ```
47
- $ gem install private_please
48
- ```
49
54
 
50
- ## Usage : the 2 modes (auto and manual)
55
+ Add this line to your application's Gemfile:
51
56
 
52
- You can use *private_please* (*PP*) in either **auto-mode** or in **manual mode**.
53
- Optionally you can customize the report contents with the env. variable ```PP_OPTIONS```.
54
-
55
- - in **auto-mode** (the example above in TL;DR), you simply run your program with ```pp_ruby``` (instead of ```ruby```) and all your code is inspected by *PP* while it runs.
56
- - in **manual mode** you must manually instrument (==add code to) each file you want *PP* to track and inspect.
57
+ ```ruby
58
+ gem 'private_please'
59
+ ```
57
60
 
61
+ And then execute:
58
62
 
59
- ### Auto-mode
60
- PP will run the program and track all the classes and methods that are defined.
63
+ $ bundle
61
64
 
62
- How to :
63
- - step 1 : use ```pp_ruby``` instead of ```ruby```.
65
+ Or install it yourself as:
64
66
 
65
- Example :
66
- ```
67
- $ bundle exec pp_ruby -Ilib normal.rb
68
- ^^^
69
- ```
70
- A report is automatically printed when the program exits
67
+ $ gem install private_please
71
68
 
69
+ ## Development
72
70
 
73
- ### Manual mode
74
- You tell PP which classes and which methods to track.
71
+ 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.
75
72
 
76
- How to :
77
- - step 1 : Instrument your Ruby code
78
- - step 2 : launch program as usual, with ```ruby```.
73
+ 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).
79
74
 
75
+ ## Contributing
80
76
 
81
- Example :
82
- #### step 1 : Instrument your code
83
- ```ruby
84
- # load PP
85
- require 'private_please' <<<<<<< ADD THIS (only 1x in the whole program)
86
- PrivatePlease.pp_automatic_mode_disable <<<<<<< " " " " " " " " "
87
-
88
- class BigCode
89
-
90
- private_please << ~~ JUST ADD THIS in every file you want PP to inspect
91
- <<
92
- << Meaning : "track the code below"
93
-
94
- def long_method # the only method that should be public
95
- _part_1
96
- _part_2
97
- end
98
-
99
- def _part_1
100
- # ....
101
- end
102
- def _part_2
103
- # ....
104
- end
105
- end
106
-
107
- BigCode.new.long_method
108
- ```
77
+ Bug reports and pull requests are welcome on GitHub at https://github.com/alainravet/private_please.
109
78
 
110
- ####step 2 : run you code normally
111
- ```
112
- $ ruby big_code.rb
113
- ```
114
79
 
80
+ ## License
115
81
 
116
- ## Contributing
117
- **Please respect and reuse the current code style and formatting**
82
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
118
83
 
119
- 1. Fork it
120
- 2. Create your feature branch (`git checkout -b my-new-feature`)
121
- 3. Commit your changes (`git commit -am 'Added some feature'`)
122
- 4. Push to the branch (`git push origin my-new-feature`)
123
- 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
- #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'private_please'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,15 @@
1
+ require 'private_please/utils/ruby_utils'
2
+
3
+ module PrivatePlease
4
+ class Config
5
+ attr_reader :additional_excluded_dirs
6
+
7
+ def initialize
8
+ @additional_excluded_dirs = ['(eval)']
9
+ end
10
+
11
+ def exclude_dir(val)
12
+ additional_excluded_dirs << val
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ require 'private_please/tracking/trace_point_processor'
2
+
3
+ module PrivatePlease
4
+ class MethodsCallsTracker
5
+ def self.instance
6
+ @instance ||= new(Config.new)
7
+ end
8
+
9
+ def self.reset
10
+ @instance = nil
11
+ end
12
+
13
+ # -----
14
+ attr_reader :config
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ @trace_point_processor = Tracking::TracePointProcessor.new(config)
19
+ end
20
+ private_class_method :new
21
+
22
+ def start_tracking
23
+ tracer.enable
24
+ end
25
+
26
+ def stop_tracking
27
+ tracer.disable
28
+ end
29
+
30
+ def result
31
+ @trace_point_processor.result
32
+ end
33
+
34
+ private
35
+
36
+ def tracer
37
+ @_tracer ||= begin
38
+ TracePoint.new do |tp|
39
+ @trace_point_processor.process(tp)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ module PrivatePlease
2
+ module Reporting
3
+ class SimpleText
4
+ def initialize(result)
5
+ @result = result
6
+ end
7
+
8
+ def text
9
+ compiled_data = compile_data # passed to ERB via 'binding'
10
+ erb = ERB.new(File.read(template), 0, '%<>')
11
+ erb.result(binding)
12
+ end
13
+
14
+ #-----------------------------------------------------------------------------
15
+ private
16
+
17
+ def template
18
+ templates_home = File.expand_path(File.dirname(__FILE__) + '/templates')
19
+ "#{templates_home}/simple.txt.erb"
20
+ end
21
+
22
+ # Output example:
23
+ # [
24
+ # ["/tmp/project/foo/simple_text_foo.rb",
25
+ # [[18, "SimpleTextFoo#public_i_2"],
26
+ # [11, "SimpleTextFoo.public_c_2"]
27
+ # ]
28
+ # ]
29
+ # ...
30
+ # ]
31
+ def compile_data
32
+ res = Hash.new { |h, k| h[k] = [] }
33
+ @result.to_two_level_hash.each do |klass, methods_and_locations|
34
+ methods_and_locations.each_pair do |meth, locations|
35
+ source, lineno = locations
36
+ res[source] << [lineno, "#{klass}#{meth}"]
37
+ end
38
+ end
39
+ res.sort # by source file path
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ ====================================================================================
2
+ = Privatazable methods : =
3
+ ====================================================================================
4
+ % compiled_data.each do |source_file_and_linenos_and_methods|
5
+ % source_file, linenos_and_methods = source_file_and_linenos_and_methods
6
+ <%= source_file
7
+ %>
8
+ % linenos_and_methods.sort_by(&:first).each do |lineno, meth|
9
+ <%= "%4d %s" % [lineno, meth] %>
10
+ % end
11
+ % end
@@ -0,0 +1,20 @@
1
+ module PrivatePlease
2
+ module Storage
3
+ class CalledMethodsMemoryStore
4
+ attr_reader :public_calls, :private_calls
5
+
6
+ def initialize
7
+ @public_calls = Set.new
8
+ @private_calls = Set.new
9
+ end
10
+
11
+ def add_public_call(value)
12
+ @public_calls .add value
13
+ end
14
+
15
+ def add_private_call(value)
16
+ @private_calls.add value
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,81 @@
1
+ # rubocop:disable all
2
+ module PrivatePlease
3
+ module Debug
4
+ def self.log_to_trace_file(tpd)
5
+ TRACES_LINES.puts TracePointDataLogger.format(tpd)
6
+ end
7
+
8
+ def self.enabled?
9
+ defined?(TRACES_LINES)
10
+ end
11
+
12
+ module TracePointDataLogger
13
+ MARKERS = {
14
+ call: '-->',
15
+ line: "\n l "
16
+ }.freeze
17
+
18
+ def self.format(cur_tpd)
19
+ class_and_method = cur_tpd.method_full_name
20
+ if @prev_cur_tpd.nil?
21
+ same_object_id = same_lineno = false
22
+ else
23
+ same_object_id = @prev_cur_tpd.object_id == cur_tpd.object_id
24
+ same_lineno = @prev_cur_tpd.lineno == cur_tpd.lineno
25
+ same_code = @prev_cur_tpd.code == cur_tpd.code
26
+ same_class_and_method = @prev_class_and_method == class_and_method
27
+ same_self = @prev_self == cur_tpd._self
28
+ same_defined_class = @prev_defined_class == cur_tpd.defined_class
29
+ end
30
+
31
+ @prev_cur_tpd = cur_tpd
32
+ @prev_class_and_method = class_and_method
33
+ @prev_self = cur_tpd._self
34
+ @prev_defined_class = cur_tpd.defined_class
35
+
36
+ data = {
37
+ marker: MARKERS[cur_tpd.event],
38
+ lineno: same_lineno ? '==' : cur_tpd.lineno,
39
+ event: cur_tpd.event,
40
+ object_id: same_object_id ? '==' : cur_tpd.object_id,
41
+ code: same_code ? '"' : cur_tpd.code,
42
+ class_and_method: same_class_and_method ? '""' : class_and_method,
43
+ _self: same_self ? '=' : cur_tpd._self,
44
+ defined_class: same_defined_class ? '=' : cur_tpd.defined_class
45
+ }
46
+ text_line(data)
47
+ end
48
+
49
+ def self.header
50
+ text_line(
51
+ marker: '',
52
+ lineno: 'LINE',
53
+ event: 'EVENT',
54
+ object_id: 'OBJECT_ID',
55
+ code: 'CODE',
56
+ class_and_method: 'method_as_key',
57
+ _self: '_self',
58
+ defined_class: 'defined_class'
59
+ )
60
+ end
61
+
62
+ def self.text_line(data)
63
+ zelf = begin
64
+ data[:_self].to_s
65
+ rescue
66
+ 'RESCUED:' + data[:_self].inspect
67
+ end
68
+ '%3s %4s :%10s | %14s | %-80s | %-60s > %s' % [
69
+ data[:marker],
70
+ data[:lineno],
71
+ data[:event],
72
+ data[:object_id],
73
+ data[:code],
74
+ data[:class_and_method],
75
+ [zelf, data[:defined_class]].join('/')
76
+ ]
77
+ end
78
+ end
79
+ end
80
+ end
81
+ # rubocop:enable all