chusaku 0.1.3 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7fd38001d891049886a95d54019ce4636dee3762e19f50e83bf7b0d53732cd6
4
- data.tar.gz: a55e1bd009ad426e9258a6d6098d029d45bfb043a6736d9dda492a64b83d6ddf
3
+ metadata.gz: f2976318f89e26e31f77c54e1cc338812ac6a107ec033d53c077fd4eb1759be6
4
+ data.tar.gz: 8413d9353d6115929985f3d6275396abeb25d7b9df834ccd0d4e19fd834ef574
5
5
  SHA512:
6
- metadata.gz: f135d7d2979a67bf3b9f883f5775511a78088b9c8468676da7d028551d4bfbc80ba89b44c182c88da98bd3adbed2deed425a97f2d5fe7b46fe6ebdbac85dc966
7
- data.tar.gz: 7efc39dbd3e86b1f4672900535d1f3556b907d7c6f5220a61182a4aa3b44685a333d41c67fa78fc923e41df33b39ba49bc520743a3e9b5186179b86eb78cdbf7
6
+ metadata.gz: 44c12d0f6c0f970cd3a0e5c76cd14b18d8bfbf16f7d39e8789e4c09c87a8273a8b3e288de865533c7a39b81fe1dd51088a1ddc7c33edb0325e12a676153d5563
7
+ data.tar.gz: 5e01aeaa4eea9099c47445c88db326ff4ceb5d028088bbd6c2a328f21673c17d80c93bc8fda0c4135e76956f1439a26df758985a23bfe895d5f3f2ff14eb3982
@@ -0,0 +1,4 @@
1
+ plugins:
2
+ rubocop:
3
+ enabled: true
4
+ channel: rubocop-0-77
@@ -1,27 +1,21 @@
1
+ require:
2
+ - rubocop-performance
3
+
1
4
  AllCops:
2
5
  Exclude:
3
6
  - 'bin/**/*'
4
-
5
- Layout/IndentationConsistency:
6
- EnforcedStyle: rails
7
+ - 'test/mock/app/**/*'
8
+ - 'test/mock/examples/**/*'
7
9
 
8
10
  Metrics/AbcSize:
9
- Enabled: false
11
+ Exclude:
12
+ - 'test/**/*'
10
13
 
11
14
  Metrics/MethodLength:
12
- Enabled: false
13
-
14
- Metrics/PerceivedComplexity:
15
- Enabled: false
16
-
17
- Style/ClassVars:
18
- Enabled: false
19
-
20
- Style/CommentedKeyword:
21
- Enabled: false
22
-
23
- Style/Documentation:
24
- Enabled: false
15
+ Max: 25
16
+ Exclude:
17
+ - 'test/**/*'
25
18
 
26
- Style/RegexpLiteral:
27
- Enabled: false
19
+ Style/ClassAndModuleChildren:
20
+ Exclude:
21
+ - 'test/**/*'
data/README.md CHANGED
@@ -1,16 +1,22 @@
1
1
  # Chusaku
2
2
 
