octaccord 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +62 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.org +95 -0
- data/Rakefile +7 -0
- data/bin/octaccord +311 -0
- data/examples/Itr0000-template.md.erb +93 -0
- data/examples/octaccord-completion.zsh +11 -0
- data/lib/extensions.rb +2 -0
- data/lib/extensions/octokit.rb +16 -0
- data/lib/octaccord.rb +10 -0
- data/lib/octaccord/command.rb +16 -0
- data/lib/octaccord/command/add_collaborator.rb +34 -0
- data/lib/octaccord/command/comments.rb +50 -0
- data/lib/octaccord/command/completions.rb +56 -0
- data/lib/octaccord/command/create_iteration.rb +21 -0
- data/lib/octaccord/command/get_team_members.rb +18 -0
- data/lib/octaccord/command/info.rb +18 -0
- data/lib/octaccord/command/label.rb +16 -0
- data/lib/octaccord/command/scan.rb +122 -0
- data/lib/octaccord/command/show.rb +28 -0
- data/lib/octaccord/command/update_issues.rb +28 -0
- data/lib/octaccord/errors.rb +2 -0
- data/lib/octaccord/formatter.rb +105 -0
- data/lib/octaccord/formatter/comment.rb +58 -0
- data/lib/octaccord/formatter/debug.rb +17 -0
- data/lib/octaccord/formatter/issue.rb +134 -0
- data/lib/octaccord/formatter/list.rb +36 -0
- data/lib/octaccord/formatter/number.rb +14 -0
- data/lib/octaccord/formatter/pbl.rb +23 -0
- data/lib/octaccord/formatter/table.rb +17 -0
- data/lib/octaccord/formatter/text.rb +14 -0
- data/lib/octaccord/formatter/user.rb +42 -0
- data/lib/octaccord/iteration.rb +61 -0
- data/lib/octaccord/version.rb +3 -0
- data/octaccord.gemspec +24 -0
- data/spec/octaccord_spec.rb +11 -0
- data/spec/spec_helper.rb +2 -0
- metadata +130 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Command
|
3
|
+
class Show
|
4
|
+
|
5
|
+
def initialize(client, repos, issue_number, **options)
|
6
|
+
begin
|
7
|
+
comments = client.issues_comments(repos, :since => Date.today - 14)
|
8
|
+
comments.each do |comment|
|
9
|
+
issue = comment.rels[:issue].get.data
|
10
|
+
formatter = Octaccord::Formatter.build(formatter: :text)
|
11
|
+
formatter << issue
|
12
|
+
print "* "
|
13
|
+
print formatter.to_s
|
14
|
+
print " * "
|
15
|
+
lines = Formatter::Comment.new(comment).adjust_indent.split(/\r?\n/)
|
16
|
+
print lines.first.sub(/^#+\s*/, '')
|
17
|
+
print "..." if lines.length > 1
|
18
|
+
print "[>>](#{comment.html_url})"
|
19
|
+
print "\n"
|
20
|
+
end
|
21
|
+
# issue = client.issue(repos, issue_number)
|
22
|
+
rescue Octokit::ClientError => e
|
23
|
+
STDERR.puts "Error: ##{issue} -- #{e.message.split(' // ').first}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # class Show
|
27
|
+
end # module Command
|
28
|
+
end # module Octaccord
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Command
|
3
|
+
class UpdateIssues
|
4
|
+
Encoding.default_external = "UTF-8"
|
5
|
+
|
6
|
+
def initialize(client, repos, issues, **options)
|
7
|
+
issues.each do |issue|
|
8
|
+
number = issue.to_i
|
9
|
+
begin
|
10
|
+
if label = options[:add_label]
|
11
|
+
response = client.add_labels_to_an_issue(repos, number, [label])
|
12
|
+
pp response if options[:debug]
|
13
|
+
puts "Add label #{label} to ##{number}."
|
14
|
+
end
|
15
|
+
|
16
|
+
if label = options[:remove_label]
|
17
|
+
response = client.remove_label(repos, number, label)
|
18
|
+
pp response if options[:debug]
|
19
|
+
puts "Remove label #{label} from ##{number}."
|
20
|
+
end
|
21
|
+
rescue Octokit::ClientError => e
|
22
|
+
STDERR.puts "Error: ##{issue} -- #{e.message.split(' // ').first}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # class UpdateIssues
|
27
|
+
end # module Command
|
28
|
+
end # module Octaccord
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "tsort"
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
include TSort
|
5
|
+
alias tsort_each_node each_key
|
6
|
+
def tsort_each_child(node, &block)
|
7
|
+
fetch(node).each(&block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Octaccord
|
12
|
+
module Formatter
|
13
|
+
|
14
|
+
class FormatterNameError < StandardError; end
|
15
|
+
|
16
|
+
def self.build(formatter: :text)
|
17
|
+
formatters = {
|
18
|
+
:debug => Debug,
|
19
|
+
:list => List,
|
20
|
+
:number => Number,
|
21
|
+
:pbl => Pbl,
|
22
|
+
:table => Table,
|
23
|
+
:text => Text
|
24
|
+
}
|
25
|
+
formatter = formatter.to_sym
|
26
|
+
return formatters[formatter].new if formatters[formatter]
|
27
|
+
raise FormatterNameError.new("Unknown format: #{formatter}")
|
28
|
+
end
|
29
|
+
|
30
|
+
class Base
|
31
|
+
def initialize
|
32
|
+
@issues = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(issue)
|
36
|
+
@issues << Issue.new(issue)
|
37
|
+
end
|
38
|
+
|
39
|
+
def tsort
|
40
|
+
graph = {}
|
41
|
+
@issues.each do |issue|
|
42
|
+
graph[issue.number.to_i] ||= []
|
43
|
+
issue.references.each do |parent|
|
44
|
+
graph[parent] ||= []
|
45
|
+
graph[parent] << issue.number.to_i
|
46
|
+
end
|
47
|
+
end
|
48
|
+
graph.tsort
|
49
|
+
end
|
50
|
+
|
51
|
+
def order(numbers)
|
52
|
+
numbers = scan_issue_numbers_from_string(numbers) if numbers.is_a?(String)
|
53
|
+
|
54
|
+
ordered = numbers.map do |n|
|
55
|
+
@issues.find{|issue| issue.number.to_i == n}
|
56
|
+
end.compact
|
57
|
+
|
58
|
+
@issues = ordered + (@issues - ordered)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
format_frame_header +
|
63
|
+
format_header +
|
64
|
+
format_body +
|
65
|
+
format_footer +
|
66
|
+
format_frame_footer
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def scan_issue_numbers_from_string(string, one_for_each_line = true)
|
72
|
+
regexp = one_for_each_line ? /#(\d+).*\n?/ : /#(\d+)/
|
73
|
+
string.scan(regexp).map{|i| i.first.to_i}
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_body
|
77
|
+
@issues.map{|issue| format_item(issue)}.compact.join("\n") + "\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_frame_header
|
81
|
+
""
|
82
|
+
end
|
83
|
+
|
84
|
+
def format_frame_footer
|
85
|
+
""
|
86
|
+
end
|
87
|
+
|
88
|
+
def format_header ;""; end
|
89
|
+
def format_footer ;""; end
|
90
|
+
end # class Base
|
91
|
+
|
92
|
+
dir = File.dirname(__FILE__) + "/formatter"
|
93
|
+
autoload :Comment, "#{dir}/comment.rb"
|
94
|
+
autoload :Debug, "#{dir}/debug.rb"
|
95
|
+
autoload :List, "#{dir}/list.rb"
|
96
|
+
autoload :Number, "#{dir}/number.rb"
|
97
|
+
autoload :Pbl, "#{dir}/pbl.rb"
|
98
|
+
autoload :Table, "#{dir}/table.rb"
|
99
|
+
autoload :User, "#{dir}/user.rb"
|
100
|
+
autoload :Text, "#{dir}/text.rb"
|
101
|
+
|
102
|
+
autoload :Issue, "#{dir}/issue.rb"
|
103
|
+
|
104
|
+
end # module Formatter
|
105
|
+
end # module Octaccord
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Formatter
|
3
|
+
class Comment
|
4
|
+
def initialize(comment)
|
5
|
+
@resource = comment
|
6
|
+
end
|
7
|
+
|
8
|
+
def user
|
9
|
+
User.new(@resource.user)
|
10
|
+
end
|
11
|
+
|
12
|
+
def url
|
13
|
+
@resource.url
|
14
|
+
end
|
15
|
+
|
16
|
+
def created_at
|
17
|
+
@resource.created_at.localtime
|
18
|
+
end
|
19
|
+
|
20
|
+
def updated_at
|
21
|
+
@resource.updated_at.localtime
|
22
|
+
end
|
23
|
+
|
24
|
+
def html_url
|
25
|
+
@resource.html_url
|
26
|
+
end
|
27
|
+
|
28
|
+
def issue_url
|
29
|
+
@resource.issue_url
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
@resource.id
|
34
|
+
end
|
35
|
+
|
36
|
+
def body
|
37
|
+
@resource.body
|
38
|
+
end
|
39
|
+
|
40
|
+
def references
|
41
|
+
@resource.body.scan(/#(\d+)/).map{|d| d.first.to_i}
|
42
|
+
end
|
43
|
+
|
44
|
+
def summary
|
45
|
+
lines = @resource.body.split(/\r?\n/).map{|line|
|
46
|
+
line.sub(/^[*#]+\s+/, "")
|
47
|
+
}.join(" ")[0,200]
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :title, :summary
|
51
|
+
|
52
|
+
def link(text: "...")
|
53
|
+
"[#{text}](#{@resource.html_url})"
|
54
|
+
end
|
55
|
+
|
56
|
+
end # class Comment
|
57
|
+
end # module Formatter
|
58
|
+
end # module Octaccord
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Formatter
|
3
|
+
class Debug < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def format_frame_header ; ""; end
|
7
|
+
def format_frame_footer ; ""; end
|
8
|
+
|
9
|
+
def format_item(issue)
|
10
|
+
"##{issue.number} #{issue.title} labels:#{issue.labels} ms:#{issue.milestone}" +
|
11
|
+
" created_at:#{issue.created_at}" +
|
12
|
+
" updated_at:#{issue.updated_at}" +
|
13
|
+
" refs:#{issue.references.join(',')}"
|
14
|
+
end
|
15
|
+
end # class Debug
|
16
|
+
end # module Formatter
|
17
|
+
end # module Octaccord
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Formatter
|
3
|
+
class Issue
|
4
|
+
def initialize(issue)
|
5
|
+
@resource = issue
|
6
|
+
end
|
7
|
+
|
8
|
+
def status
|
9
|
+
return ":parking:" if labels =~ /PBL/
|
10
|
+
# return ":white_check_mark:" if @resource.state == "closed"
|
11
|
+
return ":arrow_upper_left:" if @resource.pull_request
|
12
|
+
return avatar if @resource.assignee
|
13
|
+
days = ((Time.now - @resource.updated_at) / (24*3600)).to_i
|
14
|
+
return ":ghost:" if days > 60
|
15
|
+
return ":grey_question:"
|
16
|
+
end
|
17
|
+
|
18
|
+
def user
|
19
|
+
User.new(@resource.user) if @resource.user
|
20
|
+
end
|
21
|
+
|
22
|
+
def assignee
|
23
|
+
User.new(@resource.assignee) if @resource.assignee
|
24
|
+
end
|
25
|
+
|
26
|
+
def avatar
|
27
|
+
if @resource.assignee
|
28
|
+
return self.assignee.avatar
|
29
|
+
end
|
30
|
+
return ":grey_question:"
|
31
|
+
end
|
32
|
+
|
33
|
+
def pr
|
34
|
+
if @resource.pull_request then ":arrow_upper_left:" else nil end
|
35
|
+
end
|
36
|
+
|
37
|
+
def references
|
38
|
+
@resource.body.scan(/#(\d+)/).map{|d| d.first.to_i}
|
39
|
+
end
|
40
|
+
|
41
|
+
def summary
|
42
|
+
"##{@resource.number} #{@resource.title}" + (labels != "" ? " (#{labels})" : "")
|
43
|
+
end
|
44
|
+
|
45
|
+
def link
|
46
|
+
type = if @resource.pull_request then "pull" else "issues" end
|
47
|
+
"[##{@resource.number}](../#{type}/#{@resource.number} \"#{@resource.title}\")"
|
48
|
+
end
|
49
|
+
|
50
|
+
def number
|
51
|
+
"#{@resource.number}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def plain_link
|
55
|
+
type = if @resource.pull_request then "pull" else "issues" end
|
56
|
+
"[##{@resource.number}](../#{type}/#{@resource.number})"
|
57
|
+
end
|
58
|
+
|
59
|
+
def comments
|
60
|
+
# https://github.com/octokit/octokit.rb#uri-templates
|
61
|
+
comments = []
|
62
|
+
|
63
|
+
STDERR.puts "* issue comments: #{@resource.rels[:comments].href}"
|
64
|
+
|
65
|
+
@resource.rels[:comments].get.data.each do |c|
|
66
|
+
comments << adjust_indent(c.body)
|
67
|
+
end
|
68
|
+
return comments.join
|
69
|
+
end
|
70
|
+
|
71
|
+
def title
|
72
|
+
if @resource.state == "closed" then "~~#{@resource.title}~~" else @resource.title end
|
73
|
+
end
|
74
|
+
|
75
|
+
def labels
|
76
|
+
@resource.labels.map{|label| label.name}.join(',')
|
77
|
+
end
|
78
|
+
|
79
|
+
def milestone
|
80
|
+
if @resource.milestone then "#{@resource.milestone.title}" else nil end
|
81
|
+
end
|
82
|
+
|
83
|
+
def created_at
|
84
|
+
@resource.created_at.localtime
|
85
|
+
end
|
86
|
+
|
87
|
+
def updated_at
|
88
|
+
@resource.updated_at.localtime
|
89
|
+
end
|
90
|
+
|
91
|
+
def story
|
92
|
+
extract_section(@resource.body, "Story")
|
93
|
+
end
|
94
|
+
|
95
|
+
def demo
|
96
|
+
extract_section(@resource.body, "Demo")
|
97
|
+
end
|
98
|
+
|
99
|
+
def cost
|
100
|
+
extract_section(@resource.body, "Cost")
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def extract_section(lines, heading)
|
106
|
+
in_section, body = false, ""
|
107
|
+
regexp_in = /^(\#+)\s+#{heading}/
|
108
|
+
regexp_out = nil
|
109
|
+
|
110
|
+
lines.split(/\r?\n/).each do |line|
|
111
|
+
if regexp_in.match(line)
|
112
|
+
level, in_section = $1.length, true
|
113
|
+
regexp_out = /^\#{#{level}}\s+/
|
114
|
+
next
|
115
|
+
end
|
116
|
+
|
117
|
+
in_section = false if in_section and regexp_out.match(line)
|
118
|
+
body << line + "\n" if in_section
|
119
|
+
end
|
120
|
+
return body.to_s.gsub(/\r?\n/, ' ')
|
121
|
+
end
|
122
|
+
|
123
|
+
def adjust_indent(body)
|
124
|
+
new_body = ""
|
125
|
+
body.split(/\r?\n/).each do |line|
|
126
|
+
line = "#####" + line if line =~ /^\#+/
|
127
|
+
new_body << line + "\n"
|
128
|
+
end
|
129
|
+
return new_body
|
130
|
+
end
|
131
|
+
|
132
|
+
end # class Issue
|
133
|
+
end # module Formatter
|
134
|
+
end # module Octaccord
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Formatter
|
3
|
+
class List < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def format_frame_footer
|
7
|
+
"\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def format_item(issue, options = {})
|
11
|
+
header = options[:header] || "*"
|
12
|
+
comments = if options[:include_comments] then "\n" + issue.comments else "" end
|
13
|
+
|
14
|
+
if issue.references.length > 0
|
15
|
+
refs = " refs:#{issue.references.map{|i| number_link(i)}.join(",")}"
|
16
|
+
else
|
17
|
+
refs = ""
|
18
|
+
end
|
19
|
+
|
20
|
+
if issue.labels != ""
|
21
|
+
labels = " labels:#{issue.labels}"
|
22
|
+
else
|
23
|
+
labels = ""
|
24
|
+
end
|
25
|
+
|
26
|
+
headline = "#{header} #{issue.link} #{issue.status} #{issue.title}#{refs}#{labels}#{comments}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def number_link(number)
|
31
|
+
"[##{number}](../issues/#{number})"
|
32
|
+
end
|
33
|
+
|
34
|
+
end # class List
|
35
|
+
end # module Formatter
|
36
|
+
end # module Octaccord
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Formatter
|
3
|
+
class Number < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def format_frame_header ; ""; end
|
7
|
+
def format_frame_footer ; ""; end
|
8
|
+
|
9
|
+
def format_item(issue)
|
10
|
+
issue.number
|
11
|
+
end
|
12
|
+
end # class Number
|
13
|
+
end # module Formatter
|
14
|
+
end # module Octaccord
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Octaccord
|
2
|
+
module Formatter
|
3
|
+
class Pbl < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def format_header
|
7
|
+
"| No. | Title | Story | Demo | Cost |\n" +
|
8
|
+
"| :---- | :---- | :---- | :---- | ----: |\n"
|
9
|
+
end
|
10
|
+
|
11
|
+
def format_item(issue)
|
12
|
+
return nil unless issue.labels =~ /PBL/
|
13
|
+
cols = []
|
14
|
+
cols << "#{issue.plain_link}"
|
15
|
+
cols << issue.title
|
16
|
+
cols << issue.story
|
17
|
+
cols << issue.demo
|
18
|
+
cols << issue.cost
|
19
|
+
cols = "| " + cols.join(" | ") + " |"
|
20
|
+
end
|
21
|
+
end # class Pbl
|
22
|
+
end # module Formatter
|
23
|
+
end # module Octaccord
|