chusaku 0.1.4 → 0.4.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: 519cb59d3970def8c48a1f9995f8208e2455d17c6f3061ff56ceed1e9cd2e593
4
- data.tar.gz: 4de2d9aae8d0e9d97d998a53b47fd4428855ed7f5898926ae3e1f14d1b6fe90e
3
+ metadata.gz: 76adb49d62bcaeab5291f4b09b12318ea5e80dc7c4b77ff7bbfee697732a953f
4
+ data.tar.gz: 8434d642b81b5d4cb67b7f9852adef2da679c24f7c9ea287aba903653b9703ee
5
5
  SHA512:
6
- metadata.gz: f619946e1d2a43aacec4d8bd2488d5b5af4c4e24f64bc233c294097ffc51e2c4bf3c3028530302397c026d099835d2587df1cc96356643102812bf93754a8265
7
- data.tar.gz: 871f3aab82d32b5dff9dd508fbc8119ace1125cc88e14f0e36489b094228e3843bd47fa418a0314017fe974c5ee1a149fd86585f47f494e888de92e82fa3da54
6
+ metadata.gz: 7b5880ed576e31a7f80a970389dc559e81ec932005716122de67a5f2c25586f82e674e6bdf1aba100d13a5bc7cba41df458b6e439c825684fa51ef11f77c6337
7
+ data.tar.gz: fa2ed22e8e87762f4577f4e45765f22c1e2d8139eb8d02f18682f7859ac543bfad8977b545645717a5f6acb2fd75f1eac02de6697982de6eebc02cedd595c68c
@@ -0,0 +1,4 @@
1
+ plugins:
2
+ rubocop:
3
+ enabled: true
4
+ channel: rubocop-0-77
@@ -1,27 +1,27 @@
1
+ require:
2
+ - rubocop-performance
3
+
1
4
  AllCops:
2
5
  Exclude:
3
6
  - 'bin/**/*'
7
+ - 'test/mock/app/**/*'
8
+ - 'test/mock/examples/**/*'
4
9
 
5
- Layout/IndentationConsistency:
6
- EnforcedStyle: rails
10
+ Layout/LineLength:
11
+ Max: 120
7
12
 
8
13
  Metrics/AbcSize:
9
- Enabled: false
14
+ Exclude:
15
+ - 'test/**/*'
10
16
 
11
17
  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
18
+ Max: 25
19
+ Exclude:
20
+ - 'test/**/*'
22
21
 
23
- Style/Documentation:
24
- Enabled: false
22
+ Metrics/ModuleLength:
23
+ Max: 250
25
24
 
26
- Style/RegexpLiteral:
27
- Enabled: false
25
+ Style/ClassAndModuleChildren:
26
+ Exclude:
27
+ - 'test/**/*'
data/README.md CHANGED
@@ -1,8 +1,8 @@
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
 
@@ -49,6 +49,33 @@ From the root of your Rails application, run:
49
49
  $ bundle exec chusaku
