octaccord 0.0.1
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 +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
|