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,93 @@
1
+ :bangbang: This Iteration Record is created by octaccord from examples/Itr0000-template.md.erb
2
+
3
+ Example:
4
+ ```sh
5
+ $ octaccord create_iteration nomlab/LastNote Itr0095 \
6
+ --team="nomlab/GN" \
7
+ --start="2014-07-03T16:30:00+09:00" \
8
+ --due="2014-07-16T13:00:00+09:00" \
9
+ --manager="okada-takuya" \
10
+ --template=../octaccord/examples/Itr0000-template.md.erb \
11
+ | octaccord filter > Itr0095-sample.md
12
+
13
+ $ octaccord filter < Product-Backlog.md > tmp.md
14
+ $ mv tmp.md Product-Backlog.md
15
+ ```
16
+ <%
17
+ now = Time.now.iso8601
18
+ repos = itr.repository
19
+ members = itr.members.sort.map{|m| "@" + m}.join(", ")
20
+ -%>
21
+ [[(<%= itr.prev.name %>)|<%= itr.prev.name %>]] | [[(<%= itr.next.name %>)|<%= itr.next.name %>]] | [[(PBL)|Product-Backlog]] | [PBL Issues](../issues?labels=PBL&state=open&sort=updated)
22
+
23
+ # <%= itr.name %> Iteration Record
24
+
25
+ * Manager: @<%= itr.manager %>
26
+ * Term: <%= itr.start.strftime("%Y-%m-%d %H:%M") %>..<%= itr.due.strftime("%Y-%m-%d %H:%M") %>
27
+
28
+ ## <%= itr.start.strftime("%Y-%m-%d") %> Iteration Plan
29
+
30
+ * Attendees: <%= members %>
31
+ * Pairs: <%= members %>
32
+
33
+ ### 1. High-Level Objectives
34
+
35
+ [[PBL|Product-Backlog]] と [PBL Issues](../issues?labels=PBL&state=open&sort=updated) を見ながら以下について議論した.
36
+
37
+ #### 完成イメージと本イテレーションの方針について
38
+
39
+ * blah blah ........
40
+ * blah blah ........
41
+ * blah blah ........
42
+
43
+ #### [[Product Backlog|Product-Backlog]] の調整について
44
+
45
+ * #17 は,Cost を 5 から 8 に上げるべき.なぜなら blah blah ........
46
+ * #17 よりも #38 を優先したほうがよい.なぜなら blah blah ........
47
+ * blah blah ........
48
+
49
+ #### 本イテレーションで取り組む PBL Issue について
50
+
51
+ * 議論の結果,[[Product Backlog|Product-Backlog]] を調整して優先順位の並べ替えを行った.
52
+ * 取り組むべき PBL Issue に PBL と <% itr.name %> ラベルを付与し,それらの description 欄に Story, Demo, Cost の項目を記述した.
53
+ * 個々の PBL Issue に関する技術的議論は,その Issue のコメント欄に記述した.
54
+ * 以下,[PBL Issues](../issues?labels=PBL&state=open&sort=updated) から octaccord によって自動抽出した結果を示す.
55
+
56
+ <!-- begin:octaccord scan <%= repos %> --search="label:PBL label:<%= itr.name %>" --format=pbl --topology=Product-Backlog.md -->
57
+ <!-- end:octaccord -->
58
+
59
+ #### 本議論中で更新された Issue コメント一覧
60
+
61
+ <!-- begin:octaccord comments <%= repos %> --since="<%= itr.start.iso8601 %>" --before="<%= (itr.start + 3600 * 2).iso8601 %>"-->
62
+ <!-- end:octaccord -->
63
+
64
+ ### 2. Work Item Assignments
65
+
66
+ <!-- begin:octaccord scan <%= repos %> --search="label:<%= itr.name %> -label:PBL" --format=list -->
67
+ <!-- end:octaccord -->
68
+
69
+ ### 3. Evaluation Criteria
70
+
71
+ * 全テストの完成と通過
72
+ * コードレビューの完了
73
+ * 全ての PBL Issue について,デモを実施
74
+
75
+ ## <%= itr.due.strftime("%Y-%m-%d") %> Iteration Review
76
+
77
+ * Attendees: <%= members %>
78
+
79
+ できあがったプロダクトを見ながら,リリース可能かどうかを判断する会議.
80
+ * 完了できなかった,プロダクトバックログについて担当者が説明する
81
+ * プロダクトバックログに追加すべき項目の有無について議論する
82
+ * プロジェクトを進めるうえで問題となる項目について議論する
83
+ * 現在の進捗をふまえて,リリース日や完了日を予測する
84
+
85
+ ### 1. Issue and Pull Request Review
86
+
87
+ Issues を見ながら,Issues にコメントを付ける.議事は,Issues のコメントとして付ける.
88
+ 本 Wiki には, octaccord がコメントを追加する.
89
+
90
+ #### 本議論中で更新された Issue コメント一覧
91
+
92
+ <!-- begin:octaccord comments <%= repos %> --since="<%= itr.due.iso8601 %>" --before="<%= (itr.due + 3600 * 2).iso8601 %>"-->
93
+ <!-- end:octaccord -->
@@ -0,0 +1,11 @@
1
+ compdef _octaccord octaccord
2
+
3
+ _octaccord () {
4
+ local curcontext="$curcontext"
5
+ local line state _opts
6
+
7
+ _opts=("${(@f)$(octaccord completions -- ${(Q)words[2,-1]})}")
8
+ $_opts && return 0
9
+
10
+ return 1
11
+ }
@@ -0,0 +1,2 @@
1
+ dir = File.dirname(__FILE__) + "/extensions"
2
+ require "#{dir}/octokit.rb"
@@ -0,0 +1,16 @@
1
+ require "octokit"
2
+
3
+ module Octokit
4
+ class Client
5
+ module Issues
6
+ # There are these two PRs in the octokit upstream issues.
7
+ # However, both of them are not merged yet.
8
+ # So, I introduced PR 232 by hand.
9
+ # https://github.com/octokit/octokit.rb/pull/229
10
+ # https://github.com/octokit/octokit.rb/pull/232
11
+ def ext_update_issue(repo, number, options = {})
12
+ patch "#{Repository.path repo}/issues/#{number}", options
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ require "octaccord/version"
2
+ require "octaccord/errors"
3
+ require "octaccord/command"
4
+ require "octaccord/formatter"
5
+ require "octaccord/iteration"
6
+ require "extensions"
7
+
8
+ module Octaccord
9
+ # Your code goes here...
10
+ end
@@ -0,0 +1,16 @@
1
+ module Octaccord
2
+ module Command
3
+ dir = File.dirname(__FILE__) + "/command"
4
+ autoload :AddCollaborator, "#{dir}/add_collaborator.rb"
5
+ autoload :Comments, "#{dir}/comments.rb"
6
+ autoload :Completions, "#{dir}/completions.rb"
7
+ autoload :GetTeamMembers, "#{dir}/get_team_members.rb"
8
+ autoload :Info, "#{dir}/info.rb"
9
+ autoload :Label, "#{dir}/label.rb"
10
+ autoload :Link, "#{dir}/link.rb"
11
+ autoload :Scan, "#{dir}/scan.rb"
12
+ autoload :Show, "#{dir}/show.rb"
13
+ autoload :UpdateIssues, "#{dir}/update_issues.rb"
14
+ autoload :CreateIteration, "#{dir}/create_iteration.rb"
15
+ end # module Command
16
+ end # module Octaccord
@@ -0,0 +1,34 @@
1
+ module Octaccord
2
+ module Command
3
+ class AddCollaborator
4
+ Encoding.default_external = "UTF-8"
5
+
6
+ def initialize(client, repos, **options)
7
+ if options[:users]
8
+ add_users(client, repos, options[:users].split(','))
9
+ elsif options[:teams]
10
+ add_team_members(client, repos, options[:teams].split(','))
11
+ end
12
+ end
13
+
14
+ def add_users(client, repos, users)
15
+ users.each do |user|
16
+ response = client.add_collaborator(repos, user)
17
+ end
18
+ end
19
+
20
+ def add_team_members(client, repos, teams)
21
+ teams.each do |team|
22
+ org, team_name = team.split('/')
23
+ response = client.organization_teams(org)
24
+ team_id = response.select{|t| t.name == team_name}.first.id
25
+ response = client.team_members(team_id)
26
+ members = response.map(&:login)
27
+ members.each do |member|
28
+ response = client.add_collaborator(repos, member)
29
+ end
30
+ end
31
+ end
32
+ end # class AddCollaborator
33
+ end # module Command
34
+ end # module Octaccord
@@ -0,0 +1,50 @@
1
+ module Octaccord
2
+ module Command
3
+ class Comments
4
+
5
+ def initialize(client, repos, since, before)
6
+ begin
7
+ comments = client.issues_comments(repos, :since => since)
8
+ issues = gather_issues(comments, before)
9
+
10
+ issues.each do |uri, comments|
11
+ next if comments.empty?
12
+ issue = comments.first.rels[:issue].get.data
13
+ print format_issue(issue)
14
+
15
+ comments.each do |comment|
16
+ print format_comment(comment)
17
+ end
18
+ end
19
+ print "\n"
20
+ rescue Octokit::ClientError => e
21
+ STDERR.puts "Error: ##{issue} -- #{e.message.split(' // ').first}"
22
+ end
23
+ end
24
+ private
25
+
26
+ def gather_issues(comments, before)
27
+ issues = {}
28
+
29
+ comments.each do |comment|
30
+ next if comment.updated_at > before
31
+ uri = comment.rels[:issue].href
32
+ issues[uri] ||= []
33
+ issues[uri] << comment
34
+ end
35
+ return issues
36
+ end
37
+
38
+ def format_comment(comment)
39
+ comment = Formatter::Comment.new(comment)
40
+ " * #{comment.summary} #{comment.link(text: "...")}\n"
41
+ end
42
+
43
+ def format_issue(issue)
44
+ issue = Formatter::Issue.new(issue)
45
+ return "* #{issue.link} #{issue.status} #{issue.title}\n"
46
+ end
47
+
48
+ end # class Comments
49
+ end # module Command
50
+ end # module Octaccord
@@ -0,0 +1,56 @@
1
+ module Octaccord
2
+ module Command
3
+ class Completions
4
+
5
+ def initialize(help, global_options, arguments)
6
+ command_name = arguments.first
7
+
8
+ if command_name and help[command_name]
9
+ options(help, global_options, command_name)
10
+ else
11
+ commands(help)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def commands(help)
18
+ puts "_values"
19
+ puts "Sub-commands:"
20
+ puts "help[Available commands or one specific COMMAND]"
21
+
22
+ help.each do |name, option|
23
+ next if name == "completions"
24
+ puts "#{name}[#{option.description}]"
25
+ end
26
+ end
27
+
28
+ def options(help, global_options, command_name)
29
+ print "_arguments\n-A\n*\n"
30
+ options = help[command_name].options.merge(global_options)
31
+
32
+ options.each do |name, opt|
33
+ if opt.type == :boolean
34
+ print "(--#{name})--#{name}[#{opt.description}]\n"
35
+ else
36
+ print "(--#{name})--#{name}=-[#{opt.description}]:#{opt.banner}:#{possible_values(opt)}\n"
37
+ end
38
+ end
39
+ end
40
+
41
+ def possible_values(option)
42
+ return "(" + option.enum.join(" ") + ")" if option.enum
43
+
44
+ case option.banner
45
+ when "FILE"
46
+ "_files"
47
+ when "DIRECTORY"
48
+ "_files -/"
49
+ else
50
+ ""
51
+ end
52
+ end
53
+
54
+ end # class Completions
55
+ end # module Command
56
+ end # module Octaccord
@@ -0,0 +1,21 @@
1
+ require "erb"
2
+
3
+ module Octaccord
4
+ module Command
5
+ class CreateIteration
6
+ def initialize(client, repos, name, **options)
7
+ if file = options[:template]
8
+ content = File.open(file).read
9
+ else
10
+ content = gets(nil)
11
+ end
12
+ template = ERB.new(content, nil, "-")
13
+ options.delete(:template)
14
+
15
+ # binding itr, repos
16
+ itr = Octaccord::Iteration.new(client: client, name: name, repository: repos, **options)
17
+ puts template.result(binding)
18
+ end
19
+ end # class CreateIteration
20
+ end # module Command
21
+ end # module Octaccord
@@ -0,0 +1,18 @@
1
+ module Octaccord
2
+ module Command
3
+ class GetTeamMembers
4
+ Encoding.default_external = "UTF-8"
5
+
6
+ def initialize(client, teams, **options)
7
+ teams.split(',').each do |team|
8
+ org, team_name = team.split('/')
9
+ response = client.organization_teams(org)
10
+ team_id = response.select{|t| t.name == team_name}.first.id
11
+ response = client.team_members(team_id)
12
+ members = response.map(&:login)
13
+ puts members.join(', ')
14
+ end
15
+ end
16
+ end # class GetTeamMembers
17
+ end # module Command
18
+ end # module Octaccord
@@ -0,0 +1,18 @@
1
+ require "rugged"
2
+
3
+ module Octaccord
4
+ module Command
5
+ class Info
6
+
7
+ def initialize(client, **options)
8
+ begin
9
+ repo = Rugged::Repository.discover(".")
10
+ url = repo.remotes.find {|remote| remote.url =~ /github\.com/}.url
11
+ puts url
12
+ rescue Rugged::RepositoryError => e
13
+ STDERR.print "Error: you are not in github repository.\n"
14
+ end
15
+ end
16
+ end # class Info
17
+ end # module Command
18
+ end # module Octaccord
@@ -0,0 +1,16 @@
1
+ module Octaccord
2
+ module Command
3
+ class Label
4
+
5
+ def initialize(client, repos, **options)
6
+ begin
7
+ client.labels(repos).each do |label|
8
+ puts label.name
9
+ end
10
+ rescue Octokit::ClientError => e
11
+ STDERR.puts "Error: ##{issue} -- #{e.message.split(' // ').first}"
12
+ end
13
+ end
14
+ end # class Label
15
+ end # module Command
16
+ end # module Octaccord
@@ -0,0 +1,122 @@
1
+ module Octaccord
2
+ module Command
3
+ class Scan
4
+ Encoding.default_external = "UTF-8"
5
+
6
+ def initialize(client, repos, replace = nil, **options)
7
+ if options[:format] == "json"
8
+ formatter = nil
9
+ else
10
+ formatter = Octaccord::Formatter.build(formatter: options[:format] || :text)
11
+ end
12
+ if query = options[:search]
13
+ items = search(client, repos, query, formatter)
14
+ else
15
+ items = scan(client, repos, options[:type], formatter)
16
+ end
17
+ format(items, formatter, replace)
18
+ end
19
+
20
+ def search(client, repos, query, formatter)
21
+ # https://help.github.com/articles/searching-issues
22
+ # or issues = client.list_issues(repos)
23
+ # type:issue means ignore pull request
24
+ query = "repo:#{repos} #{query}"
25
+ query << " state:open" unless query =~ /state:/
26
+ query << " type:issue" unless query =~ /type:/
27
+ STDERR.puts "Query: #{query}" if $OCTACCORD_DEBUG
28
+
29
+ if query =~ /(-)?(refers|refered):\(([^)]*)\)/
30
+ negop, reftype, subquery = $1, $2, $3
31
+ query = query.sub(/-?(refers|refered):\([^)]*\)/, "")
32
+
33
+ subquery << " repo:#{repos}"
34
+ subquery << " state:open" unless subquery =~ /state:/
35
+ subquery << " type:issue" unless subquery =~ /type:/
36
+
37
+ if $OCTACCORD_DEBUG
38
+ STDERR.puts "SUBQUERY: #{subquery}"
39
+ STDERR.puts "QUERY: #{query}"
40
+ end
41
+
42
+ subitems = client.search_issues(subquery).items
43
+ issues = client.search_issues(query).items
44
+
45
+ if reftype == "refers"
46
+ return refers(subitems, issues, (negop == "-"))
47
+ else
48
+ return refered(issues, subitems, (negop == "-"))
49
+ end
50
+ end
51
+
52
+ issues = client.search_issues(query).items
53
+ end
54
+
55
+ def scan(client, repos, type, formatter)
56
+ if type == "pr"
57
+ issues = client.issues(repos).select{|issue| issue.pull_request}
58
+ else
59
+ issues = client.issues(repos).select{|issue| not issue.pull_request}
60
+ end
61
+ end
62
+
63
+ def refers(parents, issues, negop)
64
+ parents, children = corss_reference(parents, issues, negop)
65
+ return children
66
+ end
67
+
68
+ def refered(parents, issues, negop)
69
+ parents, children = corss_reference(parents, issues, negop)
70
+ return parents
71
+ end
72
+
73
+ def corss_reference(parents, children, negop)
74
+ if $OCTACCORD_DEBUG
75
+ STDERR.puts "* PARENTS: #{parents.map{|i| i.number}.join(', ')}"
76
+ STDERR.puts "* CHILDREN: #{children.map{|i| i.number}.join(', ')}"
77
+ STDERR.puts "* NEGOP: #{negop}"
78
+ end
79
+
80
+ refered_parents = []
81
+ refering_children = []
82
+
83
+ parents.each do |parent|
84
+ STDERR.puts "ABOUT PARENT: #{parent.number}:" if $OCTACCORD_DEBUG
85
+ children.each do |child|
86
+ Octaccord::Formatter::Issue.new(child).references.each do |refer_num|
87
+ STDERR.puts " child #{child.number} refers #{refer_num} #{parent.number.to_i == refer_num ? " FOUND" : "" }" if $OCTACCORD_DEBUG
88
+ if parent.number.to_i == refer_num
89
+ STDERR.puts "FOUND #{refer_num}" if $OCTACCORD_DEBUG
90
+ refered_parents << parent
91
+ refering_children << child
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ refered_parents.uniq!
98
+ refering_children.uniq!
99
+
100
+ if negop
101
+ return [parents-refered_parents, children-refering_children]
102
+ else
103
+ return [refered_parents, refering_children]
104
+ end
105
+ end
106
+
107
+ def format(issues, formatter, replace = nil)
108
+ if formatter == nil # raw format
109
+ print issues.to_s
110
+ return
111
+ end
112
+
113
+ issues.each do |issue|
114
+ formatter << issue
115
+ end
116
+ formatter.order(replace) if replace
117
+ print formatter.to_s
118
+ end
119
+
120
+ end # class Scan
121
+ end # module Command
122
+ end # module Octaccord