issuesrc 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/README.md +11 -5
- data/lib/issuers/github_issuer.rb +5 -33
- data/lib/issuers/issuers.rb +102 -0
- data/lib/issuesrc.rb +24 -16
- data/lib/issuesrc/file.rb +27 -1
- data/lib/issuesrc/tag.rb +20 -4
- data/lib/issuesrc/tag_extractor.rb +19 -3
- data/lib/issuesrc/version.rb +1 -1
- data/lib/sourcers/git_sourcer.rb +4 -4
- data/lib/sourcers/github_sourcer.rb +1 -1
- data/lib/sourcers/sourcers.rb +33 -0
- data/lib/tag_finders/blunt_tag_finder.rb +28 -18
- data/lib/tag_finders/tag_finders.rb +37 -0
- data/spec/issuers/github_issuer_spec.rb +4 -0
- data/spec/issuesrc/config_spec.rb +4 -0
- data/spec/issuesrc/event_loop_spec.rb +4 -0
- data/spec/issuesrc/file_spec.rb +47 -0
- data/spec/issuesrc/tag_extractor_spec.rb +31 -0
- data/spec/issuesrc/tag_spec.rb +30 -0
- data/spec/issuesrc_spec.rb +129 -9
- data/spec/sourcers/git_sourcer_spec.rb +4 -0
- data/spec/sourcers/github_sourcer_spec.rb +4 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/tag_finders/blunt_tag_finder_spec.rb +71 -0
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YmZiMTYzMmRlMWVkZDQxZGU2NjcxYTBhY2JhM2ZhZTRiMDk2ZTkwNw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZTQzZGVhY2Q2ZDhiMjdkNmQ2ZmJkMjM0MjI0MTQyMWE2ZmUyMmE4ZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YmM1YzFmZDRiYmQ1M2ZjN2VhM2E2ZGM0NDQ0NjIwYTQ1NDY2YzM4ZWY3MDNi
|
10
|
+
NjM0OGI4MzAyMTc2MmQ2ZTg0ZTZjMjE3MWJiZGYzNzNiOTlmZWExYjM1M2I2
|
11
|
+
MzJhN2Q1NTIwZTJhYWMzNjk1NzVjNGZmNmUwMGE2ZDgxMWUxMmE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmM3OTEzODY0ZDZhMTkyMzgwYTNlYmU5ODUwZjZkZDFhMGUzMjNkYjBmNDZh
|
14
|
+
N2QzODUyNjdmNjUwMDBkNzgxZDNkNGJhZWZkNmUwNDY2MGQ2NGI3NDEyZjc4
|
15
|
+
MmYwZWFkMTA4ZjYyZWFmNzczZjBiZTdjOGQ1ODlmOTljYTE2YTQ=
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# issuesrc
|
1
|
+
# issuesrc [![Build Status](https://secure.travis-ci.org/tcard/issuesrc.svg?branch=master)](http://travis-ci.org/tcard/issuesrc) [![Gem Version](https://badge.fury.io/rb/issuesrc.svg)](http://badge.fury.io/rb/issuesrc) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/tcard/issuesrc/master)
|
2
2
|
|
3
3
|
**WARNING: very early stage of development. Test at your own risk!**
|
4
4
|
|
@@ -6,10 +6,10 @@ Synchronize in-source commented tasks with your issue tracker.
|
|
6
6
|
|
7
7
|
issuesrc scans your files looking for comments tagged with labels such as TODO, BUG, FIXME, etc., and adds them your issue tracker.
|
8
8
|
|
9
|
-
* Newly found tags **will be opened as issues**. Each
|
10
|
-
* From the source code you can change the label or the description of the issue.
|
9
|
+
* Newly found tags **will be opened as issues**. Each tag will be edited in the source code to add its issue number next to it.
|
10
|
+
* From the source code you can change the label or the description of the issue. Running the command again will synchronize changes in the repo.
|
11
11
|
* You can also **appoint an assignee** by putting her username alongside the tag (eg. `TODO(tcard)`; `TODO(tcard#12345)`).
|
12
|
-
* Synchronization is one-way; changes that you do
|
12
|
+
* Synchronization is one-way; changes that you do to a issuesrc issue from the issue tracker will be lost when you run the program again. You should add any further information as comments.
|
13
13
|
* When a tag is removed from the code, it is **closed in the issue tracker**.
|
14
14
|
|
15
15
|
## Installation
|
@@ -20,10 +20,16 @@ issuesrc scans your files looking for comments tagged with labels such as TODO,
|
|
20
20
|
|
21
21
|
issuesrc connects comments found in source code with an issue tracker. It needs to be configured to talk to both.
|
22
22
|
|
23
|
-
Configuration is done both via a .toml config file and via command line arguments. See `example.toml` and run `issuesrc -h` for details.
|
23
|
+
Configuration is done both via a .toml config file and via command line arguments. See [`example.toml`](https://github.com/tcard/issuesrc/blob/master/example.toml) and run `issuesrc -h` for details.
|
24
24
|
|
25
25
|
Currently, issuesrc only supports Git for retrieving source code, and GitHub as issue tracker.
|
26
26
|
|
27
|
+
The easiest way to get started would be something like this:
|
28
|
+
|
29
|
+
$ issuesrc --repo youruser/yourrepo --github-token xxxxxxxxxxx
|
30
|
+
|
31
|
+
That will extract tasks from the comments at github.com/youruser/yourrepo, open issues for them, add each issue's number next to its comment, and commit and push the changes. (You will thus need to have push access from the environment you run this command in.)
|
32
|
+
|
27
33
|
## Contributing
|
28
34
|
|
29
35
|
1. Fork it ( https://github.com/tcard/issuesrc/fork )
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'issuesrc/config'
|
2
|
+
require 'issuers/issuers'
|
2
3
|
require 'em-http-request'
|
3
4
|
require 'json'
|
4
5
|
require 'set'
|
@@ -7,35 +8,6 @@ module Issuesrc
|
|
7
8
|
module Issuers
|
8
9
|
DEFAULT_LABEL = 'issuesrc'
|
9
10
|
|
10
|
-
class Issues
|
11
|
-
def initialize(queue)
|
12
|
-
@queue = queue
|
13
|
-
@queue_done = false
|
14
|
-
@cache = []
|
15
|
-
end
|
16
|
-
|
17
|
-
def each
|
18
|
-
i = 0
|
19
|
-
while i < @cache.length
|
20
|
-
yield @cache[i]
|
21
|
-
i += 1
|
22
|
-
end
|
23
|
-
|
24
|
-
while !@queue_done
|
25
|
-
@queue.pop do |issue_page|
|
26
|
-
if issue_page == :end
|
27
|
-
@queue_done = true
|
28
|
-
next
|
29
|
-
end
|
30
|
-
issue_page.each do |issue|
|
31
|
-
yield issue unless issue.include? 'pull_request'
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
11
|
class GithubIssuer
|
40
12
|
def initialize(args, config, event_loop)
|
41
13
|
@user, @repo = find_repo(args, config)
|
@@ -109,25 +81,25 @@ module Issuesrc
|
|
109
81
|
end
|
110
82
|
end
|
111
83
|
|
84
|
+
private
|
112
85
|
def make_sure_issue_exists_and_then
|
113
86
|
# TODO(#21)
|
114
87
|
yield true
|
115
88
|
end
|
116
89
|
|
117
|
-
private
|
118
90
|
def find_repo(args, config)
|
119
|
-
repo_arg = Issuesrc::Config
|
91
|
+
repo_arg = Issuesrc::Config.option_from_both(
|
120
92
|
:repo, ['github', 'repo'], args, config, :require => true)
|
121
93
|
repo_arg.split('/')
|
122
94
|
end
|
123
95
|
|
124
96
|
def try_find_token(args, config)
|
125
|
-
Issuesrc::Config
|
97
|
+
Issuesrc::Config.option_from_both(
|
126
98
|
:github_token, ['github', 'auth_token'], args, config)
|
127
99
|
end
|
128
100
|
|
129
101
|
def try_find_issuesrc_label(args, config)
|
130
|
-
label = Issuesrc::Config
|
102
|
+
label = Issuesrc::Config.option_from_both(
|
131
103
|
:issuesrc_label, ['issuer', 'issuesrc_label'], args, config)
|
132
104
|
if label.nil?
|
133
105
|
label = DEFAULT_LABEL
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Issuesrc
|
2
|
+
|
3
|
+
# This module holds the different classes that can be used as issuers.
|
4
|
+
#
|
5
|
+
# An issuer handles an external issue tracker. It retrieves, creates,
|
6
|
+
# updates and deletes issues in an external service.
|
7
|
+
#
|
8
|
+
# Every issuer must implement the interface defined in the
|
9
|
+
# {Issuers::IssuerInterface} class.
|
10
|
+
module Issuers
|
11
|
+
|
12
|
+
# This class is here for documentation only. All classes in the Issuers
|
13
|
+
# module that want to be considered issuers need to implement this
|
14
|
+
# interface.
|
15
|
+
class IssuerInterface
|
16
|
+
# @param args Command line arguments, as key => value.
|
17
|
+
# @param config Arguments from the configuration file, as key => value.
|
18
|
+
# @param [Issuesrc::EventLoop] event_loop An event loop that can be used
|
19
|
+
# to make asynchronous I/O.
|
20
|
+
def initialize(args, config, event_loop); end
|
21
|
+
|
22
|
+
# Loads all the open issues marked with
|
23
|
+
# {Issuesrc::Issuers::DEFAULT_LABEL} (or the label chose in the config)
|
24
|
+
# from the issue tracker.
|
25
|
+
#
|
26
|
+
# @return [Issuesrc::Issuers::Issues]
|
27
|
+
def async_load_issues(); end
|
28
|
+
|
29
|
+
# Opens a new issue with the information hold in +tag+. Sets the just
|
30
|
+
# created issue ID as +tag+'s +issue_id+ attribute.
|
31
|
+
#
|
32
|
+
# @param [Issuesrc::Tag] tag
|
33
|
+
# @yieldparam [Issuesrc::Tag] The passed tag, with the issue ID updated.
|
34
|
+
def async_create_issue(tag, &block); end
|
35
|
+
|
36
|
+
# Updates an existing issue with the information hold in +tag+.
|
37
|
+
#
|
38
|
+
# @param issue_id The ID of the issue that should be updated.
|
39
|
+
# @param [Issuesrc::Tag] tag
|
40
|
+
# @yieldparam [Issuesrc::Tag] The passed tag.
|
41
|
+
def async_update_issue(issue_id, tag, &block); end
|
42
|
+
|
43
|
+
# Closes an issue.
|
44
|
+
#
|
45
|
+
# @param issue_id The ID of the issue that should be closed.
|
46
|
+
# @yieldparam [Issuesrc::Tag] The passed tag.
|
47
|
+
def async_close_issue(issue_id, &block); end
|
48
|
+
|
49
|
+
# Updates and closes a bunch of issues.
|
50
|
+
#
|
51
|
+
# It matches the issues that are currently open in the issue tracker with
|
52
|
+
# the tags found in the source code. Those that are only in the issue
|
53
|
+
# tracker are closed. Those that are in both are updated with the
|
54
|
+
# information from the source code.
|
55
|
+
#
|
56
|
+
# Reports what is being done to the passed block.
|
57
|
+
#
|
58
|
+
# @param prev_issues An array of issues, as they are returned from
|
59
|
+
# {Issuesrc::Issuers::IssuerInterface.async_load_issues}.
|
60
|
+
# @param tags_by_issue_id
|
61
|
+
# @yieldparam issue_id The ID of an issue.
|
62
|
+
# @yieldparam {Issuesrc::Tag} tag The tag associated with the issue.
|
63
|
+
# @yieldparam action Either +:updated+ or +:closed+.
|
64
|
+
def async_update_or_close_issues(prev_issues, tags_by_issue_id, &block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# A generator of issues.
|
69
|
+
#
|
70
|
+
# Reads issues from the queue passed to the constructor and yields them.
|
71
|
+
#
|
72
|
+
# The format of each issue is specific to a particular issuer.
|
73
|
+
class Issues
|
74
|
+
def initialize(queue)
|
75
|
+
@queue = queue
|
76
|
+
@queue_done = false
|
77
|
+
@cache = []
|
78
|
+
end
|
79
|
+
|
80
|
+
def each
|
81
|
+
i = 0
|
82
|
+
while i < @cache.length
|
83
|
+
yield @cache[i]
|
84
|
+
i += 1
|
85
|
+
end
|
86
|
+
|
87
|
+
while !@queue_done
|
88
|
+
@queue.pop do |issue_page|
|
89
|
+
if issue_page == :end
|
90
|
+
@queue_done = true
|
91
|
+
next
|
92
|
+
end
|
93
|
+
issue_page.each do |issue|
|
94
|
+
yield issue unless issue.include? 'pull_request'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/issuesrc.rb
CHANGED
@@ -40,9 +40,10 @@ module Issuesrc
|
|
40
40
|
issues = issuer.async_load_issues()
|
41
41
|
|
42
42
|
created_tags, updated_tags, closed_issues = [], [], []
|
43
|
+
tags_by_issue_id = {}
|
43
44
|
|
44
45
|
sourcer.retrieve_files().each do |file|
|
45
|
-
if Issuesrc::Config
|
46
|
+
if Issuesrc::Config.option_from_args(:verbose, args)
|
46
47
|
puts file.path
|
47
48
|
end
|
48
49
|
|
@@ -54,7 +55,8 @@ module Issuesrc
|
|
54
55
|
tags = []
|
55
56
|
tag_finder.find_tags(file) { |tag| tags << tag }
|
56
57
|
|
57
|
-
|
58
|
+
tags_in_file, new_tags = program.classify_tags(tags)
|
59
|
+
tags_by_issue_id.update(tags_in_file)
|
58
60
|
|
59
61
|
new_tags.each do |tag|
|
60
62
|
created_tags << tag
|
@@ -62,16 +64,16 @@ module Issuesrc
|
|
62
64
|
program.save_tag_in_file(tag)
|
63
65
|
end
|
64
66
|
end
|
67
|
+
end
|
65
68
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
69
|
+
issuer.async_update_or_close_issues(issues, tags_by_issue_id) do
|
70
|
+
|issue_id, tag, action|
|
71
|
+
case action
|
72
|
+
when :updated
|
73
|
+
program.save_tag_in_file(tag)
|
74
|
+
updated_tags << tag
|
75
|
+
when :closed
|
76
|
+
closed_issues << issue_id
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -87,6 +89,8 @@ module Issuesrc
|
|
87
89
|
@config = config
|
88
90
|
end
|
89
91
|
|
92
|
+
attr_accessor :files_offsets
|
93
|
+
|
90
94
|
def init_files_offsets
|
91
95
|
@files_offsets = {}
|
92
96
|
end
|
@@ -128,7 +132,7 @@ module Issuesrc
|
|
128
132
|
end
|
129
133
|
|
130
134
|
def load_component(config_key, arg_key, default, options)
|
131
|
-
type = Config
|
135
|
+
type = Config.option_from_both(arg_key, config_key, @args, @config)
|
132
136
|
if type.nil?
|
133
137
|
type = default
|
134
138
|
end
|
@@ -137,7 +141,7 @@ module Issuesrc
|
|
137
141
|
|
138
142
|
def load_component_by_type(type, options)
|
139
143
|
if !options.include?(type)
|
140
|
-
exec_fail 'Unrecognized sourcer type: #{type}'
|
144
|
+
Issuesrc::exec_fail 'Unrecognized sourcer type: #{type}'
|
141
145
|
end
|
142
146
|
|
143
147
|
options[type]
|
@@ -146,7 +150,7 @@ module Issuesrc
|
|
146
150
|
# Like `load_sourcer` but for the tag finders. It only looks at
|
147
151
|
# `[tag_finders] tag_finders = [...]` from the config file.
|
148
152
|
def load_tag_finders
|
149
|
-
tag_finders = Config
|
153
|
+
tag_finders = Config.option_from_config(
|
150
154
|
['tag_finders', 'tag_finders'], @config)
|
151
155
|
if tag_finders.nil?
|
152
156
|
tag_finders = DEFAULT_TAG_FINDERS
|
@@ -155,7 +159,7 @@ module Issuesrc
|
|
155
159
|
end
|
156
160
|
|
157
161
|
def load_tag_finders_by_types(types)
|
158
|
-
tag_extractor =
|
162
|
+
tag_extractor = load_tag_extractor()
|
159
163
|
tag_finders = []
|
160
164
|
types.each do |type|
|
161
165
|
path, cls = load_component_by_type(type, TAG_FINDERS)
|
@@ -165,6 +169,10 @@ module Issuesrc
|
|
165
169
|
tag_finders
|
166
170
|
end
|
167
171
|
|
172
|
+
def load_tag_extractor
|
173
|
+
Issuesrc::TagExtractor.new(@args, @config)
|
174
|
+
end
|
175
|
+
|
168
176
|
def make_tag_finder(cls, tag_extractor)
|
169
177
|
Issuesrc::TagFinders.const_get(cls).new(tag_extractor, @args, @config)
|
170
178
|
end
|
@@ -180,7 +188,7 @@ module Issuesrc
|
|
180
188
|
ret
|
181
189
|
end
|
182
190
|
|
183
|
-
def classify_tags(tags
|
191
|
+
def classify_tags(tags)
|
184
192
|
tags_by_issue = {}
|
185
193
|
new_tags = []
|
186
194
|
tags.each do |tag|
|
data/lib/issuesrc/file.rb
CHANGED
@@ -1,4 +1,25 @@
|
|
1
1
|
module Issuesrc
|
2
|
+
# This class is here for documentation only. All classes in the Sourcers
|
3
|
+
# module that want to be considered issuers need to implement this
|
4
|
+
# interface.
|
5
|
+
class FileInterface
|
6
|
+
# @return The type of the file as a file extension.
|
7
|
+
def type; end
|
8
|
+
|
9
|
+
# @return [IO] The body of the file.
|
10
|
+
def body; end
|
11
|
+
|
12
|
+
# Replaces part of the body of the file, and saves it.
|
13
|
+
#
|
14
|
+
# @param pos Position from the beginning of the body in which the new
|
15
|
+
# content starts.
|
16
|
+
# @param old_content_length Length of previous content that should be
|
17
|
+
# replaced.
|
18
|
+
# @param new_content A string that will be written in +pos+ at the file.
|
19
|
+
def replace_at(pos, old_content_length, new_content); end
|
20
|
+
end
|
21
|
+
|
22
|
+
# A file from the filesystem.
|
2
23
|
class FSFile
|
3
24
|
attr_reader :type
|
4
25
|
attr_reader :path
|
@@ -15,7 +36,7 @@ module Issuesrc
|
|
15
36
|
def replace_at(pos, old_content_length, new_content)
|
16
37
|
fbody = body.read
|
17
38
|
fbody = replace_in_string(fbody, pos, old_content_length, new_content)
|
18
|
-
f =
|
39
|
+
f = body_for_writing()
|
19
40
|
f.write(fbody)
|
20
41
|
f.close()
|
21
42
|
end
|
@@ -24,8 +45,13 @@ module Issuesrc
|
|
24
45
|
def replace_in_string(s, pos, deleted_length, new_content)
|
25
46
|
(s[0...pos] || '') + new_content + (s[pos + deleted_length..-1] || '')
|
26
47
|
end
|
48
|
+
|
49
|
+
def body_for_writing
|
50
|
+
File.open(@path, 'wb')
|
51
|
+
end
|
27
52
|
end
|
28
53
|
|
54
|
+
# A file from the filesystem that is indexed in a Git repository.
|
29
55
|
class GitFile < FSFile
|
30
56
|
attr_reader :repo
|
31
57
|
attr_reader :path_in_repo
|
data/lib/issuesrc/tag.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
module Issuesrc
|
2
|
+
|
3
|
+
# A tag is an annotation found in the source code of a file that holds
|
4
|
+
# information about the issue it corresponds to, the author or assignee, a
|
5
|
+
# label, a title for the isssue, and its position in the file.
|
2
6
|
class Tag
|
3
7
|
attr_reader :label
|
4
8
|
attr_accessor :issue_id
|
5
9
|
attr_reader :author
|
6
10
|
attr_reader :title
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
attr_accessor :file
|
12
|
+
attr_accessor :line
|
13
|
+
attr_accessor :begin_pos
|
14
|
+
attr_accessor :end_pos
|
11
15
|
|
12
16
|
def initialize(label, issue_id, author, title, file, line,
|
13
17
|
begin_pos, end_pos)
|
@@ -21,6 +25,7 @@ module Issuesrc
|
|
21
25
|
@end_pos = end_pos
|
22
26
|
end
|
23
27
|
|
28
|
+
# The string representation of the tag, to be included in the source file.
|
24
29
|
def to_s
|
25
30
|
ret = ""
|
26
31
|
ret << @label
|
@@ -40,6 +45,17 @@ module Issuesrc
|
|
40
45
|
ret
|
41
46
|
end
|
42
47
|
|
48
|
+
# Writes the tag in its file, using its string representation.
|
49
|
+
#
|
50
|
+
# Also updates the tag position information depending on +offset+
|
51
|
+
#
|
52
|
+
# @param offsets As the tag's position information might have been outdated
|
53
|
+
# by other tags having been written to the file, this function needs
|
54
|
+
# to know how much does it need to correct its position. +offsets+
|
55
|
+
# is a list of pairs +(position, offset)+ that tells that at
|
56
|
+
# a given position a given offset has been added or substracted.
|
57
|
+
# @return +offsets+, updated with the new offset resulting from editing the
|
58
|
+
# file.
|
43
59
|
def write_in_file(offsets)
|
44
60
|
total_offset = 0
|
45
61
|
offsets.each do |pos, offset|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'issuesrc/config'
|
2
|
+
require 'issuesrc/tag'
|
2
3
|
|
3
4
|
module Issuesrc
|
4
5
|
TAG_EXTRACTORS = [
|
@@ -22,11 +23,26 @@ module Issuesrc
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
26
|
+
# Extracts a tag from a line of source code, if there is one.
|
27
|
+
#
|
28
|
+
# It passes the line through one or more extractor functions. The first
|
29
|
+
# that returns non-nil will be returned.
|
30
|
+
#
|
31
|
+
# @param source A string.
|
32
|
+
# @return [Issuesrc::Tag] Or +nil+ if no tag is found.
|
25
33
|
def extract(source)
|
26
34
|
@extractors.each do |extr|
|
27
|
-
|
28
|
-
if !
|
29
|
-
return
|
35
|
+
tag_data = try_extractor(extr, source)
|
36
|
+
if !tag_data.nil?
|
37
|
+
return Issuesrc::Tag.new(
|
38
|
+
tag_data['type'],
|
39
|
+
tag_data['issue_id'],
|
40
|
+
tag_data['author'],
|
41
|
+
tag_data['title'],
|
42
|
+
nil, nil,
|
43
|
+
tag_data['begin_pos'],
|
44
|
+
tag_data['end_pos']
|
45
|
+
)
|
30
46
|
end
|
31
47
|
end
|
32
48
|
nil
|
data/lib/issuesrc/version.rb
CHANGED
data/lib/sourcers/git_sourcer.rb
CHANGED
@@ -77,7 +77,7 @@ module Issuesrc
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def init_exclude(config)
|
80
|
-
@exclude = Issuesrc::Config
|
80
|
+
@exclude = Issuesrc::Config.option_from_config(
|
81
81
|
['sourcer', 'exclude_files'], config)
|
82
82
|
if @exclude.nil?
|
83
83
|
@exclude = []
|
@@ -86,12 +86,12 @@ module Issuesrc
|
|
86
86
|
|
87
87
|
|
88
88
|
def try_find_repo_url(args, config)
|
89
|
-
Issuesrc::Config
|
89
|
+
Issuesrc::Config.option_from_both(:repo_url, ['git', 'repo'],
|
90
90
|
args, config)
|
91
91
|
end
|
92
92
|
|
93
93
|
def try_find_repo_path(args, config)
|
94
|
-
Issuesrc::Config
|
94
|
+
Issuesrc::Config.option_from_both(:repo_path, ['git', 'repo_path'],
|
95
95
|
args, config)
|
96
96
|
end
|
97
97
|
|
@@ -104,7 +104,7 @@ module Issuesrc
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def decide_when_done(args_key, config_key, args, config)
|
107
|
-
opt = Issuesrc::Config
|
107
|
+
opt = Issuesrc::Config.option_from_both(
|
108
108
|
args_key, ['git', config_key], args, config)
|
109
109
|
if opt.nil?
|
110
110
|
opt = is_downloaded?
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Issuesrc
|
2
|
+
|
3
|
+
# This module holds the different classes that can be used as sourcers.
|
4
|
+
#
|
5
|
+
# A sourcer handles source code. It retrieves, reads from and edits the files
|
6
|
+
# in which tags can be found.
|
7
|
+
#
|
8
|
+
# Every sourcer must implement the interface defined in the
|
9
|
+
# {Sourcers::SourcerInterface} class.
|
10
|
+
module Sourcers
|
11
|
+
|
12
|
+
# This class is here for documentation only. All classes in the Sourcers
|
13
|
+
# module that want to be considered issuers need to implement this
|
14
|
+
# interface.
|
15
|
+
class SourcerInterface
|
16
|
+
# @param args Command line arguments, as key => value.
|
17
|
+
# @param config Arguments from the configuration file, as key => value.
|
18
|
+
def initialize(args, config); end
|
19
|
+
|
20
|
+
# Retrieves all the files in which there may be tags to find.
|
21
|
+
#
|
22
|
+
# @return [Enumerator] Enumerator of {Issuesrc::FileInterface}.
|
23
|
+
def retrieve_files; end
|
24
|
+
|
25
|
+
# Optional. Called when the execution of the program finishes.
|
26
|
+
#
|
27
|
+
# @param created_tags Array of {Issuesrc::Tag}.
|
28
|
+
# @param updated_tags Array of {Issuesrc::Tag}.
|
29
|
+
# @param closed_issue_ids Array of IDs, which are Strings.
|
30
|
+
def finish(created_tags, updated_tags, closed_issue_ids); end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,6 +2,11 @@ require 'issuesrc/tag'
|
|
2
2
|
|
3
3
|
module Issuesrc
|
4
4
|
module TagFinders
|
5
|
+
|
6
|
+
# A tag finder that doesn't do any parsing; it just bluntly traverses the
|
7
|
+
# source code looking for things that look like comments.
|
8
|
+
#
|
9
|
+
# It tries to skip comments inside strings.
|
5
10
|
class BluntTagFinder
|
6
11
|
DEFAULT_COMMENT_MARKERS = [['//', "\n"], ['/*', '*/']]
|
7
12
|
DEFAULT_STRING_MARKERS = ['"', "'"]
|
@@ -38,18 +43,13 @@ module Issuesrc
|
|
38
43
|
# span several lines.
|
39
44
|
nline_offset = 0
|
40
45
|
comment.split("\n").each do |line|
|
41
|
-
|
42
|
-
if !
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
file,
|
49
|
-
nline + nline_offset,
|
50
|
-
pos + tag_data['begin_pos'],
|
51
|
-
pos + tag_data['end_pos']
|
52
|
-
)
|
46
|
+
tag = @tag_extractor.extract(line)
|
47
|
+
if !tag.nil?
|
48
|
+
tag.file = file
|
49
|
+
tag.line = nline + nline_offset
|
50
|
+
tag.begin_pos += pos
|
51
|
+
tag.end_pos += pos
|
52
|
+
yield tag
|
53
53
|
end
|
54
54
|
pos += line.length + 1
|
55
55
|
nline_offset += 1
|
@@ -60,7 +60,7 @@ module Issuesrc
|
|
60
60
|
private
|
61
61
|
def find_comments(file)
|
62
62
|
comment_markers, string_markers = decide_markers(file)
|
63
|
-
body = file
|
63
|
+
body = get_file_body(file)
|
64
64
|
comment_finder = CommentFinder.new(body, comment_markers, string_markers)
|
65
65
|
pos = 0
|
66
66
|
|
@@ -76,6 +76,10 @@ module Issuesrc
|
|
76
76
|
]
|
77
77
|
end
|
78
78
|
|
79
|
+
def get_file_body(file)
|
80
|
+
file.body.read.force_encoding('BINARY') # TODO(#26): Use less memory here.
|
81
|
+
end
|
82
|
+
|
79
83
|
class CommentFinder
|
80
84
|
def initialize(body, comment_markers, string_markers)
|
81
85
|
@body = body
|
@@ -129,15 +133,15 @@ module Issuesrc
|
|
129
133
|
end
|
130
134
|
|
131
135
|
def read_delimited(markers)
|
132
|
-
consumed = state_boundary(markers, :begin)
|
136
|
+
marker_i, consumed = state_boundary(markers, :begin)
|
133
137
|
lex = @body[0...consumed]
|
134
138
|
consume_body(consumed)
|
135
139
|
|
136
|
-
consumed_end = state_boundary(markers, :end)
|
140
|
+
_, consumed_end = state_boundary(markers, :end, marker_i)
|
137
141
|
while !@body.empty? && consumed_end.nil?
|
138
142
|
lex << @body[0]
|
139
143
|
consumed += consume_body(1)
|
140
|
-
consumed_end = state_boundary(markers, :end)
|
144
|
+
_, consumed_end = state_boundary(markers, :end, marker_i)
|
141
145
|
end
|
142
146
|
|
143
147
|
if !consumed_end.nil?
|
@@ -158,18 +162,24 @@ module Issuesrc
|
|
158
162
|
end
|
159
163
|
end
|
160
164
|
|
161
|
-
def state_boundary(markers, begin_or_end)
|
165
|
+
def state_boundary(markers, begin_or_end, marker_i=nil)
|
162
166
|
if @body.nil?
|
163
167
|
nil
|
164
168
|
end
|
165
169
|
|
170
|
+
i = 0
|
166
171
|
markers.each do |marker|
|
172
|
+
if !marker_i.nil? && i != marker_i
|
173
|
+
i += 1
|
174
|
+
next
|
175
|
+
end
|
167
176
|
if marker.instance_of? Array
|
168
177
|
marker = marker[begin_or_end == :begin ? 0 : 1]
|
169
178
|
end
|
170
179
|
if @body.start_with? marker
|
171
|
-
return marker.length
|
180
|
+
return [i, marker.length]
|
172
181
|
end
|
182
|
+
i += 1
|
173
183
|
end
|
174
184
|
nil
|
175
185
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Issuesrc
|
2
|
+
|
3
|
+
# This module holds the different classes that can be used as tag finders.
|
4
|
+
#
|
5
|
+
# An issuer handles an external issue tracker. It retrieves, creates,
|
6
|
+
# updates and deletes issues in an external service.
|
7
|
+
#
|
8
|
+
# Every tag finder must implement the interface defined in the
|
9
|
+
# {TagFinders::TagFinderInterface} class.
|
10
|
+
module TagFinders
|
11
|
+
|
12
|
+
# This class is here for documentation only. All classes in the TagFinders
|
13
|
+
# module that want to be considered tag finders need to implement this
|
14
|
+
# interface.
|
15
|
+
class TagFinderInterface
|
16
|
+
# @param tag_extractor [Issuesrc::TagExtractor]
|
17
|
+
# @param args Command line arguments, as key => value.
|
18
|
+
# @param config Arguments from the configuration file, as key => value.
|
19
|
+
def initialize(tag_extractor, args, config); end
|
20
|
+
|
21
|
+
# Tells if the tag finder can process the given file or not.
|
22
|
+
#
|
23
|
+
# @param [Issuesrc::FileInterface] file
|
24
|
+
# @return [Bool]
|
25
|
+
def accepts?(file); end
|
26
|
+
|
27
|
+
# Finds all the tags in a file.
|
28
|
+
#
|
29
|
+
# Reads in the file's body looking for tags, using the instance's
|
30
|
+
# +tag_extractor+.
|
31
|
+
#
|
32
|
+
# @param [Issuesrc::FileInterface] file
|
33
|
+
# @yieldparam tag [Issuesrc::Tag]
|
34
|
+
def find_tags(file); end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/issuesrc/file_spec.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/issuesrc/file'
|
3
|
+
|
4
|
+
describe Issuesrc::FSFile do
|
5
|
+
before :all do
|
6
|
+
@obj = Issuesrc::FSFile.new('/made/up/path.txt')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'extracts the type from the extension' do
|
10
|
+
expect(@obj.type).to be == 'txt'
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#replace_at' do
|
14
|
+
it 'replaces in the middle of the body' do
|
15
|
+
body = double()
|
16
|
+
allow(body).to receive(:write).with('Lorem ipsum REPLACED sit amet')
|
17
|
+
allow(@obj).to receive(:body).and_return(
|
18
|
+
double(:read => 'Lorem ipsum dolor sit amet'))
|
19
|
+
allow(@obj).to receive(:body_for_writing).and_return(body)
|
20
|
+
expect(body).to receive(:close)
|
21
|
+
|
22
|
+
@obj.replace_at(12, 5, 'REPLACED')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'replaces at the beginning of the body' do
|
26
|
+
body = double()
|
27
|
+
allow(body).to receive(:write).with('REPLACED ipsum dolor sit amet')
|
28
|
+
allow(@obj).to receive(:body).and_return(
|
29
|
+
double(:read => 'Lorem ipsum dolor sit amet'))
|
30
|
+
allow(@obj).to receive(:body_for_writing).and_return(body)
|
31
|
+
expect(body).to receive(:close)
|
32
|
+
|
33
|
+
@obj.replace_at(0, 5, 'REPLACED')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'replaces at the end of the body' do
|
37
|
+
body = double()
|
38
|
+
allow(body).to receive(:write).with('Lorem ipsum dolor sit amREPLACED')
|
39
|
+
allow(@obj).to receive(:body).and_return(
|
40
|
+
double(:read => 'Lorem ipsum dolor sit amet'))
|
41
|
+
allow(@obj).to receive(:body_for_writing).and_return(body)
|
42
|
+
expect(body).to receive(:close)
|
43
|
+
|
44
|
+
@obj.replace_at(24, 2, 'REPLACED')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/issuesrc/tag_extractor'
|
3
|
+
|
4
|
+
describe Issuesrc::TagExtractor do
|
5
|
+
before :each do
|
6
|
+
@tag_extractor = Issuesrc::TagExtractor.new({}, {})
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'parses several representations OK' do
|
10
|
+
cases = {
|
11
|
+
'TODO() Test ' => 'TODO: Test',
|
12
|
+
'FIXME:Test' => 'FIXME: Test',
|
13
|
+
'FIXME' => 'FIXME',
|
14
|
+
'TODO ( # 1234 )Test' => 'TODO(#1234): Test',
|
15
|
+
'BUG ( tcard)' => 'BUG(tcard)',
|
16
|
+
'BUG ( tcard # 1234 ):Test ' => 'BUG(tcard#1234): Test',
|
17
|
+
}
|
18
|
+
|
19
|
+
cases.each do |input, expected|
|
20
|
+
got = @tag_extractor.extract(input)
|
21
|
+
expect(got.to_s).to be == expected
|
22
|
+
idempotent = @tag_extractor.extract(expected)
|
23
|
+
expect(idempotent.to_s).to be == expected
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns nil when no tag is found' do
|
28
|
+
got = @tag_extractor.extract('no tag here, sorry')
|
29
|
+
expect(got).to be == nil
|
30
|
+
end
|
31
|
+
end
|
data/spec/issuesrc/tag_spec.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/issuesrc/tag'
|
3
|
+
|
4
|
+
describe Issuesrc::Tag do
|
5
|
+
before :all do
|
6
|
+
@file = instance_double('Issuesrc::FSFile')
|
7
|
+
@obj = Issuesrc::Tag.new(
|
8
|
+
'LABEL',
|
9
|
+
'abc123',
|
10
|
+
'theauthor',
|
11
|
+
'The title',
|
12
|
+
@file,
|
13
|
+
111,
|
14
|
+
222,
|
15
|
+
300)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#write_in_file' do
|
19
|
+
it 'writes itself in its file with offsets' do
|
20
|
+
offset = [[50, 10], [60, 15], [999, 999]]
|
21
|
+
|
22
|
+
allow(@file).to receive(:replace_at)
|
23
|
+
.with(222 + 10 + 15, 300 - 222, @obj.to_s)
|
24
|
+
old_length = 300 - 222
|
25
|
+
new_length = @obj.to_s.length
|
26
|
+
expect(@obj.write_in_file(offset.clone))
|
27
|
+
.to be == (offset + [[222, new_length - old_length]])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/issuesrc_spec.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
|
+
require_relative 'spec_helper'
|
1
2
|
require_relative '../lib/issuesrc'
|
2
3
|
|
3
4
|
describe Issuesrc do
|
4
5
|
before :each do
|
6
|
+
@args = {
|
7
|
+
:arg => 'fake arg'
|
8
|
+
}
|
9
|
+
@config = {
|
10
|
+
'config' => ['fake', 'config']
|
11
|
+
}
|
5
12
|
@obj = Class.new do
|
6
13
|
include Issuesrc
|
7
14
|
end.new
|
8
|
-
@obj.send(:set_config,
|
9
|
-
:arg => 'fake arg'
|
10
|
-
}, {
|
11
|
-
'config' => ['fake', 'config']
|
12
|
-
})
|
15
|
+
@obj.send(:set_config, @args, @config)
|
13
16
|
end
|
14
17
|
|
15
18
|
describe '#load_sourcer' do
|
@@ -70,12 +73,129 @@ describe Issuesrc do
|
|
70
73
|
end
|
71
74
|
|
72
75
|
it 'fails when loading an unknown component' do
|
73
|
-
allow(@obj).to receive(:exec_fail).and_raise('failed')
|
74
|
-
|
75
76
|
expect { @obj.send(:load_component, ['fake'], :fake, 'def', {}) }
|
76
|
-
.to raise_error(
|
77
|
+
.to raise_error(Issuesrc::IssuesrcError)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#load_tag_finders' do
|
82
|
+
def test_load_tag_finders(from_config, mock_load_comp=true)
|
83
|
+
allow(Issuesrc::Config).to receive(:option_from_config)
|
84
|
+
.with(['tag_finders', 'tag_finders'], @config)
|
85
|
+
.and_return(from_config)
|
86
|
+
allow(@obj).to receive(:load_tag_extractor)
|
87
|
+
.and_return('fake tag extractor')
|
88
|
+
|
89
|
+
types = from_config ? from_config : Issuesrc::DEFAULT_TAG_FINDERS
|
90
|
+
if types
|
91
|
+
i = 1
|
92
|
+
types.each do |v|
|
93
|
+
if mock_load_comp
|
94
|
+
path, cls = ["fake path #{i}", "fake cls #{i}"]
|
95
|
+
allow(@obj).to receive(:load_component_by_type)
|
96
|
+
.with(v, Issuesrc::TAG_FINDERS)
|
97
|
+
.and_return([path, cls])
|
98
|
+
else
|
99
|
+
path, cls = @obj.send(
|
100
|
+
:load_component_by_type, v, Issuesrc::TAG_FINDERS)
|
101
|
+
end
|
102
|
+
allow(@obj).to receive(:do_require)
|
103
|
+
.with(path)
|
104
|
+
allow(@obj).to receive(:make_tag_finder)
|
105
|
+
.with(cls, 'fake tag extractor')
|
106
|
+
.and_return("finder #{v}")
|
107
|
+
i += 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@obj.send(:load_tag_finders)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'loads the required tag finders' do
|
115
|
+
got = test_load_tag_finders(['fake', 'types'])
|
116
|
+
expect(got).to be == ['finder fake', 'finder types']
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'loads the default tag finders when no option is given' do
|
120
|
+
got = test_load_tag_finders(nil)
|
121
|
+
expect(got).to be == (Issuesrc::DEFAULT_TAG_FINDERS.collect do |v|
|
122
|
+
"finder #{v}"
|
123
|
+
end)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'fails when loading an unknown component' do
|
127
|
+
expect { test_load_tag_finders(['madeup'], false) }
|
128
|
+
.to raise_error(Issuesrc::IssuesrcError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#select_tag_finder_for' do
|
133
|
+
before :each do
|
134
|
+
@file = instance_double('Issuesrc::FSFile', :type => 'ty')
|
135
|
+
@tag_finders = [
|
136
|
+
instance_double('Issuesrc::TagFinder'),
|
137
|
+
instance_double('Issuesrc::TagFinder')
|
138
|
+
]
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'returns the finder which accepts this file type' do
|
142
|
+
allow(@tag_finders[0]).to receive(:accepts?)
|
143
|
+
.with(@file).and_return(false)
|
144
|
+
allow(@tag_finders[1]).to receive(:accepts?)
|
145
|
+
.with(@file).and_return(true)
|
146
|
+
|
147
|
+
got = @obj.select_tag_finder_for(@file, @tag_finders)
|
148
|
+
|
149
|
+
expect(got).to be == @tag_finders[1]
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'returns nil when no finder is found' do
|
153
|
+
allow(@tag_finders[0]).to receive(:accepts?)
|
154
|
+
.with(@file).and_return(false)
|
155
|
+
allow(@tag_finders[1]).to receive(:accepts?)
|
156
|
+
.with(@file).and_return(false)
|
157
|
+
|
158
|
+
got = @obj.select_tag_finder_for(@file, @tag_finders)
|
159
|
+
|
160
|
+
expect(got).to be == nil
|
77
161
|
end
|
78
162
|
end
|
79
163
|
|
80
|
-
|
164
|
+
describe '#classify_tags' do
|
165
|
+
before :each do
|
166
|
+
@tags = [
|
167
|
+
instance_double('Issuesrc::Tag', :issue_id => '123'),
|
168
|
+
instance_double('Issuesrc::Tag', :issue_id => nil),
|
169
|
+
instance_double('Issuesrc::Tag', :issue_id => '456'),
|
170
|
+
instance_double('Issuesrc::Tag', :issue_id => nil),
|
171
|
+
]
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'classifies tags between those with IDs and new ones' do
|
175
|
+
tags_by_issue, new_tags = @obj.send(:classify_tags, @tags)
|
176
|
+
expect(tags_by_issue).to be == {
|
177
|
+
'123' => @tags[0],
|
178
|
+
'456' => @tags[2],
|
179
|
+
}
|
180
|
+
expect(new_tags).to be == [@tags[1], @tags[3]]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#save_tag_in_file' do
|
185
|
+
before :each do
|
186
|
+
@obj.init_files_offsets()
|
187
|
+
@tag = instance_double('Issuesrc::Tag',
|
188
|
+
:issue_id => '123',
|
189
|
+
:file => instance_double('Issuesrc::FSFile', :path => 'fake path'))
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'saves a tag in its file and saves the given offset' do
|
193
|
+
allow(@tag).to receive(:write_in_file).with([]).and_return('offsets')
|
194
|
+
@obj.send(:save_tag_in_file, @tag)
|
195
|
+
|
196
|
+
expect(@obj.files_offsets).to be == {
|
197
|
+
'fake path' => 'offsets'
|
198
|
+
}
|
199
|
+
end
|
200
|
+
end
|
81
201
|
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/tag_finders/blunt_tag_finder'
|
3
|
+
|
4
|
+
describe Issuesrc::TagFinders::BluntTagFinder do
|
5
|
+
before :all do
|
6
|
+
tag_extractor = Issuesrc::TagExtractor.new({}, {})
|
7
|
+
@obj = Issuesrc::TagFinders::BluntTagFinder.new(tag_extractor, {}, {})
|
8
|
+
|
9
|
+
# BUG: Comments in @base_file are being parsed! We need a way of overriding this.
|
10
|
+
@base_file = <<EOD
|
11
|
+
This is a file with several kind of comments supposed to be caught.
|
12
|
+
|
13
|
+
/*This is a normal block of BUG code*/
|
14
|
+
|
15
|
+
<!--
|
16
|
+
TODO: Multiline should work too.
|
17
|
+
abc BUG: Another one.
|
18
|
+
-->
|
19
|
+
|
20
|
+
Haskell has {- TODO comments like-} this.
|
21
|
+
|
22
|
+
|
23
|
+
// This should match FIXME(tcard): until the end of the line.
|
24
|
+
|
25
|
+
Also # TODO(#38): like this.
|
26
|
+
"Don't" be -- crazy TODO and # TODO(#39): mix them!
|
27
|
+
|
28
|
+
EOD
|
29
|
+
|
30
|
+
@cases = {
|
31
|
+
'madeup' => [
|
32
|
+
# BUG(#40): Shouldn't match end of block comments.
|
33
|
+
[97, 107, 'BUG: code*/'],
|
34
|
+
[240, 280, 'FIXME(tcard): until the end of the line.'],
|
35
|
+
],
|
36
|
+
'html' => [
|
37
|
+
[114, 146, 'TODO: Multiline should work too.'],
|
38
|
+
[152, 169, 'BUG: Another one.'],
|
39
|
+
],
|
40
|
+
'sql' => [
|
41
|
+
[326, 351, 'TODO: and # TODO mix them!'],
|
42
|
+
],
|
43
|
+
'sh' => [
|
44
|
+
[289, 305, 'TODO: like this.'],
|
45
|
+
[337, 351, 'TODO: mix them!'],
|
46
|
+
],
|
47
|
+
'hs' => [
|
48
|
+
[190, 210, 'TODO: comments like-}'],
|
49
|
+
[326, 351, 'TODO: and # TODO mix them!'],
|
50
|
+
],
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'finds all the expected tags for each language' do
|
55
|
+
@cases.each do |type, expected|
|
56
|
+
file = instance_double('Issuesrc::FSFile', :type => type)
|
57
|
+
expect(@obj.accepts?(file)).to be == true
|
58
|
+
|
59
|
+
allow(@obj).to receive(:get_file_body).with(file)
|
60
|
+
.and_return(@base_file)
|
61
|
+
|
62
|
+
tags = []
|
63
|
+
@obj.find_tags(file) do |tag|
|
64
|
+
expect(tag.file).to be == file
|
65
|
+
tags << [tag.begin_pos, tag.end_pos, tag.to_s]
|
66
|
+
end
|
67
|
+
|
68
|
+
expect(tags).to be == expected
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: issuesrc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Toni Cárdenas
|
@@ -89,6 +89,7 @@ extensions: []
|
|
89
89
|
extra_rdoc_files: []
|
90
90
|
files:
|
91
91
|
- .gitignore
|
92
|
+
- .travis.yml
|
92
93
|
- Gemfile
|
93
94
|
- LICENSE.txt
|
94
95
|
- README.md
|
@@ -97,6 +98,7 @@ files:
|
|
97
98
|
- example.toml
|
98
99
|
- issuesrc.gemspec
|
99
100
|
- lib/issuers/github_issuer.rb
|
101
|
+
- lib/issuers/issuers.rb
|
100
102
|
- lib/issuesrc.rb
|
101
103
|
- lib/issuesrc/config.rb
|
102
104
|
- lib/issuesrc/event_loop.rb
|
@@ -106,7 +108,9 @@ files:
|
|
106
108
|
- lib/issuesrc/version.rb
|
107
109
|
- lib/sourcers/git_sourcer.rb
|
108
110
|
- lib/sourcers/github_sourcer.rb
|
111
|
+
- lib/sourcers/sourcers.rb
|
109
112
|
- lib/tag_finders/blunt_tag_finder.rb
|
113
|
+
- lib/tag_finders/tag_finders.rb
|
110
114
|
- spec/issuers/github_issuer_spec.rb
|
111
115
|
- spec/issuesrc/config_spec.rb
|
112
116
|
- spec/issuesrc/event_loop_spec.rb
|
@@ -116,6 +120,7 @@ files:
|
|
116
120
|
- spec/issuesrc_spec.rb
|
117
121
|
- spec/sourcers/git_sourcer_spec.rb
|
118
122
|
- spec/sourcers/github_sourcer_spec.rb
|
123
|
+
- spec/spec_helper.rb
|
119
124
|
- spec/tag_finders/blunt_tag_finder_spec.rb
|
120
125
|
homepage: https://github.com/tcard/issuesrc
|
121
126
|
licenses:
|
@@ -151,5 +156,6 @@ test_files:
|
|
151
156
|
- spec/issuesrc_spec.rb
|
152
157
|
- spec/sourcers/git_sourcer_spec.rb
|
153
158
|
- spec/sourcers/github_sourcer_spec.rb
|
159
|
+
- spec/spec_helper.rb
|
154
160
|
- spec/tag_finders/blunt_tag_finder_spec.rb
|
155
161
|
has_rdoc:
|