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