issue-beaver 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,7 +14,7 @@ module Enumerable
14
14
  id = id_method ? x.send(id_method) : x
15
15
  yielder << x unless yielded_ids.include? id
16
16
  end
17
- end
17
+ end.lazy
18
18
  end
19
19
 
20
20
  # The left enumerator is accessed first
@@ -1,4 +1,6 @@
1
1
  require 'treetop'
2
2
 
3
3
  require 'issue_beaver/grammars/ruby_comments'
4
- Treetop.load File.expand_path('../grammars/ruby_comments.treetop', __FILE__)
4
+ require 'issue_beaver/grammars/github_issue_body'
5
+ Treetop.load File.expand_path('../grammars/ruby_comments.treetop', __FILE__)
6
+ Treetop.load File.expand_path('../grammars/github_issue_body.treetop', __FILE__)
@@ -0,0 +1,11 @@
1
+ module IssueBeaver
2
+ module Grammars
3
+ module GithubIssueBody
4
+ MAGIC = "ISSUEBEAVER_ISSUE"
5
+
6
+ def self.build_mark(file, begin_line, commit)
7
+ "<!-- #{MAGIC}[#{file}:#{begin_line}@#{commit}] -->"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ module IssueBeaver
2
+ module Grammars
3
+ grammar GithubIssueBody
4
+ rule details
5
+ (mark / .)* {
6
+ def details
7
+ details = elements.find{|e| e.respond_to?(:is_mark?) }
8
+ return nil unless details
9
+
10
+ {
11
+ commit: details.commit_id.text_value.to_s,
12
+ begin: details.begin_line.text_value.to_i,
13
+ file: details.file.text_value.to_s
14
+ }
15
+ end
16
+ }
17
+ end
18
+
19
+ # Example for a mark:
20
+ # <!-- ISSUEBEAVER_ISSUE[fixtures/ruby.rb:2@879bee2f50] -->
21
+ # Update GithubIssueBody.build_mark when changing this!
22
+ rule mark
23
+ '<!--' white? magic_string '[' file ':' begin_line '@' commit_id ']' white? '-->' {
24
+ def is_mark?
25
+ true
26
+ end
27
+ }
28
+ end
29
+
30
+ rule magic_string
31
+ 'ISSUEBEAVER_ISSUE'
32
+ end
33
+
34
+ rule file
35
+ [^:]+
36
+ end
37
+
38
+ rule begin_line
39
+ [0-9]+
40
+ end
41
+
42
+ rule commit_id
43
+ [0-9a-fA-F]+
44
+ end
45
+
46
+ rule white
47
+ [ \t]+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -45,6 +45,17 @@ module IssueBeaver
45
45
  end
46
46
 
47
47
 
48
+ def head_commit
49
+ @head_commit ||= `git rev-parse HEAD`.chomp
50
+ end
51
+
52
+
53
+ def is_ahead?(commit)
54
+ return false unless commit
55
+ !`git rev-list "#{head_commit}" | grep $(git rev-parse "#{commit}")`.chomp.empty?
56
+ end
57
+
58
+
48
59
  private
49
60
 
50
61
  def discover_root_dir(dir)
@@ -9,10 +9,14 @@ module IssueBeaver
9
9
  @repository = repository
10
10
 
11
11
  class << self
12
- delegate :update, :create, :default_attributes, :first, to: :@repository
12
+ delegate :update, :create, :first, to: :@repository
13
13
  end
14
14
  end
15
15
 
16
+ def self.default_attributes
17
+ @repository ? @repository.default_attributes : {}
18
+ end
19
+
16
20
 
17
21
  def self.repo_name() @repository.repo if @repository end
18
22
 
@@ -23,8 +27,7 @@ module IssueBeaver
23
27
 
24
28
 
25
29
  include Shared::AttributesModel
26
- ATTRIBUTES = [:number, :state, :title, :body, :file, :begin_line, :created_at, :updated_at, :labels, :assignee]
27
-
30
+ ATTRIBUTES = [:number, :state, :title, :body, :file, :begin_line, :created_at, :updated_at, :labels, :assignee, :commit]
28
31
 
29
32
  def closed?() state == "closed" end
30
33
 
@@ -41,6 +44,15 @@ module IssueBeaver
41
44
  def must_update?() changed_attributes_for_update.any? end
42
45
 
43
46
 
47
+ def must_close?() !!@must_close end
48
+
49
+
50
+ def mark_for_closing(flag = true)
51
+ @must_close = flag
52
+ self.state = "closed"
53
+ end
54
+
55
+
44
56
  def changed_attributes_for_update
45
57
  Hashie::Mash.new(changed_attributes).only(:title, :body, :assignee)
46
58
  end
