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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8806c3155ef8119ce8144d2de6224cb9ec8c4cfb
4
- data.tar.gz: 223707b63c5ee375fc0a6735cd9a04a85361952a
3
+ metadata.gz: 8416c62e1edaaecff55eca789a8480abee33ed7e
4
+ data.tar.gz: e1a5011e4329bf97b0115bdaf95cf56107042b00
5
5
  SHA512:
6
- metadata.gz: 88c75c7ba8f9f1fe97257c873200c5e793c32a62060b1b01a4016c665b85ad3f4f8924f772903aebf0b8506cd2c701aaa90f757d1c04796184b2f005182a4524
7
- data.tar.gz: c1306ec22ca0401966bc6d66a5ebb695bde5acd85016b1edcf6f95922901a29822333c83055d868e61db2b1c1c6c7600bda9928df5e2c7e76c7a39d1ab02aefa
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 methods
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
- method_commits = api.history(ARGV[0])
11
- method_commits.each do |method_commit|
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
@@ -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
- @repository.commits.map do |commit|
12
- method_definitions = commit.source_files.inject([]) do |definitions, source_file|
13
- method_finder = MethodFinder.new(source_file: source_file)
14
- definitions += Array(method_finder.find(method_identifier))
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
@@ -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
- user = { email: 'test@example.com', name: 'test', time: Time.now }
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
- path = root.empty? ? blob_hash[:name] : File.join(root, blob_hash[:name])
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 to_s
17
- "#{commit}: #{method_definition}"
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
@@ -13,11 +13,8 @@ module MethodLog
13
13
  [source_file, lines].hash
14
14
  end
15
15
 
16
- def to_s
17
- [
18
- "#{source_file.path}:#{lines}",
19
- source_file.snippet(lines)
20
- ].join($/)
16
+ def source
17
+ source_file.snippet(lines)
21
18
  end
22
19
 
23
20
  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 'ripper'
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 < Ripper
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
- @namespaces = []
10
+ @scope = Scope::Root.new
11
11
  @methods = {}
12
- @at_scope_start = false
13
- parse
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 on_kw(name)
21
- case name
22
- when 'def'
23
- @line_number = lineno
24
- when 'class', 'module'
25
- @at_scope_start = true
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 on_const(name)
30
- if @at_scope_start
31
- @namespaces << name
32
- @at_scope_start = false
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 on_def(name, *args)
37
- identifier = "#{@namespaces.join('::')}##{name}"
38
- lines = (@line_number - 1)..(lineno - 1)
39
- definition = MethodDefinition.new(source_file: @source_file, lines: lines)
40
- @methods[identifier] = definition
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 on_class(*args)
44
- @namespaces.pop
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 on_module(*args)
48
- @namespaces.pop
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
@@ -1,3 +1,3 @@
1
1
  module MethodLog
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
data/method_log.gemspec CHANGED
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ['lib']
20
20
 
21
21
  s.add_dependency 'rugged'
22
+ s.add_dependency 'parser'
23
+ s.add_dependency 'diffy'
22
24
 
23
25
  s.add_development_dependency 'rake'
24
26
  s.add_development_dependency 'rspec'
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
@@ -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: 'source') }
9
- let(:method_definition) { MethodLog::MethodDefinition.new(source_file: source_file, lines: 5..7) }
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 'describes method definition' do
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.to_s).to eq(%{
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
@@ -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
@@ -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.2
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 00:00:00.000000000 Z
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.14
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