issue-beaver 0.1.2 → 0.2.0

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