octaccord 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +62 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.org +95 -0
  8. data/Rakefile +7 -0
  9. data/bin/octaccord +311 -0
  10. data/examples/Itr0000-template.md.erb +93 -0
  11. data/examples/octaccord-completion.zsh +11 -0
  12. data/lib/extensions.rb +2 -0
  13. data/lib/extensions/octokit.rb +16 -0
  14. data/lib/octaccord.rb +10 -0
  15. data/lib/octaccord/command.rb +16 -0
  16. data/lib/octaccord/command/add_collaborator.rb +34 -0
  17. data/lib/octaccord/command/comments.rb +50 -0
  18. data/lib/octaccord/command/completions.rb +56 -0
  19. data/lib/octaccord/command/create_iteration.rb +21 -0
  20. data/lib/octaccord/command/get_team_members.rb +18 -0
  21. data/lib/octaccord/command/info.rb +18 -0
  22. data/lib/octaccord/command/label.rb +16 -0
  23. data/lib/octaccord/command/scan.rb +122 -0
  24. data/lib/octaccord/command/show.rb +28 -0
  25. data/lib/octaccord/command/update_issues.rb +28 -0
  26. data/lib/octaccord/errors.rb +2 -0
  27. data/lib/octaccord/formatter.rb +105 -0
  28. data/lib/octaccord/formatter/comment.rb +58 -0
  29. data/lib/octaccord/formatter/debug.rb +17 -0
  30. data/lib/octaccord/formatter/issue.rb +134 -0
  31. data/lib/octaccord/formatter/list.rb +36 -0
  32. data/lib/octaccord/formatter/number.rb +14 -0
  33. data/lib/octaccord/formatter/pbl.rb +23 -0
  34. data/lib/octaccord/formatter/table.rb +17 -0
  35. data/lib/octaccord/formatter/text.rb +14 -0
  36. data/lib/octaccord/formatter/user.rb +42 -0
  37. data/lib/octaccord/iteration.rb +61 -0
  38. data/lib/octaccord/version.rb +3 -0
  39. data/octaccord.gemspec +24 -0
  40. data/spec/octaccord_spec.rb +11 -0
  41. data/spec/spec_helper.rb +2 -0
  42. 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,2 @@
1
+ class Octaccord::ConfigurationError < StandardError
2
+ end
@@ -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