method_log 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -5
- data/bin/method_log +9 -3
- data/lib/method_log/api.rb +19 -5
- data/lib/method_log/commit.rb +19 -5
- data/lib/method_log/method_commit.rb +14 -2
- data/lib/method_log/method_definition.rb +2 -5
- data/lib/method_log/method_diff.rb +17 -0
- data/lib/method_log/method_finder.rb +64 -25
- data/lib/method_log/scope.rb +69 -0
- data/lib/method_log/version.rb +1 -1
- data/method_log.gemspec +2 -0
- data/spec/api_spec.rb +11 -3
- data/spec/commit_spec.rb +40 -0
- data/spec/method_commit_spec.rb +11 -2
- data/spec/method_definition_spec.rb +2 -7
- data/spec/method_diff_spec.rb +13 -0
- data/spec/method_finder_spec.rb +175 -0
- data/spec/scope_spec.rb +79 -0
- metadata +47 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8416c62e1edaaecff55eca789a8480abee33ed7e
|
4
|
+
data.tar.gz: e1a5011e4329bf97b0115bdaf95cf56107042b00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d221a1bf23774b980ef1b54869e1beca1ec9eac6a98e5fa628235c63e122658ec5f0667beb2ab3536945fd52b0e0d97dd3c59e5288d2f2c0b777d38e3b310a60
|
7
|
+
data.tar.gz: 787efa546b545eb8376f515d58b98d4161a53dc57a22d17157bae60b481d74ee05c44b4014a5f1499c6685a6defa0c9855067b5f2955467abf930ebfbbedf3bf
|
data/README.md
CHANGED
@@ -21,22 +21,24 @@ This is a work-in-progress and nowhere near production-ready.
|
|
21
21
|
### Todo
|
22
22
|
|
23
23
|
* Support earlier versions of Ruby (it ought to be possible to support down to v1.9.3 fairly easily)
|
24
|
-
* Support class
|
25
|
-
* Support namespaced classes e.g. `class Foo::Bar`
|
24
|
+
* Support absolute namespaces e.g. `class ::Foo::Bar`
|
26
25
|
* Support for Rspec tests
|
27
|
-
* Display diffs in method implementation between commits
|
28
|
-
* Only display diffs when method implementation has changed
|
29
|
-
* Default to looking for git repo in current working directory
|
30
26
|
* Default to looking for commits in current git branch
|
31
27
|
* Maybe add as new git command or extension to existing command e.g. `git log`
|
32
28
|
* Optimise search for method definitions:
|
29
|
+
* Only consider commits where file that last contained method has changed
|
33
30
|
* First look in file where method was last defined
|
34
31
|
* Simple text search for files containing method name to narrow files that need to be parsed
|
32
|
+
* Find "similar" method implementations e.g. by comparing ASTs of implementations
|
35
33
|
|
36
34
|
### Credits
|
37
35
|
|
38
36
|
Written by [James Mead](http://jamesmead.org) and the other members of [Go Free Range](http://gofreerange.com).
|
39
37
|
|
38
|
+
Thanks to Michael Feathers for some ideas in [delta-flora](https://github.com/michaelfeathers/delta-flora).
|
39
|
+
|
40
|
+
Thanks to [TICOSA](http://ticosa.org/) for giving me the impetus to do something about an idea I'd been kicking around for a while.
|
41
|
+
|
40
42
|
### License
|
41
43
|
|
42
44
|
Released under the [MIT License](https://github.com/freerange/method_log/blob/master/LICENSE).
|
data/bin/method_log
CHANGED
@@ -7,7 +7,13 @@ require 'method_log/api'
|
|
7
7
|
|
8
8
|
repository = MethodLog::Repository.new(path: Dir.pwd)
|
9
9
|
api = MethodLog::API.new(repository: repository)
|
10
|
-
|
11
|
-
|
12
|
-
puts method_commit
|
10
|
+
api.diffs(ARGV[0]).each do |method_commit, method_diff|
|
11
|
+
puts "commit #{method_commit.sha}"
|
12
|
+
puts "Author: #{method_commit.author[:name]} <#{method_commit.author[:email]}>"
|
13
|
+
puts "Date: #{method_commit.author[:time].strftime('%a %b %-e %T %Y %z')}"
|
14
|
+
puts
|
15
|
+
puts method_commit.message
|
16
|
+
puts
|
17
|
+
puts method_diff.to_s(:color)
|
18
|
+
puts
|
13
19
|
end
|
data/lib/method_log/api.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'method_log/method_finder'
|
2
2
|
require 'method_log/method_commit'
|
3
|
+
require 'method_log/method_diff'
|
3
4
|
|
4
5
|
module MethodLog
|
5
6
|
class API
|
@@ -8,12 +9,25 @@ module MethodLog
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def history(method_identifier)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
Enumerator.new do |yielder|
|
13
|
+
@repository.commits.each do |commit|
|
14
|
+
method_definitions = commit.source_files.inject([]) do |definitions, source_file|
|
15
|
+
method_finder = MethodFinder.new(source_file: source_file)
|
16
|
+
definitions += Array(method_finder.find(method_identifier))
|
17
|
+
end
|
18
|
+
yielder << MethodCommit.new(commit: commit, method_definition: method_definitions.first)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def diffs(method_identifier)
|
24
|
+
Enumerator.new do |yielder|
|
25
|
+
history(method_identifier).each_cons(2) do |(commit, parent)|
|
26
|
+
diff = MethodDiff.new(commit: commit, parent: parent)
|
27
|
+
unless diff.empty?
|
28
|
+
yielder << [commit, diff]
|
29
|
+
end
|
15
30
|
end
|
16
|
-
MethodCommit.new(commit: commit, method_definition: method_definitions.first)
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|
data/lib/method_log/commit.rb
CHANGED
@@ -17,24 +17,32 @@ module MethodLog
|
|
17
17
|
@index.add(path: source_file.path, oid: oid, mode: 0100644)
|
18
18
|
end
|
19
19
|
|
20
|
-
def apply
|
20
|
+
def apply(user: { email: 'test@example.com', name: 'test', time: Time.now }, message: 'commit-message')
|
21
21
|
tree = @index.write_tree(@repository)
|
22
22
|
parents = @repository.empty? ? [] : [@repository.head.target].compact
|
23
|
-
|
24
|
-
@sha = Rugged::Commit.create(@repository, tree: tree, parents: parents, update_ref: 'HEAD', author: user, committer: user, message: 'commit-message')
|
23
|
+
@sha = Rugged::Commit.create(@repository, tree: tree, parents: parents, update_ref: 'HEAD', author: user, committer: user, message: message)
|
25
24
|
end
|
26
25
|
|
27
26
|
def source_files
|
28
|
-
commit = @repository.lookup(sha)
|
29
27
|
source_files = []
|
30
28
|
commit.tree.walk_blobs do |root, blob_hash|
|
31
|
-
|
29
|
+
name = blob_hash[:name]
|
30
|
+
next unless File.extname(name) == '.rb'
|
31
|
+
path = root.empty? ? name : File.join(root, name)
|
32
32
|
source = @repository.lookup(blob_hash[:oid]).text
|
33
33
|
source_files << SourceFile.new(path: path, source: source)
|
34
34
|
end
|
35
35
|
source_files
|
36
36
|
end
|
37
37
|
|
38
|
+
def author
|
39
|
+
commit.author
|
40
|
+
end
|
41
|
+
|
42
|
+
def message
|
43
|
+
commit.message
|
44
|
+
end
|
45
|
+
|
38
46
|
def ==(other)
|
39
47
|
sha == other.sha
|
40
48
|
end
|
@@ -46,5 +54,11 @@ module MethodLog
|
|
46
54
|
def to_s
|
47
55
|
sha
|
48
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def commit
|
61
|
+
@commit ||= @repository.lookup(sha)
|
62
|
+
end
|
49
63
|
end
|
50
64
|
end
|
@@ -13,8 +13,20 @@ module MethodLog
|
|
13
13
|
[commit, method_definition].hash
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
16
|
+
def sha
|
17
|
+
commit.sha
|
18
|
+
end
|
19
|
+
|
20
|
+
def author
|
21
|
+
commit.author
|
22
|
+
end
|
23
|
+
|
24
|
+
def message
|
25
|
+
commit.message
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_source
|
29
|
+
method_definition && method_definition.source + $/
|
18
30
|
end
|
19
31
|
|
20
32
|
protected
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'diffy'
|
2
|
+
|
3
|
+
module MethodLog
|
4
|
+
class MethodDiff
|
5
|
+
def initialize(commit: nil, parent: nil)
|
6
|
+
@commit, @parent = commit, parent
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s(mode = :text)
|
10
|
+
Diffy::Diff.new(@commit.method_source, @parent.method_source).to_s(mode)
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
to_s.chomp.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,51 +1,90 @@
|
|
1
|
-
require '
|
1
|
+
require 'parser/current'
|
2
2
|
|
3
3
|
require 'method_log/method_definition'
|
4
|
+
require 'method_log/scope'
|
4
5
|
|
5
6
|
module MethodLog
|
6
|
-
class MethodFinder <
|
7
|
+
class MethodFinder < Parser::AST::Processor
|
7
8
|
def initialize(source_file: nil)
|
8
|
-
super(source_file.source)
|
9
9
|
@source_file = source_file
|
10
|
-
@
|
10
|
+
@scope = Scope::Root.new
|
11
11
|
@methods = {}
|
12
|
-
|
13
|
-
|
12
|
+
ast = Parser::CurrentRuby.parse(source_file.source)
|
13
|
+
process(ast)
|
14
14
|
end
|
15
15
|
|
16
16
|
def find(method_identifier)
|
17
17
|
@methods[method_identifier]
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
def on_module(node)
|
21
|
+
const_node = node.children.first
|
22
|
+
constants = process_const(const_node)
|
23
|
+
new_constant = constants.pop
|
24
|
+
with_scope(@scope.for(constants).define(new_constant)) { super }
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :on_class, :on_module
|
28
|
+
|
29
|
+
def on_sclass(node)
|
30
|
+
target_node = node.children.first
|
31
|
+
with_scope(singleton_scope_for(target_node)) { super }
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_def(node)
|
35
|
+
name, args_node, body_node = *node
|
36
|
+
record_method_definition(@scope, name, node)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_defs(node)
|
41
|
+
definee_node, name, args_node, body_node = *node
|
42
|
+
scope = singleton_scope_for(definee_node)
|
43
|
+
record_method_definition(scope, name, node)
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def singleton_scope_for(node)
|
50
|
+
case node.type
|
51
|
+
when :self
|
52
|
+
@scope.singleton
|
53
|
+
when :const
|
54
|
+
constants = process_const(node)
|
55
|
+
@scope.for(constants).singleton
|
56
|
+
else
|
57
|
+
@scope.define(node.inspect).singleton
|
26
58
|
end
|
27
59
|
end
|
28
60
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
61
|
+
def process_const(node, namespaces = [])
|
62
|
+
scope_node, name = *node
|
63
|
+
namespaces.unshift(name)
|
64
|
+
if scope_node
|
65
|
+
process_const(scope_node, namespaces)
|
33
66
|
end
|
67
|
+
namespaces
|
34
68
|
end
|
35
69
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
70
|
+
def lines_for(node)
|
71
|
+
expression = node.location.expression
|
72
|
+
first_line = expression.line - 1
|
73
|
+
last_line = expression.source_buffer.decompose_position(expression.end_pos).first - 1
|
74
|
+
first_line..last_line
|
41
75
|
end
|
42
76
|
|
43
|
-
def
|
44
|
-
@
|
77
|
+
def record_method_definition(scope, name, node)
|
78
|
+
definition = MethodDefinition.new(source_file: @source_file, lines: lines_for(node))
|
79
|
+
identifier = scope.method_identifier(name)
|
80
|
+
@methods[identifier] = definition
|
45
81
|
end
|
46
82
|
|
47
|
-
def
|
48
|
-
@
|
83
|
+
def with_scope(scope, &block)
|
84
|
+
@scope = scope
|
85
|
+
yield
|
86
|
+
ensure
|
87
|
+
@scope = scope.parent
|
49
88
|
end
|
50
89
|
end
|
51
90
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module MethodLog
|
2
|
+
class Scope
|
3
|
+
class Null < Scope
|
4
|
+
def initialize
|
5
|
+
super(name: 'null')
|
6
|
+
end
|
7
|
+
|
8
|
+
def lookup(name)
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Root < Scope
|
14
|
+
def initialize
|
15
|
+
super(name: 'root', parent: Scope::Null.new)
|
16
|
+
end
|
17
|
+
|
18
|
+
def names
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :parent
|
24
|
+
|
25
|
+
def initialize(name: nil, parent: nil, singleton: false)
|
26
|
+
@name = name
|
27
|
+
@parent = parent
|
28
|
+
@singleton = singleton
|
29
|
+
@modules = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def for(modules)
|
33
|
+
scope = self
|
34
|
+
modules.each do |mod|
|
35
|
+
scope = scope.lookup(mod) || scope.define(mod)
|
36
|
+
end
|
37
|
+
scope
|
38
|
+
end
|
39
|
+
|
40
|
+
def define(name)
|
41
|
+
@modules[name] = Scope.new(name: name, parent: self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def lookup(name)
|
45
|
+
@modules.fetch(name) { @parent.lookup(name) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def singleton
|
49
|
+
Scope.new(name: @name, parent: self, singleton: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_identifier(name)
|
53
|
+
[names.join('::'), separator, name].join
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def names
|
59
|
+
names = @singleton ? [] : [@name]
|
60
|
+
names = @parent.names + names
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def separator
|
66
|
+
@singleton ? '.' : '#'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/method_log/version.rb
CHANGED
data/method_log.gemspec
CHANGED
data/spec/api_spec.rb
CHANGED
@@ -23,7 +23,7 @@ describe MethodLog::API do
|
|
23
23
|
foo_1 = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
24
24
|
class Foo
|
25
25
|
def bar
|
26
|
-
# implementation
|
26
|
+
# implementation 1
|
27
27
|
end
|
28
28
|
end
|
29
29
|
}.strip)
|
@@ -32,7 +32,7 @@ end
|
|
32
32
|
# move method definition down one line
|
33
33
|
class Foo
|
34
34
|
def bar
|
35
|
-
# implementation
|
35
|
+
# implementation 2
|
36
36
|
end
|
37
37
|
end
|
38
38
|
}.strip)
|
@@ -49,7 +49,7 @@ end
|
|
49
49
|
|
50
50
|
repository = MethodLog::Repository.new(path: repository_path)
|
51
51
|
api = MethodLog::API.new(repository: repository)
|
52
|
-
method_commits = api.history('Foo#bar')
|
52
|
+
method_commits = api.history('Foo#bar').to_a
|
53
53
|
|
54
54
|
method_definition_1 = MethodLog::MethodDefinition.new(source_file: foo_1, lines: 1..3)
|
55
55
|
method_definition_2 = MethodLog::MethodDefinition.new(source_file: foo_2, lines: 2..4)
|
@@ -58,5 +58,13 @@ end
|
|
58
58
|
method_commit_2 = MethodLog::MethodCommit.new(commit: commit_2, method_definition: method_definition_2)
|
59
59
|
|
60
60
|
expect(method_commits).to eq([method_commit_2, method_commit_1])
|
61
|
+
|
62
|
+
method_commit, method_diff = api.diffs('Foo#bar').first
|
63
|
+
expect(method_diff.to_s.strip).to eq(%{
|
64
|
+
def bar
|
65
|
+
- # implementation 2
|
66
|
+
+ # implementation 1
|
67
|
+
end
|
68
|
+
}.strip)
|
61
69
|
end
|
62
70
|
end
|
data/spec/commit_spec.rb
CHANGED
@@ -41,5 +41,45 @@ describe MethodLog::Commit do
|
|
41
41
|
commit = repository.commits.first
|
42
42
|
expect(commit.source_files).to eq([source_one, source_two])
|
43
43
|
end
|
44
|
+
|
45
|
+
it 'only includes source files with ruby file extension' do
|
46
|
+
source_file = MethodLog::SourceFile.new(path: 'path/to/source_one.py', source: 'source-file')
|
47
|
+
|
48
|
+
repository = MethodLog::Repository.new(path: repository_path)
|
49
|
+
commit = repository.build_commit
|
50
|
+
commit.add(source_file)
|
51
|
+
commit.apply
|
52
|
+
|
53
|
+
repository = MethodLog::Repository.new(path: repository_path)
|
54
|
+
commit = repository.commits.first
|
55
|
+
expect(commit.source_files).to be_empty
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'makes author available' do
|
59
|
+
user = { email: 'test@example.com', name: 'test', time: Time.now.round }
|
60
|
+
source_file = MethodLog::SourceFile.new(path: 'path/to/source.rb', source: 'source')
|
61
|
+
|
62
|
+
repository = MethodLog::Repository.new(path: repository_path)
|
63
|
+
commit = repository.build_commit
|
64
|
+
commit.add(source_file)
|
65
|
+
commit.apply(user: user)
|
66
|
+
|
67
|
+
repository = MethodLog::Repository.new(path: repository_path)
|
68
|
+
commit = repository.commits.first
|
69
|
+
expect(commit.author).to eq(user)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'makes message available' do
|
73
|
+
source_file = MethodLog::SourceFile.new(path: 'path/to/source.rb', source: 'source')
|
74
|
+
|
75
|
+
repository = MethodLog::Repository.new(path: repository_path)
|
76
|
+
commit = repository.build_commit
|
77
|
+
commit.add(source_file)
|
78
|
+
commit.apply(message: 'commit-message')
|
79
|
+
|
80
|
+
repository = MethodLog::Repository.new(path: repository_path)
|
81
|
+
commit = repository.commits.first
|
82
|
+
expect(commit.message).to eq('commit-message')
|
83
|
+
end
|
44
84
|
end
|
45
85
|
end
|
data/spec/method_commit_spec.rb
CHANGED
@@ -5,8 +5,8 @@ require 'method_log/method_definition'
|
|
5
5
|
|
6
6
|
describe MethodLog::MethodCommit do
|
7
7
|
let(:commit) { MethodLog::Commit.new(sha: nil) }
|
8
|
-
let(:source_file) { MethodLog::SourceFile.new(path: '/path/to/source.rb', source:
|
9
|
-
let(:method_definition) { MethodLog::MethodDefinition.new(source_file: source_file, lines:
|
8
|
+
let(:source_file) { MethodLog::SourceFile.new(path: '/path/to/source.rb', source: "line 0\nline 1\nline 2\n") }
|
9
|
+
let(:method_definition) { MethodLog::MethodDefinition.new(source_file: source_file, lines: 0..1) }
|
10
10
|
let(:method_commit) {
|
11
11
|
MethodLog::MethodCommit.new(commit: commit, method_definition: method_definition)
|
12
12
|
}
|
@@ -21,4 +21,13 @@ describe MethodLog::MethodCommit do
|
|
21
21
|
it 'has same hash as another method commit with same commit and method definition' do
|
22
22
|
expect(method_commit.hash).to eq(method_commit_with_same_commit_and_method_definition.hash)
|
23
23
|
end
|
24
|
+
|
25
|
+
it 'returns method source with trailing newline' do
|
26
|
+
expect(method_commit.method_source).to eq("line 0\nline 1\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns nil if no method definition' do
|
30
|
+
method_commit = MethodLog::MethodCommit.new(commit: commit, method_definition: nil)
|
31
|
+
expect(method_commit.method_source).to be_nil
|
32
|
+
end
|
24
33
|
end
|
@@ -26,14 +26,9 @@ end
|
|
26
26
|
expect(definition_one.hash).to eq(definition_two.hash)
|
27
27
|
end
|
28
28
|
|
29
|
-
it '
|
29
|
+
it 'provides access to the method source' do
|
30
30
|
definition = MethodLog::MethodDefinition.new(source_file: source_file, lines: 1..3)
|
31
31
|
|
32
|
-
expect(definition.
|
33
|
-
path/to/source.rb:1..3
|
34
|
-
def bar
|
35
|
-
# implementation
|
36
|
-
end
|
37
|
-
}.strip)
|
32
|
+
expect(definition.source).to eq(%{ def bar\n # implementation\n end})
|
38
33
|
end
|
39
34
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'method_log/method_diff'
|
2
|
+
|
3
|
+
describe MethodLog::MethodDiff do
|
4
|
+
let(:commit) { double(:commit) }
|
5
|
+
let(:parent) { double(:parent) }
|
6
|
+
let(:diff) { MethodLog::MethodDiff.new(commit: commit, parent: parent) }
|
7
|
+
|
8
|
+
it 'generates text diff of the method source for two commits' do
|
9
|
+
commit.stub(:method_source).and_return(%{line 1\nline 2\n})
|
10
|
+
parent.stub(:method_source).and_return(%{line 2\nline 3\n})
|
11
|
+
expect(diff.to_s).to eq(%{-line 1\n line 2\n+line 3\n})
|
12
|
+
end
|
13
|
+
end
|
data/spec/method_finder_spec.rb
CHANGED
@@ -50,4 +50,179 @@ end
|
|
50
50
|
|
51
51
|
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 2..4))
|
52
52
|
end
|
53
|
+
|
54
|
+
it 'finds definition of instance method on namespaced class within previously defined module' do
|
55
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
56
|
+
module Foo
|
57
|
+
end
|
58
|
+
|
59
|
+
class Foo::Bar
|
60
|
+
def baz
|
61
|
+
# implementation
|
62
|
+
end
|
63
|
+
end
|
64
|
+
}.strip)
|
65
|
+
|
66
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
67
|
+
method_definition = method_finder.find('Foo::Bar#baz')
|
68
|
+
|
69
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 4..6))
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'finds definition of instance method on namespaced module within previously defined module' do
|
73
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
74
|
+
module Foo
|
75
|
+
end
|
76
|
+
|
77
|
+
module Foo::Bar
|
78
|
+
def baz
|
79
|
+
# implementation
|
80
|
+
end
|
81
|
+
end
|
82
|
+
}.strip)
|
83
|
+
|
84
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
85
|
+
method_definition = method_finder.find('Foo::Bar#baz')
|
86
|
+
|
87
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 4..6))
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'finds definition of class method on class' do
|
91
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
92
|
+
module Foo
|
93
|
+
def self.bar
|
94
|
+
# implementation
|
95
|
+
end
|
96
|
+
end
|
97
|
+
}.strip)
|
98
|
+
|
99
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
100
|
+
method_definition = method_finder.find('Foo.bar')
|
101
|
+
|
102
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 1..3))
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'finds definition of class method on class with explicit reference to class' do
|
106
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
107
|
+
module Foo
|
108
|
+
def Foo.bar
|
109
|
+
# implementation
|
110
|
+
end
|
111
|
+
end
|
112
|
+
}.strip)
|
113
|
+
|
114
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
115
|
+
method_definition = method_finder.find('Foo.bar')
|
116
|
+
|
117
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 1..3))
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'finds definition of class method on class with explicit reference to namespaced class' do
|
121
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
122
|
+
module Foo
|
123
|
+
class Bar
|
124
|
+
def (Foo::Bar).baz
|
125
|
+
# implementation
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
}.strip)
|
130
|
+
|
131
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
132
|
+
method_definition = method_finder.find('Foo::Bar.baz')
|
133
|
+
|
134
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 2..4))
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'finds definition of class method on class with explicit reference to namespaced class outside current scope' do
|
138
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
139
|
+
class Foo
|
140
|
+
end
|
141
|
+
|
142
|
+
class Bar
|
143
|
+
def Foo.baz
|
144
|
+
# implementation
|
145
|
+
end
|
146
|
+
end
|
147
|
+
}.strip)
|
148
|
+
|
149
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
150
|
+
method_definition = method_finder.find('Foo.baz')
|
151
|
+
|
152
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 4..6))
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'finds definition of class method on class when singleton class is re-opened' do
|
156
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
157
|
+
class Foo
|
158
|
+
class << self
|
159
|
+
def bar
|
160
|
+
# implementation
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
}.strip)
|
165
|
+
|
166
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
167
|
+
method_definition = method_finder.find('Foo.bar')
|
168
|
+
|
169
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 2..4))
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'finds definition of class method on class when singleton class is re-opened with explicit reference to class' do
|
173
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
174
|
+
class Foo
|
175
|
+
class << Foo
|
176
|
+
def bar
|
177
|
+
# implementation
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
}.strip)
|
182
|
+
|
183
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
184
|
+
method_definition = method_finder.find('Foo.bar')
|
185
|
+
|
186
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 2..4))
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'finds definition of class method on class when singleton class is re-opened with explicit reference to class outside current scope' do
|
190
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
191
|
+
class Foo
|
192
|
+
end
|
193
|
+
|
194
|
+
class Bar
|
195
|
+
class << Foo
|
196
|
+
def bar
|
197
|
+
# implementation
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
}.strip)
|
202
|
+
|
203
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
204
|
+
method_definition = method_finder.find('Foo.bar')
|
205
|
+
|
206
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 5..7))
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'finds definition of class method on unknown object when singleton class is re-opened' do
|
210
|
+
foo = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
|
211
|
+
class Foo
|
212
|
+
def initialize
|
213
|
+
@foo = new
|
214
|
+
class << @foo
|
215
|
+
def bar
|
216
|
+
# implementation
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
}.strip)
|
222
|
+
|
223
|
+
method_finder = MethodLog::MethodFinder.new(source_file: foo)
|
224
|
+
method_definition = method_finder.find('Foo::(ivar :@foo).bar')
|
225
|
+
|
226
|
+
expect(method_definition).to eq(MethodLog::MethodDefinition.new(source_file: foo, lines: 4..6))
|
227
|
+
end
|
53
228
|
end
|
data/spec/scope_spec.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'method_log/scope'
|
4
|
+
|
5
|
+
describe MethodLog::Scope do
|
6
|
+
let(:root) { MethodLog::Scope::Root.new }
|
7
|
+
|
8
|
+
it 'ruby interactive style method identifier for top-level module' do
|
9
|
+
a = root.define(:A)
|
10
|
+
expect(a.method_identifier('foo')).to eq('A#foo')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'ruby interactive style method identifier for module nested inside top-level module' do
|
14
|
+
a = root.define(:A)
|
15
|
+
b = a.define(:B)
|
16
|
+
expect(b.method_identifier('foo')).to eq('A::B#foo')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'ruby interactive style method identifier for module nested inside module nested inside top-level module' do
|
20
|
+
a = root.define(:A)
|
21
|
+
b = a.define(:B)
|
22
|
+
c = b.define(:C)
|
23
|
+
expect(c.method_identifier('foo')).to eq('A::B::C#foo')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'ruby interactive style method identifier for method on singleton class' do
|
27
|
+
a = root.define(:A)
|
28
|
+
singleton = a.singleton
|
29
|
+
expect(singleton.method_identifier('foo')).to eq('A.foo')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'ruby interactive style method identifier for method on singleton class nested inside top-level module' do
|
33
|
+
a = root.define(:A)
|
34
|
+
b = a.define(:B)
|
35
|
+
singleton = b.singleton
|
36
|
+
expect(singleton.method_identifier('foo')).to eq('A::B.foo')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'looks up scope for top-level module' do
|
40
|
+
a = root.define(:A)
|
41
|
+
expect(a.lookup(:A)).to eq(a)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'looks up scopes for top-level module and nested module from nested module' do
|
45
|
+
a = root.define(:A)
|
46
|
+
b = a.define(:B)
|
47
|
+
expect(b.lookup(:A)).to eq(a)
|
48
|
+
expect(b.lookup(:B)).to eq(b)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'looks up scopes for modules from double-nested module' do
|
52
|
+
a = root.define(:A)
|
53
|
+
b = a.define(:B)
|
54
|
+
c = b.define(:C)
|
55
|
+
expect(c.lookup(:A)).to eq(a)
|
56
|
+
expect(c.lookup(:B)).to eq(b)
|
57
|
+
expect(c.lookup(:C)).to eq(c)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'looks up qualified module' do
|
61
|
+
a = root.define(:A)
|
62
|
+
b = a.define(:B)
|
63
|
+
c = b.define(:C)
|
64
|
+
expect(c.for([:A])).to eq(a)
|
65
|
+
expect(c.for([:A, :B])).to eq(b)
|
66
|
+
expect(c.for([:A, :B, :C])).to eq(c)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'defines missing modules' do
|
70
|
+
root.for([:A, :B, :C])
|
71
|
+
a = root.lookup(:A)
|
72
|
+
b = a.lookup(:B)
|
73
|
+
c = b.lookup(:C)
|
74
|
+
expect(a).not_to be_nil
|
75
|
+
expect(b).not_to be_nil
|
76
|
+
expect(c).not_to be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
metadata
CHANGED
@@ -1,55 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: method_log
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Mead
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rugged
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: parser
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: diffy
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
25
53
|
- !ruby/object:Gem::Version
|
26
54
|
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
|
-
- -
|
59
|
+
- - ">="
|
32
60
|
- !ruby/object:Gem::Version
|
33
61
|
version: '0'
|
34
62
|
type: :development
|
35
63
|
prerelease: false
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
37
65
|
requirements:
|
38
|
-
- -
|
66
|
+
- - ">="
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rspec
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
|
-
- -
|
73
|
+
- - ">="
|
46
74
|
- !ruby/object:Gem::Version
|
47
75
|
version: '0'
|
48
76
|
type: :development
|
49
77
|
prerelease: false
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
|
-
- -
|
80
|
+
- - ">="
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '0'
|
55
83
|
description:
|
@@ -60,8 +88,8 @@ executables:
|
|
60
88
|
extensions: []
|
61
89
|
extra_rdoc_files: []
|
62
90
|
files:
|
63
|
-
- .gitignore
|
64
|
-
- .travis.yml
|
91
|
+
- ".gitignore"
|
92
|
+
- ".travis.yml"
|
65
93
|
- Gemfile
|
66
94
|
- LICENSE
|
67
95
|
- README.md
|
@@ -72,8 +100,10 @@ files:
|
|
72
100
|
- lib/method_log/commit.rb
|
73
101
|
- lib/method_log/method_commit.rb
|
74
102
|
- lib/method_log/method_definition.rb
|
103
|
+
- lib/method_log/method_diff.rb
|
75
104
|
- lib/method_log/method_finder.rb
|
76
105
|
- lib/method_log/repository.rb
|
106
|
+
- lib/method_log/scope.rb
|
77
107
|
- lib/method_log/source_file.rb
|
78
108
|
- lib/method_log/version.rb
|
79
109
|
- method_log.gemspec
|
@@ -93,7 +123,9 @@ files:
|
|
93
123
|
- spec/commit_spec.rb
|
94
124
|
- spec/method_commit_spec.rb
|
95
125
|
- spec/method_definition_spec.rb
|
126
|
+
- spec/method_diff_spec.rb
|
96
127
|
- spec/method_finder_spec.rb
|
128
|
+
- spec/scope_spec.rb
|
97
129
|
- spec/source_file_spec.rb
|
98
130
|
- spec/spec_helper.rb
|
99
131
|
homepage: http://jamesmead.org
|
@@ -106,17 +138,17 @@ require_paths:
|
|
106
138
|
- lib
|
107
139
|
required_ruby_version: !ruby/object:Gem::Requirement
|
108
140
|
requirements:
|
109
|
-
- -
|
141
|
+
- - ">="
|
110
142
|
- !ruby/object:Gem::Version
|
111
143
|
version: '0'
|
112
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
145
|
requirements:
|
114
|
-
- -
|
146
|
+
- - ">="
|
115
147
|
- !ruby/object:Gem::Version
|
116
148
|
version: '0'
|
117
149
|
requirements: []
|
118
150
|
rubyforge_project:
|
119
|
-
rubygems_version: 2.0
|
151
|
+
rubygems_version: 2.2.0
|
120
152
|
signing_key:
|
121
153
|
specification_version: 4
|
122
154
|
summary: Trace the history of an individual method in a git repository.
|
@@ -125,6 +157,8 @@ test_files:
|
|
125
157
|
- spec/commit_spec.rb
|
126
158
|
- spec/method_commit_spec.rb
|
127
159
|
- spec/method_definition_spec.rb
|
160
|
+
- spec/method_diff_spec.rb
|
128
161
|
- spec/method_finder_spec.rb
|
162
|
+
- spec/scope_spec.rb
|
129
163
|
- spec/source_file_spec.rb
|
130
164
|
- spec/spec_helper.rb
|