50
50
  ```
51
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
+ ```
63
+
64
+
65
+ ## Pre-commit Hook
66
+
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.
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
+ ```
76
+
77
+ This example config only runs Chusaku if `routes.rb` was modified.
78
+
52
79
 
53
80
  ## Development
54
81
 
@@ -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
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.name = 'chusaku'
9
9
  spec.version = Chusaku::VERSION
10
10
  spec.authors = ['Nishiki Liu']
11
- spec.email = ['nishiki.liu@gmail.com']
11
+ spec.email = ['nishiki@hey.com']
12
12
 
13
13
  spec.summary = 'Annotate your Rails controllers with route info.'
14
14
  spec.description = 'Annotate your Rails controllers with route info.'
@@ -37,13 +37,15 @@ Gem::Specification.new do |spec|
37
37
  f.match(%r{^(test|spec|features)/})
38
38
  end
39
39
  end
40
- spec.bindir = 'bin'
41
- spec.executables = 'chusaku'
40
+ spec.bindir = 'bin'
41
+ spec.executables = 'chusaku'
42
42
  spec.require_paths = ['lib']
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
50
  spec.add_dependency 'rails', '> 2.0'
49
51
  end
@@ -4,92 +4,163 @@ require 'chusaku/version'
4
4
  require 'chusaku/parser'
5
5
  require 'chusaku/routes'
6
6
 
7
+ # Handles core functionality of annotating projects.
7
8
  module Chusaku
8
- # The main method to run Chusaku. Annotate all actions in your Rails project
9
- # as follows:
10
- #
11
- # # @route GET /waterlilies/:id (waterlilies)
12
- # def show
13
- # # ...
14
- # end
15
- def self.call
16
- routes = Chusaku::Routes.call
17
- controller_pattern = 'app/controllers/**/*_controller.rb'
18
- controller_paths = Dir.glob(Rails.root.join(controller_pattern))
19
-
20
- # Loop over all controller file paths.
21
- controller_paths.each do |path|
22
- controller = /controllers\/(.*)_controller\.rb/.match(path)[1]
23
- actions = routes[controller]
24
- next if actions.nil?
25
-
26
- # Parse the file and iterate over the parsed content, two entries at a
27
- # time.
28
- parsed_file = Chusaku::Parser.call(path: path, actions: actions.keys)
29
- parsed_file.each_cons(2) do |prev, curr|
30
- # Remove all @route comments in the previous group.
31
- if prev[:type] == :comment
32
- prev[:body] = prev[:body].gsub(/^\s*#\s*@route.*$\n/, '')
33
- 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 flags [Hash] 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
34
33
 
35
- # Only proceed if we are currently looking at an action.
34
+ output_results
35
+ end
36
+
37
+ private
38
+
39
+ # Adds annotations to the given file.
40
+ #
41
+ # @param path [String] Path to file
42
+ # @param controller [String] Controller name
43
+ # @param actions [Array<String>] 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)
36
49
  next unless curr[:type] == :action
37
50
 
38
- # Fetch current action in routes.
39
- action = curr[:action]
40
- data = routes[controller][action]
41
- next unless data.any?
42
-
43
- # Add annotations.
44
- whitespace = /^(\s*).*$/.match(curr[:body])[1]
45
- data.reverse.each do |datum|
46
- annotation = annotate(datum)
47
- comment = "#{whitespace}# #{annotation}\n"
48
- curr[:body] = comment + curr[:body]
49
- end
51
+ route_data = @routes[controller][curr[:action]]
52
+ next unless route_data.any?
53
+
54
+ annotate_group(group: curr, route_data: route_data)
50
55
  end
51
56
 
52
- # Write to file.
53
- parsed_content = parsed_file.map { |pf| pf[:body] }
54
- write(path, parsed_content.join)
57
+ write_to_file(path: path, parsed_file: parsed_file)
55
58
  end
56
59
 
57
- # Output results to user.
58
- if controller_paths.any?
59
- puts "Annotated #{controller_paths.join(', ')}"
60
- else
61
- puts "Nothing to annotate"
60
+ # Given a parsed group, clean out its contents.
61
+ #
62
+ # @param group [Hash] { 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}, '')
62
70
  end
63
- end
64
71
 
65
- # Write given content to a file. If we're using an overridden version of File,
66
- # then use its method instead for testing purposes.
67
- #
68
- # @param {String} path
69
- # @param {String} content
70
- # @return {void}
71
- def self.write(path, content)
72
- File.open(path, 'r+') do |file|
73
- if file.respond_to?(:test_write)
74
- file.test_write(content, path)
75
- else
76
- file.write(content)
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 group [Hash] Parsed content given by Chusaku::Parser
78
+ # @param route_data [Hash] 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
+ comment = "#{whitespace}# #{annotate_route(**datum)}\n"
84
+ group[:body] = comment + group[:body]
77
85
  end
78
86
  end
79
- end
80
87
 
81
- # Given a hash describing an action, generate an annotation in the form:
82
- #
83
- # @route GET /waterlilies/:id (waterlilies)
84
- #
85
- # @param {Hash} action_info
86
- # @return {String}
87
- def self.annotate(action_info)
88
- verb = action_info[:verb]
89
- path = action_info[:path]
90
- name = action_info[:name]
91
- annotation = "@route #{verb} #{path}"
92
- annotation += " (#{name})" unless name.nil?
93
- annotation
88
+ # Generate route annotation.
89
+ #
90
+ # @param verb [String] HTTP verb for route
91
+ # @param path [String] Rails path for route
92
+ # @param name [String] Name used in route helpers
93
+ # @param defaults [Hash] Default parameters for route
94
+ # @return [String] "@route <verb> <path> {<defaults>} (<name>)"
95
+ def annotate_route(verb:, path:, name:, defaults:)
96
+ annotation = "@route #{verb} #{path}"
97
+ if defaults&.any?
98
+ defaults_str =
99
+ defaults
100
+ .map { |key, value| "#{key}: #{value.inspect}" }
101
+ .join(', ')
102
+ annotation += " {#{defaults_str}}"
103
+ end
104
+ annotation += " (#{name})" unless name.nil?
105
+ annotation
106
+ end
107
+
108
+ # Write annotated content to a file if it differs from the original.
109
+ #
110
+ # @param path [String] File path to write to
111
+ # @param parsed_file [Hash] Hash mutated by {#annotate_group}
112
+ # @return [void]
113
+ def write_to_file(path:, parsed_file:)
114
+ new_content = new_content_for(parsed_file)
115
+ return unless parsed_file[:content] != new_content
116
+
117
+ !@flags.include?(:dry) && perform_write(path: path, content: new_content)
118
+ @annotated_paths.push(path)
119
+ end
120
+
121
+ # Extracts the new file content for the given parsed file.
122
+ #
123
+ # @param parsed_file [Hash] { groups => Array<Hash> }
124
+ # @return [String] New file content
125
+ def new_content_for(parsed_file)
126
+ parsed_file[:groups].map { |pf| pf[:body] }.join
127
+ end
128
+
129
+ # Wraps the write operation. Needed to clearly distinguish whether it's a
130
+ # write in the test suite or a write in actual use.
131
+ #
132
+ # @param path [String] File path
133
+ # @param content [String] File content
134
+ # @return [void]
135
+ def perform_write(path:, content:)
136
+ File.open(path, file_mode) do |file|
137
+ if file.respond_to?(:test_write)
138
+ file.test_write(content, path)
139
+ else
140
+ file.write(content)
141
+ end
142
+ end
143
+ end
144
+
145
+ # When running the test suite, we want to make sure we're not overwriting
146
+ # any files. `r` mode ensures that, and `w` is used for actual usage.
147
+ #
148
+ # @return [String] 'r' or 'w'
149
+ def file_mode
150
+ File.instance_methods.include?(:test_write) ? 'r' : 'w'
151
+ end
152
+
153
+ # Output results to user.
154
+ #
155
+ # @return [Integer] 0 for success, 1 for error
156
+ def output_results
157
+ if @annotated_paths.any?
158
+ puts("Annotated #{@annotated_paths.join(', ')}")
159
+ @flags.include?(:error_on_annotation) ? 1 : 0
160
+ else
161
+ puts('Nothing to annotate')
162
+ 0
163
+ end
164
+ end
94
165
  end
95
166
  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 args [Array<String>] 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 opts [OptionParser] 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 opts [OptionParser] 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 opts [OptionParser] 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 opts [OptionParser] 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,47 @@
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
+ # Primary method to call.
6
7
  #
7
8
  # Example output:
8
9
  #
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 } ]
10
+ # {
11
+ # content: <Original file content>,
12
+ # groups: [
13
+ # {
14
+ # type: :code,
15
+ # body: 'class Foo\n',
16
+ # action: nil
17
+ # },
18
+ # {
19
+ # type: :comment,
20
+ # body: ' # Bar\n # Baz\n',
21
+ # action: nil
22
+ # },
23
+ # {
24
+ # type: :action,
25
+ # body: ' def action_name; end\n',
26
+ # action: 'action_name'
27
+ # }
28
+ # {
29
+ # type: :code,
30
+ # body: 'end # vanilla is the best flavor\n',
31
+ # action: nil
32
+ # }
33
+ # ]
34
+ # }
21
35
  #
22
- # @param {String} path
23
- # @param {Array<String>} actions
24
- # @return {Array<Hash>}
36
+ # @param path [String] File path to parse
37
+ # @param actions [Array<String>] List of valid actions for this route
38
+ # @return [Hash] { content => String, groups => Array<Hash> }
25
39
  def self.call(path:, actions:)
26
40
  groups = []
27
41
  group = {}
42
+ content = IO.read(path)
28
43
 
29
- File.open(path, 'r').each_line do |line|
44
+ content.each_line do |line|
30
45
  parsed_line = parse_line(line: line, actions: actions)
31
46
 
32
47
  if group[:type] != parsed_line[:type]
@@ -42,23 +57,25 @@ module Chusaku
42
57
 
43
58
  # Push the last group onto the array and return.
44
59
  groups.push(group)
45
- groups
60
+ { content: content, groups: groups }
46
61
  end
47
62
 
48
- # Given a line and actions, returns the line's type:
63
+ # Given a line and actions, returns the line's type.
64
+ #
65
+ # A type can be one of:
49
66
  #
50
67
  # 1. comment - A line that is entirely commented. Lines that have trailing
51
68
  # comments do not fall under this category.
52
69
  # 2. action - A line that contains an action definition.
53
70
  # 3. code - Anything else.
54
71
  #
55
- # And give back a Hash in the form:
72
+ # Returns a Hash in the form:
56
73
  #
57
74
  # { type: :action, body: 'def foo', action: 'foo' }
58
75
  #
59
- # @param {String} line
60
- # @param {Array<String>} actions
61
- # @return {Hash}
76
+ # @param line [String] A line of a file
77
+ # @param actions [Array<String>] List of valid actions for this route
78
+ # @return [Hash] { type => Symbol, body => String, action => String }
62
79
  def self.parse_line(line:, actions:)
63
80
  comment_match = /^\s*#.*$/.match(line)
64
81
  def_match = /^\s*def\s+(\w*)\s*\w*.*$/.match(line)
@@ -1,67 +1,107 @@
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
- # { verb: ['GET'], path: '/users/:id', name: ['edit_user'] }],
13
- # 'update' => [
14
- # { verb: ['PUT', 'PATCH'],
15
- # path: '/users',
16
- # name: ['edit_user', 'edit_user2'] }]
17
- # },
18
- # 'empanadas' => {
19
- # 'create' => [
20
- # { verb: 'POST', path: '/empanadas', name: nil }]
21
- # }
22
- # }
23
- #
24
- # @return {Hash} Routes hash
25
- def self.call
26
- routes = {}
4
+ # Handles extracting information about the Rails project's routes.
5
+ class Routes
6
+ class << self
7
+ # Primary method to call.
8
+ #
9
+ # Example output:
10
+ #
11
+ # {
12
+ # 'users' => {
13
+ # 'edit' => [
14
+ # { verb: 'GET', path: '/users/:id', name: 'edit_user' }
15
+ # ],
16
+ # 'update' => [
17
+ # { verb: 'PATCH', path: '/users', name: 'edit_user' },
18
+ # { verb: 'PUT', path: '/users', name: 'edit_user' }
19
+ # ]
20
+ # },
21
+ # 'empanadas' => {
22
+ # 'create' => [
23
+ # { verb: 'POST', path: '/empanadas', name: nil }
24
+ # ]
25
+ # }
26
+ # }
27
+ #
28
+ # @return [Hash] Routes hash
29
+ def call
30
+ routes = {}
27
31
 
28
- Rails.application.routes.routes.each do |route|
29
- defaults = route.defaults
30
- controller = defaults[:controller]
31
- action = defaults[:action]
32
+ Rails.application.routes.routes.each do |route|
33
+ controller, action, defaults = extract_data_from(route)
34
+ routes[controller] ||= {}
35
+ routes[controller][action] ||= []
36
+
37
+ add_info_for \
38
+ route: route,
39
+ routes: routes,
40
+ controller: controller,
41
+ action: action,
42
+ defaults: defaults
43
+ end
32
44
 
33
- routes[controller] ||= {}
34
- routes[controller][action] ||= []
35
- routes[controller][action].push(format_route(route))
45
+ backfill_routes(routes)
36
46
  end
37
47
 
38
- backfill_routes(routes)
39
- end
48
+ private
40
49
 
41
- private
50
+ # Adds formatted route info for the given param combination.
51
+ #
52
+ # @param route [Hash] Route info
53
+ # @param routes [Hash] Collection of all route info
54
+ # @param controller [String] Controller key
55
+ # @param action [String] Action key
56
+ # @param defaults [Hash] Default parameters for route
57
+ # @return [void]
58
+ def add_info_for(route:, routes:, controller:, action:, defaults:)
59
+ verbs_for(route).each do |verb|
60
+ routes[controller][action]
61
+ .push(format(route: route, verb: verb, defaults: defaults))
62
+ routes[controller][action].uniq!
63
+ end
64
+ end
42
65
 
43
- # Extract information of a given route.
66
+ # Extract the HTTP verbs for a Rails route. Required for older versions of
67
+ # Rails that return regular expressions for a route verb which sometimes
68
+ # contains multiple verbs.
44
69
  #
45
- # @param {ActionDispatch::Journey::Route} route - Route given by Rails
46
- # @return {Hash} Formatted hash for given route
47
- def self.format_route(route)
70
+ # @param route [ActionDispatch::Journey::Route] Route given by Rails
71
+ # @return [Array<String>] List of HTTP verbs for the given route
72
+ def verbs_for(route)
73
+ route_verb = route.verb.to_s
74
+
75
+ %w[GET POST PUT PATCH DELETE].select do |verb|
76
+ route_verb.include?(verb)
77
+ end
78
+ end
79
+
80
+ # Formats information for a given route.
81
+ #
82
+ # @param route [ActionDispatch::Journey::Route] Route given by Rails
83
+ # @param verb [String] HTTP verb
84
+ # @param defaults [Hash] Default parameters for route
85
+ # @return [Hash] { verb => String, path => String, name => String }
86
+ def format(route:, verb:, defaults:)
48
87
  {
49
- verb: route.verb,
88
+ verb: verb,
50
89
  path: route.path.spec.to_s.gsub('(.:format)', ''),
51
- name: route.name
90
+ name: route.name,
91
+ defaults: defaults
52
92
  }
53
93
  end
54
94
 
55
95
  # Given a routes hash, backfill entries that aren't already filled by
56
96
  # `Rails.application.routes`.
57
97
  #
58
- # @param {Hash} routes - Routes hash generated by this class
59
- # @return {Hash} Backfilled routes
60
- def self.backfill_routes(routes)
98
+ # @param routes [Hash] Routes hash generated by this class
99
+ # @return [Hash] Backfilled routes hash
100
+ def backfill_routes(routes)
61
101
  paths = {}
62
102
 
63
103
  routes.each do |_controller, actions|
64
- actions.each do |action, data|
104
+ actions.each do |_action, data|
65
105
  data.each do |datum|
66
106
  paths[datum[:path]] ||= datum[:name]
67
107
  datum[:name] ||= paths[datum[:path]]
@@ -71,5 +111,18 @@ module Chusaku
71
111
 
72
112
  routes
73
113
  end
114
+
115
+ # Given a route, extract the controller and action strings.
116
+ #
117
+ # @param route [ActionDispatch::Journey::Route] Route instance
118
+ # @return [Array<Object>] (String, String, Hash)
119
+ def extract_data_from(route)
120
+ defaults = route.defaults.dup
121
+ controller = defaults.delete(:controller)
122
+ action = defaults.delete(:action)
123
+
124
+ [controller, action, defaults]
125
+ end
126
+ end
74
127
  end
75
128
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
- VERSION = '0.1.4'
4
+ VERSION = '0.4.0'
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.4
4
+ version: 0.4.0
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-10-05 00:00:00.000000000 Z
11
+ date: 2020-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,14 +44,42 @@ 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
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.77'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.77'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-performance
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rails
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -68,13 +96,14 @@ dependencies:
68
96
  version: '2.0'
69
97
  description: Annotate your Rails controllers with route info.
70
98
  email:
71
- - nishiki.liu@gmail.com
99
+ - nishiki@hey.com
72
100
  executables:
73
101
  - chusaku
74
102
  extensions: []
75
103
  extra_rdoc_files: []
76
104
  files:
77
105
  - ".circleci/config.yml"
106
+ - ".codeclimate.yml"
78
107
  - ".gitignore"
79
108
  - ".rubocop.yml"
80
109
  - Gemfile
@@ -86,6 +115,7 @@ files:
86
115
  - bin/setup
87
116
  - chusaku.gemspec
88
117
  - lib/chusaku.rb
118
+ - lib/chusaku/cli.rb
89
119
  - lib/chusaku/parser.rb
90
120
  - lib/chusaku/routes.rb
91
121
  - lib/chusaku/version.rb
@@ -112,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
142
  - !ruby/object:Gem::Version
113
143
  version: '0'
114
144
  requirements: []
115
- rubygems_version: 3.0.3
145
+ rubygems_version: 3.1.4
116
146
  signing_key:
117
147
  specification_version: 4
118
148
  summary: Annotate your Rails controllers with route info.