3
- | Build | Gem |
4
- |-------|-----|
5
- |[![CircleCI](https://circleci.com/gh/nshki/chusaku.svg?style=svg&circle-token=e1917972632f242932171de0ca5443148e83151c)](https://circleci.com/gh/nshki/chusaku)|[![Gem Version](https://badge.fury.io/rb/chusaku.svg)](https://badge.fury.io/rb/chusaku)|
3
+ | Gem | CI | Quality |
4
+ |-----|----|---------|
5
+ |[![Gem Version](https://badge.fury.io/rb/chusaku.svg)](https://badge.fury.io/rb/chusaku)|[![CircleCI](https://circleci.com/gh/nshki/chusaku.svg?style=svg&circle-token=e1917972632f242932171de0ca5443148e83151c)](https://circleci.com/gh/nshki/chusaku)|[![Maintainability](https://api.codeclimate.com/v1/badges/e21235bd513aadf0407b/maintainability)](https://codeclimate.com/github/nshki/chusaku/maintainability)
6
6
 
7
7
  Add comments above your Rails actions that look like:
8
8
 
9
9
  ```ruby
10
- # @route [GET] /waterlilies/:id (waterlily)
10
+ # @route GET /waterlilies/:id (waterlily)
11
11
  def show
12
12
  # ...
13
13
  end
14
+
15
+ # @route PATCH /waterlilies/:id (waterlily)
16
+ # @route PUT /waterlilies/:id (waterlily)
17
+ def update
18
+ # ...
19
+ end
14
20
  ```
15
21
 
16
22
  Based on your `routes.rb` file!
@@ -43,19 +49,44 @@ From the root of your Rails application, run:
43
49
  $ bundle exec chusaku
44
50
  ```
45
51
 
52
+ Chusaku has some flags available for use as well:
53
+
54
+ ```
55
+ $ bundle exec chusaku --help
56
+ Usage: chusaku [options]
57
+ --exit-with-error-on-annotation
58
+ Fail if any file was annotated
59
+ --dry-run Run without file modifications
60
+ -v, --version Show Chusaku version number and quit
61
+ -h, --help Show this help message and quit
62
+ ```
46
63
 
47
- ## Development
48
64
 
49
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
65
+ ## Pre-commit Hook
50
66
 
51
- 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).
67
+ Here's an example setup that you could use for automating Chusaku as a Git hook
68
+ with the [Lefthook](https://github.com/Arkweid/lefthook) gem.
52
69
 
70
+ ```yaml
71
+ pre-commit:
72
+ commands:
73
+ chusaku:
74
+ run: eval "! git diff --staged --name-only | grep -q 'routes.rb' && exit 0 || bundle exec chusaku --exit-with-error-on-annotation"
75
+ ```
53
76
 
54
- ## Contributing
77
+ This example config only runs Chusaku if `routes.rb` was modified.
55
78
 
56
- Bug reports and pull requests are welcome on GitHub at https://github.com/nshki/chusaku.
57
79
 
80
+ ## Development
81
+
82
+ Read the blog post explaining how the gem works at a high level:
83
+ https://nshki.com/chusaku-a-controller-annotation-gem/.
58
84
 
59
- ## License
85
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
86
+ `bundle exec rake test` to run the tests. You can also run `bin/console` for an
87
+ interactive prompt that will allow you to experiment.
60
88
 
61
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
89
+ To release a new version, update the version number in `version.rb`, and then
90
+ run `bundle exec rake release`, which will create a git tag for the version,
91
+ git commits and tags, and push the `.gem` file to
92
+ [rubygems.org](https://rubygems.org).
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- unless File.directory?('./app/controllers') && File.exist?('./Rakefile')
4
- abort 'Please run chusaku from the root of your project.'
5
- end
6
-
7
3
  require 'rubygems'
8
4
 
9
5
  begin
@@ -18,5 +14,6 @@ begin
18
14
  rescue StandardError
19
15
  end
20
16
 
21
- require 'chusaku'
22
- Chusaku.call
17
+ require 'chusaku/cli'
18
+
19
+ exit Chusaku::CLI.new.call
@@ -43,8 +43,9 @@ Gem::Specification.new do |spec|
43
43
 
44
44
  spec.add_development_dependency 'bundler', '~> 2.0'
45
45
  spec.add_development_dependency 'minitest', '~> 5.0'
46
- spec.add_development_dependency 'rake', '~> 10.0'
46
+ spec.add_development_dependency 'rake', '~> 12.3'
47
+ spec.add_development_dependency 'rubocop', '~> 0.77'
48
+ spec.add_development_dependency 'rubocop-performance', '~> 1.5'
47
49
 
48
- spec.add_dependency 'rails', '> 4.2'
49
- spec.add_dependency 'ruby-progressbar', '~> 1.10.1'
50
+ spec.add_dependency 'rails', '> 2.0'
50
51
  end
@@ -1,89 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby-progressbar'
4
3
  require 'chusaku/version'
5
4
  require 'chusaku/parser'
6
5
  require 'chusaku/routes'
7
6
 
7
+ # Handles core functionality of annotating projects.
8
8
  module Chusaku
9
- # The main method to run Chusaku. Annotate all actions in your Rails project
10
- # as follows:
11
- #
12
- # # @route [GET] /waterlilies/:id (waterlilies)
13
- # def show
14
- # # ...
15
- # end
16
- def self.call
17
- routes = Chusaku::Routes.call
18
- controller_pattern = 'app/controllers/**/*_controller.rb'
19
- controller_paths = Dir.glob(Rails.root.join(controller_pattern))
20
-
21
- # Start progress bar.
22
- progressbar = ProgressBar.create \
23
- title: 'Chusaku',
24
- total: controller_paths.count
25
-
26
- # Loop over all controller file paths.
27
- controller_paths.each do |path|
28
- progressbar.increment
29
- controller = /controllers\/(.*)_controller\.rb/.match(path)[1]
30
- actions = routes[controller]
31
- next if actions.nil?
32
-
33
- # Parse the file and iterate over the parsed content, two entries at a
34
- # time.
35
- parsed_file = Chusaku::Parser.call(path: path, actions: actions.keys)
36
- parsed_file.each_cons(2) do |prev, curr|
37
- # Remove all @route comments in the previous group.
38
- if prev[:type] == :comment
39
- prev[:body] = prev[:body].gsub(/^\s*#\s*@route.*$\n/, '')
40
- end
9
+ class << self
10
+ # The main method to run Chusaku. Annotate all actions in a Rails project as
11
+ # follows:
12
+ #
13
+ # # @route GET /waterlilies/:id (waterlilies)
14
+ # def show
15
+ # # ...
16
+ # end
17
+ #
18
+ # @param {Hash} flags - CLI flags
19
+ # @return {Integer} - 0 on success, 1 on error
20
+ def call(flags = {})
21
+ @flags = flags
22
+ @routes = Chusaku::Routes.call
23
+ @annotated_paths = []
24
+ controllers_pattern = 'app/controllers/**/*_controller.rb'
25
+
26
+ Dir.glob(Rails.root.join(controllers_pattern)).each do |path|
27
+ controller = %r{controllers\/(.*)_controller\.rb}.match(path)[1]
28
+ actions = @routes[controller]
29
+ next if actions.nil?
30
+
31
+ annotate_file(path: path, controller: controller, actions: actions.keys)
32
+ end
33
+
34
+ output_results
35
+ end
41
36
 
42
- # Only proceed if we are currently looking at an action.
37
+ private
38
+
39
+ # Adds annotations to the given file.
40
+ #
41
+ # @param {String} path - Path to file
42
+ # @param {String} controller - Controller name
43
+ # @param {Array<String>} actions - List of valid actions for the controller
44
+ # @return {void}
45
+ def annotate_file(path:, controller:, actions:)
46
+ parsed_file = Chusaku::Parser.call(path: path, actions: actions)
47
+ parsed_file[:groups].each_cons(2) do |prev, curr|
48
+ clean_group(prev)
43
49
  next unless curr[:type] == :action
44
50
 
45
- # Insert annotation comment.
46
- action = curr[:action]
47
- annotation = annotate(routes[controller][action])
48
- whitespace = /^(\s*).*$/.match(curr[:body])[1]
51
+ route_data = @routes[controller][curr[:action]]
52
+ next unless route_data.any?
53
+
54
+ annotate_group(group: curr, route_data: route_data)
55
+ end
56
+
57
+ write_to_file(path: path, parsed_file: parsed_file)
58
+ end
59
+
60
+ # Given a parsed group, clean out its contents.
61
+ #
62
+ # @param {Hash} group - { type: Symbol, body: String }
63
+ # @return {void}
64
+ def clean_group(group)
65
+ return unless group[:type] == :comment
66
+
67
+ group[:body] = group[:body].gsub(/^\s*#\s*@route.*$\n/, '')
68
+ group[:body] =
69
+ group[:body].gsub(%r{^\s*# (GET|POST|PATCH\/PUT|DELETE) \/\S+$\n}, '')
70
+ end
71
+
72
+ # Add an annotation to the given group given by Chusaku::Parser that looks
73
+ # like:
74
+ #
75
+ # @route GET /waterlilies/:id (waterlilies)
76
+ #
77
+ # @param {Hash} group - Parsed content given by Chusaku::Parser
78
+ # @param {Hash} route_data - Individual route data given by Chusaku::Routes
79
+ # @return {void}
80
+ def annotate_group(group:, route_data:)
81
+ whitespace = /^(\s*).*$/.match(group[:body])[1]
82
+ route_data.reverse_each do |datum|
83
+ name = datum[:name]
84
+ annotation = "@route #{datum[:verb]} #{datum[:path]}"
85
+ annotation += " (#{name})" unless name.nil?
49
86
  comment = "#{whitespace}# #{annotation}\n"
50
- curr[:body] = comment + curr[:body]
87
+ group[:body] = comment + group[:body]
88
+ end
89
+ end
90
+
91
+ # Write annotated content to a file if it differs from the original.
92
+ #
93
+ # @param {String} path - File path to write to
94
+ # @param {Hash} parsed_file - Hash mutated by `annotate_group`
95
+ # @return {void}
96
+ def write_to_file(path:, parsed_file:)
97
+ content = parsed_file[:groups].map { |pf| pf[:body] }.join
98
+ return unless parsed_file[:content] != content
99
+
100
+ unless @flags.include?(:dry)
101
+ # When running the test suite, we want to make sure we're not
102
+ # overwriting any files. `r` mode ensures that.
103
+ mode = File.instance_methods.include?(:test_write) ? 'r' : 'w'
104
+
105
+ File.open(path, mode) do |file|
106
+ if file.respond_to?(:test_write)
107
+ file.test_write(content, path)
108
+ else
109
+ file.write(content)
110
+ end
111
+ end
51
112
  end
52
113
 
53
- # Write to file.
54
- parsed_content = parsed_file.map { |pf| pf[:body] }
55
- write(path, parsed_content.join)
114
+ @annotated_paths.push(path)
56
115
  end
57
- end
58
116
 
59
- # Write given content to a file. If we're using an overridden version of File,
60
- # then use its method instead for testing purposes.
61
- #
62
- # @param {String} path
63
- # @param {String} content
64
- # @return {void}
65
- def self.write(path, content)
66
- File.open(path, 'r+') do |file|
67
- if file.respond_to?(:test_write)
68
- file.test_write(content, path)
117
+ # Output results to user.
118
+ #
119
+ # @return {Integer} - 0 for success, 1 for error
120
+ def output_results
121
+ if @annotated_paths.any?
122
+ puts("Annotated #{@annotated_paths.join(', ')}")
123
+ @flags.include?(:error_on_annotation) ? 1 : 0
69
124
  else
70
- file.write(content)
125
+ puts('Nothing to annotate')
126
+ 0
71
127
  end
72
128
  end
73
129
  end
74
-
75
- # Given a hash describing an action, generate an annotation in the form:
76
- #
77
- # @route [GET] /waterlilies/:id (waterlilies)
78
- #
79
- # @param {Hash} action_info
80
- # @return {String}
81
- def self.annotate(action_info)
82
- verbs = action_info[:verbs]
83
- path = action_info[:path]
84
- name = action_info[:name]
85
- annotation = "@route [#{verbs.join(', ')}] #{path}"
86
- annotation += " (#{name})" unless name.nil?
87
- annotation
88
- end
89
130
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'chusaku'
5
+
6
+ module Chusaku
7
+ # Enables flags for the `chusaku` executable.
8
+ class CLI
9
+ attr_reader :options
10
+
11
+ STATUS_SUCCESS = 0
12
+ STATUS_ERROR = 1
13
+
14
+ Finished = Class.new(RuntimeError)
15
+ NotARailsProject = Class.new(RuntimeError)
16
+
17
+ # Initializes a new instance of Chusaku::CLI.
18
+ #
19
+ # @return {Chusaku::CLI} - Instance of this class
20
+ def initialize
21
+ @options = {}
22
+ end
23
+
24
+ # Parse CLI flags, if any, and handle applicable behaviors.
25
+ #
26
+ # @param {Array<String>} args - CLI arguments
27
+ # @return {Integer} - 0 on success, 1 on error
28
+ def call(args = ARGV)
29
+ optparser.parse!(args)
30
+ check_for_rails_project
31
+ Chusaku.call(options)
32
+ rescue NotARailsProject
33
+ warn('Please run chusaku from the root of your project.')
34
+ STATUS_ERROR
35
+ rescue Finished
36
+ STATUS_SUCCESS
37
+ end
38
+
39
+ private
40
+
41
+ # Raises exception if Rails project cannot be detected.
42
+ #
43
+ # @raise {Chusaku::CLI::NotARailsProject} - Exception if not Rails project
44
+ # @return {void}
45
+ def check_for_rails_project
46
+ has_controllers = File.directory?('./app/controllers')
47
+ has_rakefile = File.exist?('./Rakefile')
48
+ raise NotARailsProject unless has_controllers && has_rakefile
49
+ end
50
+
51
+ # Returns an instance of OptionParser with supported flags.
52
+ #
53
+ # @return {OptionParser} - Preconfigured OptionParser instance
54
+ def optparser
55
+ OptionParser.new do |opts|
56
+ opts.banner = 'Usage: chusaku [options]'
57
+ add_error_on_annotation_flag(opts)
58
+ add_dry_run_flag(opts)
59
+ add_version_flag(opts)
60
+ add_help_flag(opts)
61
+ end
62
+ end
63
+
64
+ # Adds `--exit-with-error-on-annotation` flag.
65
+ #
66
+ # @param {OptionParser} opts - OptionParser instance
67
+ # @return {void}
68
+ def add_error_on_annotation_flag(opts)
69
+ opts.on(
70
+ '--exit-with-error-on-annotation',
71
+ 'Fail if any file was annotated'
72
+ ) do
73
+ @options[:error_on_annotation] = true
74
+ end
75
+ end
76
+
77
+ # Adds `--dry-run` flag.
78
+ #
79
+ # @param {OptionParser} opts - OptionParser instance
80
+ # @return {void}
81
+ def add_dry_run_flag(opts)
82
+ opts.on(
83
+ '--dry-run',
84
+ 'Run without file modifications'
85
+ ) do
86
+ @options[:dry] = true
87
+ end
88
+ end
89
+
90
+ # Adds `--version` flag.
91
+ #
92
+ # @param {OptionParser} opts - OptionParser instance
93
+ # @return {void}
94
+ def add_version_flag(opts)
95
+ opts.on(
96
+ '-v',
97
+ '--version',
98
+ 'Show Chusaku version number and quit'
99
+ ) do
100
+ puts(Chusaku::VERSION)
101
+ raise Finished
102
+ end
103
+ end
104
+
105
+ # Adds `--help` flag.
106
+ #
107
+ # @param {OptionParser} opts - OptionParser instance
108
+ # @return {void}
109
+ def add_help_flag(opts)
110
+ opts.on(
111
+ '-h',
112
+ '--help',
113
+ 'Show this help message and quit'
114
+ ) do
115
+ puts(opts)
116
+ raise Finished
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,32 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
+ # Handles parsing a file and groups its lines into categories.
4
5
  module Parser
5
- # Parses a file and groups its lines into categories:
6
- #
7
6
  # Example output:
8
7
  #
9
- # [ { type: :code,
10
- # body: 'class Foo\n',
11
- # action: nil },
12
- # { type: :comment,
13
- # body: ' # Bar\n # Baz\n',
14
- # action: nil },
15
- # { type: :action,
16
- # body: ' def action_name; end\n',
17
- # action: 'action_name' }
18
- # { type: :code,
19
- # body: 'end # vanilla is the best flavor\n',
20
- # action: nil } ]
8
+ # {
9
+ # content: <Original file content>,
10
+ # groups: [
11
+ # {
12
+ # type: :code,
13
+ # body: 'class Foo\n',
14
+ # action: nil
15
+ # },
16
+ # {
17
+ # type: :comment,
18
+ # body: ' # Bar\n # Baz\n',
19
+ # action: nil
20
+ # },
21
+ # {
22
+ # type: :action,
23
+ # body: ' def action_name; end\n',
24
+ # action: 'action_name'
25
+ # }
26
+ # {
27
+ # type: :code,
28
+ # body: 'end # vanilla is the best flavor\n',
29
+ # action: nil
30
+ # }
31
+ # ]
32
+ # }
21
33
  #
22
- # @param {String} path
23
- # @param {Array<String>} actions
24
- # @return {Array<Hash>}
34
+ # @param {String} path - File path to parse
35
+ # @param {Array<String>} actions - List of valid actions for this route
36
+ # @return {Hash} - { content: String, groups: Array<Hash> }
25
37
  def self.call(path:, actions:)
26
38
  groups = []
27
39
  group = {}
40
+ content = IO.read(path)
28
41
 
29
- File.open(path, 'r').each_line do |line|
42
+ content.each_line do |line|
30
43
  parsed_line = parse_line(line: line, actions: actions)
31
44
 
32
45
  if group[:type] != parsed_line[:type]
@@ -42,7 +55,7 @@ module Chusaku
42
55
 
43
56
  # Push the last group onto the array and return.
44
57
  groups.push(group)
45
- groups
58
+ { content: content, groups: groups }
46
59
  end
47
60
 
48
61
  # Given a line and actions, returns the line's type:
@@ -56,9 +69,9 @@ module Chusaku
56
69
  #
57
70
  # { type: :action, body: 'def foo', action: 'foo' }
58
71
  #
59
- # @param {String} line
60
- # @param {Array<String>} actions
61
- # @return {Hash}
72
+ # @param {String} line - A line of a file
73
+ # @param {Array<String>} actions - List of valid actions for this route
74
+ # @return {Hash} - { type: Symbol, body: String, action: String }
62
75
  def self.parse_line(line:, actions:)
63
76
  comment_match = /^\s*#.*$/.match(line)
64
77
  def_match = /^\s*def\s+(\w*)\s*\w*.*$/.match(line)
@@ -1,65 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
- module Routes
5
- # Extract information about the Rails project's routes.
6
- #
7
- # Example output:
8
- #
9
- # {
10
- # 'users' => {
11
- # 'edit' => {
12
- # verbs: ['GET'],
13
- # path: '/users/:id',
14
- # name: 'edit_user'
15
- # },
16
- # 'update' => {
17
- # verbs: ['PUT', 'PATCH'],
18
- # path: '/users',
19
- # name: nil
20
- # }
21
- # },
22
- # 'empanadas' => {
23
- # 'create' => {
24
- # verbs: ['POST'],
25
- # path: '/empanadas',
26
- # name: nil
27
- # }
28
- # }
29
- # }
30
- #
31
- # @return {Hash}
32
- def self.call
33
- routes = {}
4
+ # Handles extracting information about the Rails project's routes.
5
+ class Routes
6
+ class << self
7
+ # Example output:
8
+ #
9
+ # {
10
+ # 'users' => {
11
+ # 'edit' => [
12
+ # { verb: 'GET', path: '/users/:id', name: 'edit_user' }
13
+ # ],
14
+ # 'update' => [
15
+ # { verb: 'PATCH', path: '/users', name: 'edit_user' },
16
+ # { verb: 'PUT', path: '/users', name: 'edit_user' }
17
+ # ]
18
+ # },
19
+ # 'empanadas' => {
20
+ # 'create' => [
21
+ # { verb: 'POST', path: '/empanadas', name: nil }
22
+ # ]
23
+ # }
24
+ # }
25
+ #
26
+ # @return {Hash} - Routes hash
27
+ def call
28
+ routes = {}
34
29
 
35
- Rails.application.routes.routes.each do |route|
36
- defaults = route.defaults
37
- controller = defaults[:controller]
38
- action = defaults[:action]
30
+ Rails.application.routes.routes.each do |route|
31
+ controller, action = extract_controller_and_action_from(route)
32
+ routes[controller] ||= {}
33
+ routes[controller][action] ||= []
39
34
 
40
- routes[controller] ||= {}
41
- if routes[controller][action].nil?
42
- routes[controller][action] = format_action(route)
43
- else
44
- routes[controller][action][:verbs].push(route.verb)
35
+ verbs_for(route).each do |verb|
36
+ routes[controller][action].push(format(route: route, verb: verb))
37
+ routes[controller][action].uniq!
38
+ end
45
39
  end
40
+
41
+ backfill_routes(routes)
46
42
  end
47
43
 
48
- routes
49
- end
44
+ private
50
45
 
51
- private
46
+ # Extract the HTTP verbs for a Rails route. Required for older versions of
47
+ # Rails that return regular expressions for a route verb which sometimes
48
+ # contains multiple verbs.
49
+ #
50
+ # @param {ActionDispatch::Journey::Route} route - Route given by Rails
51
+ # @return {Array<String>} - List of HTTP verbs for the given route
52
+ def verbs_for(route)
53
+ route_verb = route.verb.to_s
54
+
55
+ ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].select do |verb|
56
+ route_verb.include?(verb)
57
+ end
58
+ end
52
59
 
53
- # Extract information of a given route.
60
+ # Formats information for a given route.
54
61
  #
55
- # @param {ActionDispatch::Journey::Route} route
56
- # @return {Hash}
57
- def self.format_action(route)
62
+ # @param {ActionDispatch::Journey::Route} route - Route given by Rails
63
+ # @param {String} verb - HTTP verb
64
+ # @return {Hash} - { verb: String, path: String, name: String }
65
+ def format(route:, verb:)
58
66
  {
59
- verbs: [route.verb],
67
+ verb: verb,
60
68
  path: route.path.spec.to_s.gsub('(.:format)', ''),
61
69
  name: route.name
62
70
  }
63
71
  end
72
+
73
+ # Given a routes hash, backfill entries that aren't already filled by
74
+ # `Rails.application.routes`.
75
+ #
76
+ # @param {Hash} routes - Routes hash generated by this class
77
+ # @return {Hash} - Backfilled routes hash
78
+ def backfill_routes(routes)
79
+ paths = {}
80
+
81
+ routes.each do |_controller, actions|
82
+ actions.each do |_action, data|
83
+ data.each do |datum|
84
+ paths[datum[:path]] ||= datum[:name]
85
+ datum[:name] ||= paths[datum[:path]]
86
+ end
87
+ end
88
+ end
89
+
90
+ routes
91
+ end
92
+
93
+ # Given a route, extract the controller and action strings.
94
+ #
95
+ # @param {ActionDispatch::Journey::Route} route - Route instance
96
+ # @return {Array<String>} - [String, String]
97
+ def extract_controller_and_action_from(route)
98
+ defaults = route.defaults
99
+ controller = defaults[:controller]
100
+ action = defaults[:action]
101
+
102
+ [controller, action]
103
+ end
104
+ end
64
105
  end
65
106
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
- VERSION = '0.1.3'
4
+ VERSION = '0.3.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chusaku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nishiki Liu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-13 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,42 +44,56 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '12.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '12.3'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rails
56
+ name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">"
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '4.2'
62
- type: :runtime
61
+ version: '0.77'
62
+ type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">"
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '4.2'
68
+ version: '0.77'
69
69
  - !ruby/object:Gem::Dependency
70
- name: ruby-progressbar
70
+ name: rubocop-performance
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.10.1
76
- type: :runtime
75
+ version: '1.5'
76
+ type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.10.1
82
+ version: '1.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
83
97
  description: Annotate your Rails controllers with route info.
84
98
  email:
85
99
  - nishiki.liu@gmail.com
@@ -89,6 +103,7 @@ extensions: []
89
103
  extra_rdoc_files: []
90
104
  files:
91
105
  - ".circleci/config.yml"
106
+ - ".codeclimate.yml"
92
107
  - ".gitignore"
93
108
  - ".rubocop.yml"
94
109
  - Gemfile
@@ -100,6 +115,7 @@ files:
100
115
  - bin/setup
101
116
  - chusaku.gemspec
102
117
  - lib/chusaku.rb
118
+ - lib/chusaku/cli.rb
103
119
  - lib/chusaku/parser.rb
104
120
  - lib/chusaku/routes.rb
105
121
  - lib/chusaku/version.rb
@@ -126,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
142
  - !ruby/object:Gem::Version
127
143
  version: '0'
128
144
  requirements: []
129
- rubygems_version: 3.0.3
145
+ rubygems_version: 3.1.2
130
146
  signing_key:
131
147
  specification_version: 4
132
148
  summary: Annotate your Rails controllers with route info.