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 +7 -0
- data/.circleci/config.yml +32 -0
- data/.gitignore +8 -0
- data/.standard.yml +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +42 -0
- data/LICENSE.txt +20 -0
- data/README.md +249 -0
- data/Rakefile +16 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/referral +7 -0
- data/lib/referral/cli.rb +18 -0
- data/lib/referral/error.rb +3 -0
- data/lib/referral/expands_directories.rb +13 -0
- data/lib/referral/file_store.rb +13 -0
- data/lib/referral/filters_tokens.rb +51 -0
- data/lib/referral/git_store.rb +42 -0
- data/lib/referral/matches_token_names.rb +22 -0
- data/lib/referral/parses_options.rb +64 -0
- data/lib/referral/prints_results.rb +67 -0
- data/lib/referral/runner.rb +20 -0
- data/lib/referral/scans_tokens.rb +41 -0
- data/lib/referral/sorts_tokens.rb +46 -0
- data/lib/referral/token_types.rb +186 -0
- data/lib/referral/tokenizes_identifiers.rb +32 -0
- data/lib/referral/translates_node_to_token.rb +29 -0
- data/lib/referral/value/node_type.rb +15 -0
- data/lib/referral/value/options.rb +38 -0
- data/lib/referral/value/result.rb +6 -0
- data/lib/referral/value/token.rb +68 -0
- data/lib/referral/version.rb +3 -0
- data/lib/referral.rb +11 -0
- data/referral.gemspec +29 -0
- metadata +135 -0
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
data/.standard.yml
ADDED
data/Gemfile
ADDED
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
data/exe/referral
ADDED
data/lib/referral/cli.rb
ADDED
@@ -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,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
|