chusaku 0.1.3 → 0.3.2

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: 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.