method_log 0.0.2 → 0.0.3

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