referral 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9deae17c8a8b085401ea981eb523db328a3d838ef3d0b6b84ea37eeccb333122
4
+ data.tar.gz: f89b3f60756982c430d0adb24a8c9c2617ee1c96f3e01465e9f042ae1ff107e1
5
+ SHA512:
6
+ metadata.gz: 21a98b3027be2a52f5fbd52942a638074c3b2aba4c176916af9587d17467fc8d7351887eccfe8ef29a83fcf597c682de25b8681b29ad4f4c5efe31a84c8bb6f8
7
+ data.tar.gz: 4a7ef8a0a92c9faabb345159d5837fcd15917b3146f949a612ee60cd390266f9681d836d381898f2f1aa7bc9722c813843e956c01679cbdcf6ca8727613ae92a
@@ -0,0 +1,32 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ - image: circleci/ruby:2.6
10
+
11
+ working_directory: ~/repo
12
+
13
+ steps:
14
+ - checkout
15
+
16
+ - restore_cache:
17
+ keys:
18
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
19
+ - v1-dependencies-
20
+
21
+ - run:
22
+ name: install dependencies
23
+ command: |
24
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
25
+
26
+ - save_cache:
27
+ paths:
28
+ - ./vendor/bundle
29
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
30
+
31
+ - run: bundle exec rake
32
+
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ ignore:
2
+ - "test/fixture/**/*"
3
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ referral (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ jaro_winkler (1.5.3)
11
+ minitest (5.11.3)
12
+ parallel (1.17.0)
13
+ parser (2.6.3.0)
14
+ ast (~> 2.4.0)
15
+ psych (3.1.0)
16
+ rainbow (3.0.0)
17
+ rake (12.3.2)
18
+ rubocop (0.67.2)
19
+ jaro_winkler (~> 1.5.1)
20
+ parallel (~> 1.10)
21
+ parser (>= 2.5, != 2.5.1.1)
22
+ psych (>= 3.1.0)
23
+ rainbow (>= 2.2.2, < 4.0)
24
+ ruby-progressbar (~> 1.7)
25
+ unicode-display_width (>= 1.4.0, < 1.6)
26
+ ruby-progressbar (1.10.1)
27
+ standard (0.0.41)
28
+ rubocop (~> 0.67.1)
29
+ unicode-display_width (1.5.0)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ bundler (~> 1.17.3)
36
+ minitest (~> 5.0)
37
+ rake (~> 12.3)
38
+ referral!
39
+ standard
40
+
41
+ BUNDLED WITH
42
+ 1.17.3
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2019 Test Double, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # referral
2
+
3
+ Referral is a CLI toolkit for helping you undertake complex analysis and
4
+ refactoring of Ruby codebases. It finds, filters, and sorts the definitions & references of most of the
5
+ identifiers (e.g. classes, methods, and variables) throughout your code.
6
+
7
+ Think of `referral` as a toolkit for tracking down references to the code that
8
+ you want to change, offering a number of command-line options to quickly enable
9
+ you to do things like:
10
+
11
+ * Size up a codebase by gathering basic statistics and spotting usage hotspots
12
+ * Build a to-do list to help you manage a large or complex refactor
13
+ * Get a sense for how many callers would be impacted if you deleted a method
14
+ * Before renaming a module, verify there aren't any already other modules with
15
+ the new name
16
+ * Verify that you removed every reference to a deleted class before you merge
17
+ * Identify dead code, like method definitions that aren't invoked anywhere
18
+ * Catch references that haven't been updated since a change that affected them
19
+ (via `git-blame`)
20
+ * Rather than wait for warnings at runtime, quickly make a list of every call to
21
+ deprecated methods
22
+
23
+ Because Referral is powered by the introspection made possible by Ruby 2.6's
24
+ [RubyVM::AbstractSyntaxTree](https://ruby-doc.org/core-2.6.3/RubyVM/AbstractSyntaxTree.html)
25
+ API, it requires Ruby 2.6, but can often analyze codebases that target older
26
+ Rubies.
27
+
28
+ ## Install
29
+
30
+ From the command line:
31
+
32
+ ```
33
+ $ gem install referral
34
+ ```
35
+
36
+ Or in your `Gemfile`
37
+
38
+ ```ruby
39
+ gem "referral", require: false, group: :development
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ### Basic usage
45
+
46
+ At its most basic, you can just run `referral` and it'll scan `**/*.rb` from the
47
+ current working directory and print everything:
48
+
49
+ ```
50
+ $ referral
51
+ app/channels/application_cable/channel.rb:1:0: module ApplicationCable
52
+ app/channels/application_cable/channel.rb:2:2: class ApplicationCable Channel
53
+ app/channels/application_cable/channel.rb:2:18: constant ApplicationCable::Channel ActionCable::Channel::Base
54
+ # … and then another 2400 lines (which you can easily count with `referral | wc -l`)
55
+ ```
56
+
57
+ By default, Referral will sort entries by file, line, and column. Default output
58
+ is broken into 4 columns: `location`, `type`, `scope`, and `name`.
59
+
60
+ Everything above can be custom-tailored to your purposes, so let's work through
61
+ some examples below.
62
+
63
+ ### Build a refactoring to-do spreadsheet
64
+
65
+ When I'm undergoing a large refactor, I like to start by grepping around for all
66
+ the obvious definitions and references that might be affected. Suppose I'm
67
+ going to make major changes to my `User` class, I might search with the
68
+ `--exact-name` filter like this:
69
+
70
+ ```
71
+ referral --exact-name User,user,@user,@current_user
72
+ ```
73
+
74
+ [Fun fact: if I'd have wanted to match on partial names, I could have used the looser
75
+ `--name`, or for fully-qualified names (e.g. `API::User`), the stricter
76
+ `--full-name` option.]
77
+
78
+ Next, I usually find it easiest to work through a large refactor file-by-file,
79
+ but in certain cases where I'm looking for a specific type of reference, it
80
+ makes more sense to sort by the fully-qualified scope, which can be done with
81
+ `--sort scope`:
82
+
83
+ ```
84
+ referral --exact-name User,user,@user,@current_user --sort scope
85
+ ```
86
+
87
+ Of course, if we want a checklist, the default output could be made a lot nicer
88
+ for export to a spreadsheet app like [Numbers](https://www.apple.com/numbers/).
89
+
90
+ Here's what that might look like:
91
+
92
+ ```
93
+ referral --exact-name User,user,@user,@current_user --sort scope --print-headers --delimiter "\t" > user_refs.tsv
94
+ ```
95
+
96
+ Where `--print-headers` prints an initial row of the selected column names, and `--delimiter
97
+ "\t"` separates each field by a tab (making it easier to ingest for a
98
+ spreadsheet app like Excel or Numbers), before being redirected to the file
99
+ `user_refs.tsv`.
100
+
101
+ Now, to open it in Numbers, I'd run:
102
+
103
+ ```
104
+ open -a Numbers user_refs.tsv
105
+ ```
106
+
107
+ And be immediately greeted by a spreadsheet. Heck, why not throw a checkbox on
108
+ there while we're at it:
109
+
110
+ <img width="1272" alt="Screen Shot 2019-06-27 at 1 27 42 PM" src="https://user-images.githubusercontent.com/79303/60287234-64560a00-98df-11e9-9fed-46c68fdaac58.png">
111
+
112
+ ### Detect references you forgot to update
113
+
114
+ When working in a large codebase, it can be really tough to figure out if you
115
+ remembered to update every reference to a class or method across thousands of
116
+ files, so Referral ships with the ability to get some basic information from
117
+ `git-blame`, like this:
118
+
119
+ ```
120
+ referral --column file,line,git_sha,git_author,git_commit_at,full_name
121
+ ```
122
+
123
+ By setting `--column` to a comma-separated array that includes the above,
124
+ Referral will print results that look like these:
125
+
126
+ ```
127
+ test/lib/splits_furigana_test.rb 56 634edc04 searls@gmail.com 2017-09-04T13:34:09Z SplitsFuriganaTest#test_nasty_edge_cases.assert_equal
128
+ test/lib/splits_furigana_test.rb 56 634edc04 searls@gmail.com 2017-09-04T13:34:09Z h
129
+ test/lib/splits_furigana_test.rb 56 634edc04 searls@gmail.com 2017-09-04T13:34:09Z @subject.call
130
+ ```
131
+
132
+ [Warning: running `git-blame` on each file is, of course, a bit slow. Running
133
+ this command on the [KameSame](https://kamesame.com) codebase took 3 seconds of
134
+ wall-time, compared to 0.7 seconds by default.]
135
+
136
+ And it gets better! Since we're already running blame, why not sort every line
137
+ by its most and least recent commit time?!
138
+
139
+ You can see your least-recently updated references first by adding `--sort
140
+ least_recent_commit`, which does just what it says on the tin:
141
+
142
+ ```
143
+ referral --column file,line,git_sha,git_author,git_commit_at,full_name --sort least_recent_commit
144
+ ```
145
+
146
+ And I'll see that my least-recently-updated Ruby reference is:
147
+
148
+ ```
149
+ app/channels/application_cable/channel.rb 1 searls@gmail.com 2017-08-20T14:59:35Z ApplicationCable
150
+ ```
151
+
152
+ The inclusion of `git-blame` fields and sorting can be a powerful tool to
153
+ spot-check a large refactor before deciding to merge it in.
154
+
155
+ ### Search for a regex pattern and print the source
156
+
157
+ Once in a while, I'll want to scan line-by-line in a codebase for lines that
158
+ match a given pattern, and in those cases, the `--pattern` option and `source`
159
+ column can be a big help.
160
+
161
+ Suppose I'm trying to size up a codebase by looking for how many methods appear
162
+ to have a lot of arguments. While _definitely imperfect and regex cannot parse
163
+ context-free grammars_, I can get a rough gist by searching for any lines that
164
+ have 4 or more commas on them:
165
+
166
+ ```
167
+ referral --pattern "/^([^,]*,){4,}[^,]*$/" -c location,source
168
+ ```
169
+
170
+ Which would yield results like this one:
171
+
172
+ ```
173
+ app/lib/card.rb:22:2: def self.from_everything(id:, lesson_type:, item:, assignment:, meaning:)
174
+ ```
175
+
176
+ Naturally, other programs like `find` could do this just as well, but the added
177
+ ability to see & sort by when these lines were last updated in git might be
178
+ interesting. Additionally, suppose you only wanted to find method _definitions_
179
+ with a lot of (apparent) arguments? You could filter the matches down with
180
+ `--type instance_method,class_method`, too, like this:
181
+
182
+ ```
183
+ referral --pattern "/^([^,]*,){4,}[^,]*$/" -c location,git_commit_at,source -s most_recent_commit --type instance_method,class_method
184
+ ```
185
+
186
+ And I can see that as recently as June 6th, I apparently wrote a very long
187
+ method definition. `find` can't do that (I think)!
188
+
189
+ ```
190
+ app/lib/presents_review_result.rb:60:2: 2019-06-02T02:38:01Z def item_result(study_card_identifier, user, answer, item, learning, judgment, reward)
191
+ ```
192
+
193
+ ## Options
194
+
195
+ The help output of `referral --help` will print out the available options and
196
+ defaults:
197
+
198
+ ```
199
+ Usage: referral [options] files
200
+ -v, --version Prints the version
201
+ -h, --help Prints this help
202
+ -n, --name [NAME] Partial or complete name(s) to filter
203
+ --exact-name [NAME] Exact name(s) to filter
204
+ --full-name [NAME] Exact, fully-qualified name(s) to filter
205
+ -p, --pattern [PATTERN] Regex pattern to filter
206
+ -t, --type [TYPES] Include only certain types. See Referral::TOKEN_TYPES.
207
+ --include-unnamed Include reference without identifiers (default: false)
208
+ -s, --sort {file,scope} (default: file). See Referral::SORT_FUNCTIONS
209
+ --print-headers Print header names (default: false)
210
+ -c, --columns [COL1,COL2,COL3] (default: location,type,scope,name). See Referral::COLUMN_FUNCTIONS
211
+ -d, --delimiter [DELIM] String separating columns (default: ' ')
212
+ ```
213
+
214
+ A few things to note:
215
+
216
+ * Each of `--name`, `--exact-name`, `--full-name`, `--type`, and `--columns`
217
+ accept comma-separated arrays (e.g. `-n foo,bar,baz`)
218
+
219
+ * You can browse available sort functions [in
220
+ Refferral::SORT_FUNCTIONS](/lib/referral/sorts_tokens.rb) for use with
221
+ `--sort`. Each key is the name to be specified on the command line. (If you're
222
+ feeling adventurous, we've left the hash unfrozen so you can define your own
223
+ custom sorts dynamically, but YMMV.)
224
+
225
+ * Just like sort functions, you can find the available column types [in
226
+ Refferral::COLUMN_FUNCTIONS](/lib/referral/prints_results.rb) when passing a
227
+ comma-separated list to `--column`. (This hash has
228
+ also been left mutable for you, dear user.)
229
+
230
+ * The types of AST nodes that Referral supports can be found [in
231
+ Refferral::TOKEN_TYPES](/lib/referral/token_types.rb) when filtering to
232
+ certain `--type`
233
+
234
+ * Note that the columns `git_sha`, `git_author`, `git_commit_at` and the sort
235
+ functions `most_recent_commit` and `least_recent_commit` will incur a
236
+ `git-blame` invocation for each file counted among the filtered results
237
+
238
+ * Note that the `source` column and `--pattern` options will read each file in
239
+ the result set twice: once when parsing the AST, and again when printing
240
+ results
241
+
242
+ ## Code of Conduct
243
+
244
+ This project follows Test Double's [code of
245
+ conduct](https://testdouble.com/code-of-conduct) for all community interactions,
246
+ including (but not limited to) one-on-one communications, public posts/comments,
247
+ code reviews, pull requests, and GitHub issues. If violations occur, Test Double
248
+ will take any action they deem appropriate for the infraction, up to and
249
+ including blocking a user from the organization's repositories.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ begin
2
+ require "bundler/gem_tasks"
3
+ rescue TypeError
4
+ # Support Gel
5
+ end
6
+
7
+ require "rake/testtask"
8
+ require "standard/rake"
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << "test"
12
+ t.libs << "lib"
13
+ t.test_files = FileList["test/**/*_test.rb"]
14
+ end
15
+
16
+ task default: [:test, "standard:fix"]
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "referral"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/referral ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
4
+
5
+ require "referral"
6
+
7
+ Referral::Cli.new(ARGV.dup).call
@@ -0,0 +1,18 @@
1
+ require "referral/parses_options"
2
+ require "referral/prints_results"
3
+
4
+ module Referral
5
+ class Cli
6
+ def initialize(argv)
7
+ @options = ParsesOptions.new.call(argv)
8
+ end
9
+
10
+ def call
11
+ PrintsResults.new.call(Runner.new.call(@options), @options)
12
+ rescue => e
13
+ warn "FATAL ERROR: #{e.message}"
14
+ warn e.backtrace
15
+ exit 1
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Referral
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,13 @@
1
+ module Referral
2
+ class ExpandsDirectories
3
+ def call(files_and_directories)
4
+ files_and_directories.flat_map { |f_or_d|
5
+ if File.directory?(f_or_d)
6
+ Dir["#{f_or_d}/**/*.rb"]
7
+ else
8
+ f_or_d
9
+ end
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Referral
2
+ class FileStore
3
+ GROSS_FILE_CACHE = {}
4
+
5
+ def self.read_line(file, line)
6
+ read_file(file).split("\n")[line - 1]
7
+ end
8
+
9
+ def self.read_file(file)
10
+ GROSS_FILE_CACHE[file] ||= File.read(file)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ require "referral/matches_token_names"
2
+ require "referral/value/result"
3
+ require "referral/file_store"
4
+
5
+ module Referral
6
+ FILTER_FUNCTIONS = {
7
+ name: ->(token, names) {
8
+ names.any? { |name| token.full_name.include?(name) }
9
+ },
10
+ exact_name: ->(token, exact_names) {
11
+ exact_names.any? { |query|
12
+ MatchesTokenNames.subset(token, query)
13
+ }
14
+ },
15
+ full_name: ->(token, exact_names) {
16
+ exact_names.any? { |query|
17
+ MatchesTokenNames.entirely(token, query)
18
+ }
19
+ },
20
+ pattern: ->(token, regex) {
21
+ regex.match(token.full_name) || regex.match(FileStore.read_line(token.file, token.line))
22
+ },
23
+ type: ->(token, types) {
24
+ types.include?(token.node_type.name.to_s)
25
+ },
26
+ include_unnamed: ->(token, opt_val) {
27
+ if !opt_val
28
+ /\w/ =~ token.full_name
29
+ else
30
+ true
31
+ end
32
+ },
33
+ }
34
+ class FiltersTokens
35
+ def call(tokens, options)
36
+ filters = options.to_h.select { |opt_name, opt_val|
37
+ FILTER_FUNCTIONS.key?(opt_name) && !opt_val.nil?
38
+ }
39
+
40
+ if !filters.empty?
41
+ tokens.filter { |token|
42
+ filters.all? { |(opt_name, opt_val)|
43
+ FILTER_FUNCTIONS[opt_name].call(token, opt_val)
44
+ }
45
+ }
46
+ else
47
+ result
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ require "open3"
2
+ require "time"
3
+
4
+ module Referral
5
+ class GitStore
6
+ GROSS_BLAME_CACHE = {}
7
+
8
+ def self.sha(file, line)
9
+ return unless (output = blame_line(file, line))
10
+ return unless (match = output.match(/^(\w+)/))
11
+ match[1]
12
+ end
13
+
14
+ def self.author(file, line)
15
+ return unless (output = blame_line(file, line))
16
+ return unless (match = output.match(/\(<([^>]*?)>/))
17
+ match[1]
18
+ end
19
+
20
+ def self.time(file, line)
21
+ return unless (output = blame_line(file, line))
22
+ return unless (match = output.match(/\(<.*?>\s+(\d+)\s+/))
23
+ Time.at(Integer(match[1]))
24
+ end
25
+
26
+ def self.blame_line(file, line)
27
+ return unless (output = blame(file))
28
+ output.split("\n")[line - 1]
29
+ end
30
+
31
+ # This format will look like:
32
+ # a50eb722 (<searls@gmail.com> 1561643971 -0400 2) class FirstThing
33
+ # or
34
+ # a50eb722 old/file/path.rb (<searls@gmail.com> 1561643971 -0400 2) class FirstThing
35
+ def self.blame(file)
36
+ GROSS_BLAME_CACHE[file] ||= begin
37
+ out, _, status = Open3.capture3("git blame -e -t \"#{file}\"")
38
+ status.success? ? out : ""
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module Referral
2
+ class MatchesTokenNames
3
+ def self.subset(token, query)
4
+ token_tokens = names_from_token(token)
5
+ query_tokens = names_from_query(query)
6
+
7
+ token_tokens & query_tokens == query_tokens
8
+ end
9
+
10
+ def self.entirely(token, query)
11
+ names_from_token(token) == names_from_query(query)
12
+ end
13
+
14
+ def self.names_from_token(token)
15
+ token.fully_qualified.reject { |t| t.name.nil? }.map { |t| t.name.to_s }
16
+ end
17
+
18
+ def self.names_from_query(query)
19
+ query.split(Regexp.union(JOIN_SEPARATORS.values))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,64 @@
1
+ require "optparse"
2
+ require "referral/value/options"
3
+
4
+ module Referral
5
+ class ParsesOptions
6
+ def call(argv)
7
+ options = snake_case(run_optparse(argv))
8
+ Value::Options.default.merge(
9
+ merge_files(options, argv)
10
+ ).freeze
11
+ end
12
+
13
+ private
14
+
15
+ def run_optparse(argv)
16
+ {}.tap do |options|
17
+ op = OptionParser.new
18
+ op.banner += " files"
19
+ op.version = Referral::VERSION
20
+ version!(op)
21
+ help!(op)
22
+ op.on("-n", "--name [NAME]", Array, "Partial or complete name(s) to filter")
23
+ op.on("--exact-name [NAME]", Array, "Exact name(s) to filter")
24
+ op.on("--full-name [NAME]", Array, "Exact, fully-qualified name(s) to filter")
25
+ op.on("-p", "--pattern [PATTERN]", Regexp, "Regex pattern to filter")
26
+ op.on("-t", "--type [TYPES]", Array, "Include only certain types. See Referral::TOKEN_TYPES.")
27
+ op.on("--include-unnamed", TrueClass, "Include reference without identifiers (default: false)")
28
+ op.on("-s", "--sort {file,scope}", "(default: file). See Referral::SORT_FUNCTIONS")
29
+ op.on("--print-headers", TrueClass, "Print header names (default: false)")
30
+ op.on("-c", "--columns [COL1,COL2,COL3]", Array, "(default: location,type,scope,name). See Referral::COLUMN_FUNCTIONS")
31
+ op.on("-d", "--delimiter [DELIM]", "String separating columns (default: ' ')") do |v|
32
+ "\"#{v}\"".undump
33
+ end
34
+ op.parse!(argv, into: options)
35
+ end
36
+ end
37
+
38
+ def snake_case(options)
39
+ options.transform_keys { |k|
40
+ k.to_s.tr("-", "_").to_sym
41
+ }
42
+ end
43
+
44
+ def merge_files(options, argv)
45
+ options.merge(
46
+ files: argv.empty? ? Dir["**/*.rb"] : argv.dup
47
+ )
48
+ end
49
+
50
+ def version!(op)
51
+ op.on("-v", "--version", "Prints the version") do
52
+ puts VERSION
53
+ exit 0
54
+ end
55
+ end
56
+
57
+ def help!(op)
58
+ op.on("-h", "--help", "Prints this help") do
59
+ puts op
60
+ exit 0
61
+ end
62
+ end
63
+ end
64
+ end