chusaku 0.1.4 → 0.4.0

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