@@ -71,6 +83,14 @@ module IssueBeaver
71
83
  self.created_at = Time.parse(created_at) if created_at.kind_of?(String)
72
84
  self.updated_at = Time.parse(updated_at) if updated_at.kind_of?(String)
73
85
  self.assignee = assignee.login if assignee.respond_to?(:login)
86
+ self.body ||= ""
87
+
88
+ if details = Grammars::GithubIssueBodyParser.new.parse(self.body).details
89
+ self.commit = details[:commit]
90
+ self.begin_line = details[:begin]
91
+ self.file = details[:file]
92
+ end
93
+
74
94
  @changed_attributes = nil
75
95
  end
76
96
 
@@ -80,10 +100,13 @@ module IssueBeaver
80
100
  self.title ||= ""
81
101
  self.title = "#{self.title.capitalize} (#{basename}:#{self.begin_line})"
82
102
  github_line_url = "https://github.com/#{self.class.repo_name}/blob/master/#{self.file}\#L#{self.begin_line}"
83
- self.body = %Q{#{self.body}\n\n#{github_line_url}}
103
+ self.body = %Q{#{self.body}\n\n#{github_line_url}\n\n#{magic_mark}}
84
104
  @changed_attributes = nil
85
105
  end
86
106
 
107
+ def magic_mark
108
+ Grammars::GithubIssueBody.build_mark(file, begin_line, commit)
109
+ end
87
110
 
88
111
  def update_attributes_with_limit(attrs)
89
112
  update_attributes_without_limit(attrs.only(:title, :body, :updated_at, :assignee))
@@ -97,7 +120,7 @@ module IssueBeaver
97
120
  elsif changed?
98
121
  @attributes = self.class.update(number, attributes)
99
122
  end
100
- @changed_attributes.clear
123
+ @changed_attributes.clear if @changed_attributes
101
124
  true
102
125
  end
103
126
 
@@ -4,9 +4,10 @@ module IssueBeaver
4
4
  module Models
5
5
  class Merger
6
6
 
7
- def initialize(issues, todos)
7
+ def initialize(issues, todos, repo)
8
8
  @issues = issues
9
9
  @matcher = Matcher.new(@issues, todos)
10
+ @repo = repo
10
11
  end
11
12
 
12
13
 
@@ -21,12 +22,14 @@ module IssueBeaver
21
22
 
22
23
 
23
24
  def changed
24
- @changed ||= merged_issues.select{|e| e.must_update? || e.new? }
25
+ @changed ||= added.merge_left(modified).merge_left(closed, :number)
25
26
  end
26
27
 
27
28
 
28
- # TODO: Detect removed TODO comments and close Issue on Github
29
- # Can probably be done by looking up the git history of a file.
29
+ def closed
30
+ @closed ||= unmatched_issues.select(&:must_close?)
31
+ end
32
+
30
33
 
31
34
  def merged_issues
32
35
  @merged_issues ||=
@@ -41,6 +44,18 @@ module IssueBeaver
41
44
  issue
42
45
  end
43
46
  end
47
+
48
+ # TODO: Tidy up a bit
49
+ def unmatched_issues
50
+ @unmatched_issues ||=
51
+ Enumerator.new do |yielder|
52
+ @issues.to_a.each do |issue|
53
+ next if merged_issues.to_a.map(&:number).include? issue.number
54
+ issue.mark_for_closing if @repo.is_ahead?(issue.commit)
55
+ yielder << issue
56
+ end
57
+ end.memoizing.lazy
58
+ end
44
59
  end
45
60
 
46
61
 
@@ -9,21 +9,22 @@ module IssueBeaver
9
9
  module Models
10
10
  class TodoComments
11
11
 
12
- def initialize(root_dir, files)
12
+ def initialize(root_dir, files, head)
13
13
  @root_dir = root_dir
14
14
  @files = files
15
+ @head = head
15
16
  end
16
17
 
17
18
 
18
19
  def all
19
- @todos ||= enum_scanned_files(@files).memoizing.lazy
20
+ @todos ||= enum_scanned_files(@files, @head).memoizing.lazy
20
21
  end
21
22
 
22
23
 
23
24
  private
24
25
 
25
26
  # TODO: Allow individual TODOs to follow right after each other without newline in between
26
- def enum_scanned_files(files)
27
+ def enum_scanned_files(files, head_commit)
27
28
  Enumerator.new do |yielder|
28
29
  todos = []
29
30
  parser = Grammars::RubyCommentsParser.new
@@ -34,7 +35,8 @@ module IssueBeaver
34
35
  yielder << new_todo(
35
36
  comment.merge('file' => relative_path(file),
36
37
  'created_at' => File.ctime(file),
37
- 'updated_at' => File.ctime(file)
38
+ 'updated_at' => File.ctime(file),
39
+ 'commit' => head_commit
38
40
  ))
39
41
  }
40
42
  end
@@ -119,7 +119,8 @@ module IssueBeaver
119
119
  @todo_comments ||=
120
120
  Models::TodoComments.new(
121
121
  repo(config).root_dir,
122
- repo(config).files(config['dir'])
122
+ repo(config).files(config['dir']),
123
+ repo(config).head_commit
123
124
  )
124
125
  end
125
126
 
@@ -139,8 +140,8 @@ module IssueBeaver
139
140
  end
140
141
 
141
142
 
142
- def merger(a, b)
143
- @merger ||= Models::Merger.new(a, b)
143
+ def merger(a, b, repo = repo)
144
+ @merger ||= Models::Merger.new(a, b, repo)
144
145
  end
145
146
 
146
147
 
@@ -40,6 +40,8 @@ module IssueBeaver
40
40
  def modifier
41
41
  if new?
42
42
  "added"
43
+ elsif must_close?
44
+ "closed"
43
45
  elsif changed?
44
46
  "modified"
45
47
  end
@@ -1,3 +1,3 @@
1
1
  module IssueBeaver
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,4 @@
1
+ And this should be found
2
+ as the body
3
+
4
+ <!-- ISSUEBEAVER_ISSUE[fixtures/ruby.rb:2@879bee2f50] -->
@@ -0,0 +1,34 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe IssueBeaver::Grammars::RubyCommentsParser do
4
+ subject { IssueBeaver::Grammars::GithubIssueBodyParser.new.parse(body) }
5
+ let(:details) {subject.details}
6
+
7
+ context "github_issue.txt" do
8
+ let(:body) { File.read('./spec/fixtures/github_issue.txt') }
9
+
10
+ it "should find a magic mark" do
11
+ details.should_not == nil
12
+ end
13
+
14
+ it "should find the file name" do
15
+ details[:file].should == "fixtures/ruby.rb"
16
+ end
17
+
18
+ it "should find the line number" do
19
+ details[:begin].should == 2
20
+ end
21
+
22
+ it "should find the commit id" do
23
+ details[:commit].should == "879bee2f50"
24
+ end
25
+ end
26
+
27
+ context "with empty body" do
28
+ let(:body){""}
29
+
30
+ it "should return nil" do
31
+ details.should == nil
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe IssueBeaver::Models::GithubIssue do
4
+ let(:body) { File.read('./spec/fixtures/github_issue.txt') }
5
+ subject { IssueBeaver::Models::GithubIssue.new_from_github(body: body, title: "Foo") }
6
+ let(:attrs) { subject.attributes }
7
+
8
+ it "should find the file name" do
9
+ attrs[:file].should == "fixtures/ruby.rb"
10
+ end
11
+
12
+ it "should find the line number" do
13
+ attrs[:begin_line].should == 2
14
+ end
15
+
16
+ it "should find the commit id" do
17
+ attrs[:commit].should == "879bee2f50"
18
+ end
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: issue-beaver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -204,6 +204,8 @@ files:
204
204
  - lib/enumerable/merge.rb
205
205
  - lib/issue_beaver.rb
206
206
  - lib/issue_beaver/grammars.rb
207
+ - lib/issue_beaver/grammars/github_issue_body.rb
208
+ - lib/issue_beaver/grammars/github_issue_body.treetop
207
209
  - lib/issue_beaver/grammars/ruby_comments.rb
208
210
  - lib/issue_beaver/grammars/ruby_comments.treetop
209
211
  - lib/issue_beaver/models.rb
@@ -219,9 +221,12 @@ files:
219
221
  - lib/issue_beaver/shared/model_collection.rb
220
222
  - lib/issue_beaver/version.rb
221
223
  - lib/password/password.rb
224
+ - spec/fixtures/github_issue.txt
222
225
  - spec/fixtures/ruby.rb
223
226
  - spec/fixtures/ruby2.rb
227
+ - spec/grammars/github_issue_body_spec.rb
224
228
  - spec/grammars/ruby_comments_spec.rb
229
+ - spec/models/github_issue_spec.rb
225
230
  - spec/spec_helper.rb
226
231
  homepage: https://github.com/eckardt/issue-beaver
227
232
  licenses: []
@@ -248,7 +253,10 @@ signing_key:
248
253
  specification_version: 3
249
254
  summary: Issue Beaver creates Github Issues for TODO comments in your source code
250
255
  test_files:
256
+ - spec/fixtures/github_issue.txt
251
257
  - spec/fixtures/ruby.rb
252
258
  - spec/fixtures/ruby2.rb
259
+ - spec/grammars/github_issue_body_spec.rb
253
260
  - spec/grammars/ruby_comments_spec.rb
261
+ - spec/models/github_issue_spec.rb
254
262
  - spec/spec_helper.rb