brief 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -13
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +81 -0
  4. data/Guardfile +5 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +71 -0
  7. data/Rakefile +11 -0
  8. data/bin/brief +35 -0
  9. data/brief-0.0.1.gem +0 -0
  10. data/brief.gemspec +33 -0
  11. data/examples/project_overview.md +23 -0
  12. data/lib/brief/cli/commands/config.rb +40 -0
  13. data/lib/brief/cli/commands/publish.rb +27 -0
  14. data/lib/brief/cli/commands/write.rb +26 -0
  15. data/lib/brief/configuration.rb +134 -0
  16. data/lib/brief/document.rb +68 -0
  17. data/lib/brief/dsl.rb +0 -0
  18. data/lib/brief/formatters/base.rb +12 -0
  19. data/lib/brief/formatters/github_milestone_rollup.rb +52 -0
  20. data/lib/brief/git.rb +19 -0
  21. data/lib/brief/github/wiki.rb +9 -0
  22. data/lib/brief/github.rb +78 -0
  23. data/lib/brief/github_client/authentication.rb +32 -0
  24. data/lib/brief/github_client/client.rb +86 -0
  25. data/lib/brief/github_client/commands.rb +5 -0
  26. data/lib/brief/github_client/issue_labels.rb +65 -0
  27. data/lib/brief/github_client/issues.rb +22 -0
  28. data/lib/brief/github_client/milestone_issues.rb +13 -0
  29. data/lib/brief/github_client/organization_activity.rb +9 -0
  30. data/lib/brief/github_client/organization_issues.rb +13 -0
  31. data/lib/brief/github_client/organization_repositories.rb +20 -0
  32. data/lib/brief/github_client/organization_users.rb +9 -0
  33. data/lib/brief/github_client/repository_events.rb +8 -0
  34. data/lib/brief/github_client/repository_issue_events.rb +9 -0
  35. data/lib/brief/github_client/repository_issues.rb +8 -0
  36. data/lib/brief/github_client/repository_labels.rb +18 -0
  37. data/lib/brief/github_client/repository_milestones.rb +9 -0
  38. data/lib/brief/github_client/request.rb +181 -0
  39. data/lib/brief/github_client/request_wrapper.rb +121 -0
  40. data/lib/brief/github_client/response_object.rb +50 -0
  41. data/lib/brief/github_client/single_repository.rb +9 -0
  42. data/lib/brief/github_client/user_activity.rb +16 -0
  43. data/lib/brief/github_client/user_gists.rb +9 -0
  44. data/lib/brief/github_client/user_info.rb +9 -0
  45. data/lib/brief/github_client/user_issues.rb +13 -0
  46. data/lib/brief/github_client/user_organizations.rb +9 -0
  47. data/lib/brief/github_client/user_repositories.rb +9 -0
  48. data/lib/brief/github_client.rb +43 -0
  49. data/lib/brief/handlers/base.rb +62 -0
  50. data/lib/brief/handlers/github_issue.rb +41 -0
  51. data/lib/brief/handlers/github_milestone.rb +37 -0
  52. data/lib/brief/handlers/github_wiki.rb +11 -0
  53. data/lib/brief/line.rb +69 -0
  54. data/lib/brief/parser.rb +354 -0
  55. data/lib/brief/publisher/handler_manager.rb +47 -0
  56. data/lib/brief/publisher.rb +142 -0
  57. data/lib/brief/tree.rb +42 -0
  58. data/lib/brief/version.rb +9 -0
  59. data/lib/brief.rb +56 -0
  60. data/lib/core_ext.rb +37 -0
  61. data/spec/fixtures/front_end_tutorial.md +33 -0
  62. data/spec/fixtures/generated/project_overview.json +0 -0
  63. data/spec/fixtures/generator_dsl_example.rb +22 -0
  64. data/spec/fixtures/project_overview.md +48 -0
  65. data/spec/fixtures/sample.md +19 -0
  66. data/spec/lib/brief/document_spec.rb +35 -0
  67. data/spec/lib/brief/dsl_spec.rb +21 -0
  68. data/spec/lib/brief/line_spec.rb +11 -0
  69. data/spec/lib/brief/parser_spec.rb +12 -0
  70. data/spec/spec_helper.rb +25 -0
  71. metadata +231 -9
data/lib/brief/git.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Brief
2
+ module Git
3
+ def self.current_github_repository
4
+ return nil unless Pathname(Dir.pwd).join('.git').exist?
5
+
6
+ return @current_github_repository if @current_github_repository
7
+
8
+ github_remote = `git remote`.lines.to_a.detect do |remote_name|
9
+ `git remote show #{ remote_name }`.match(/github\.com/)
10
+ end
11
+
12
+ if github_remote
13
+ lines = `git remote show #{ github_remote }`.lines.to_a.map(&:strip)
14
+ line = lines.detect {|l| l.match(/Fetch URL/)}
15
+ @current_github_repository = line.split(':').last.gsub(/\.git$/,'')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Brief
2
+ module Github
3
+ module Wiki
4
+ def self.initialize!
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ require 'brief/formatters/github_milestone_rollup'
2
+ require 'brief/handlers/github_issue'
3
+ require 'brief/handlers/github_milestone'
4
+ require 'brief/handlers/github_wiki'
5
+
6
+ module Brief
7
+ def self.github_nickname
8
+ (Brief.profile.github_nickname rescue nil) || Brief::Git.current_github_repository.try(:split, '/').try(:first)
9
+ end
10
+
11
+ def self.github_repository_name
12
+ (Brief.profile.github_repository_name rescue nil) || Brief::Git.current_github_repository.try(:split, '/').try(:last)
13
+ end
14
+
15
+ def self.repository_issues
16
+ @repository_issues ||= Brief::GithubClient::RepositoryIssues.new(user: github_nickname, repo: github_repository_name)
17
+ end
18
+
19
+ def self.repository_milestones
20
+ @repository_milestones ||= Brief::GithubClient::RepositoryMilestones.new(user: github_nickname, repo: github_repository_name)
21
+ end
22
+ end
23
+
24
+ Brief.define "Project Overview" do
25
+ aliases :overview, :project_overview
26
+
27
+ before(:write) do
28
+ require "brief/github_client"
29
+ !Brief.config.github_repository.nil?
30
+ end
31
+
32
+ before(:publish) do
33
+ require "brief/github_client"
34
+ !Brief.config.github_repository.nil?
35
+ end
36
+
37
+ sample <<-EOF
38
+ # This is a project overview
39
+
40
+ A body paragraph or two here will be the content for a cover page. You can supply whatever background
41
+ info, domain knowledge requirements, processes, file dependencies and links, whatever.
42
+
43
+ The content for the nested milestones and issues below will be transformed into raw markdown
44
+ which will generate a status summary of the milestones and issues as they get created in the Github API.
45
+
46
+ ## This is a milestone heading
47
+
48
+ A milestone is some logical way of grouping issues and attaching a date to them. You will write the body
49
+ of the individual issues and optionally assign them or label them by writing them below. When this brief
50
+ gets published, the issues will be created and added to the milestone.
51
+
52
+ ### This is an issue that belongs to the above milestone
53
+
54
+ The content of your issue goes here. Normal github flavored markdown is supported.
55
+ EOF
56
+
57
+ levels do
58
+ order(2,3,1)
59
+
60
+ level(1) do
61
+ desc "Wiki Page"
62
+ define_handler :publish, "Brief::Handlers::GithubWiki"
63
+ replaces_items_from_level 2, :with => "Brief::Formatters::GithubMilestoneRollup"
64
+ end
65
+
66
+ level(2) do
67
+ desc "Github Milestone"
68
+ define_handler :publish, "Brief::Handlers::GithubMilestone"
69
+ replaces_items_from_level 3, :with => nil
70
+ end
71
+
72
+ level(3) do
73
+ desc "Github Issue"
74
+ define_handler :publish, "Brief::Handlers::GithubIssue"
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,32 @@
1
+ module Brief::GithubClient
2
+ class Authentication
3
+ attr_accessor :github_token, :options
4
+
5
+ InvalidAuth = Class.new(Exception)
6
+
7
+ def initialize(options={})
8
+ options.symbolize_keys! if options.is_a?(Hash)
9
+ @options = options
10
+ fetch_token
11
+ end
12
+
13
+ protected
14
+
15
+ def fetch_token
16
+ @github_token = case
17
+ when options.respond_to?(:github_token)
18
+ options.github_token
19
+ when options.is_a?(Hash) && options.has_key?(:github_token)
20
+ options[:github_token]
21
+ when options.is_a?(Hash) && (options.has_key?(:username) && options.has_key?(:password))
22
+ raise "Not implemented. use personal access tokens."
23
+
24
+ when "#{ENV['GITHUB_TOKEN']}".length > 1
25
+ ENV['GITHUB_TOKEN']
26
+ else
27
+ raise InvalidAuth
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,86 @@
1
+ require 'typhoeus'
2
+
3
+ class Brief::GithubClient::Client
4
+ attr_accessor :options, :cache
5
+
6
+ InvalidAuth = Class.new(Exception)
7
+
8
+ def initialize options={}
9
+ @options = options
10
+
11
+ if options[:auto]
12
+ options[:github_token] ||= Brief::GithubClient::Authentication.new.github_token
13
+ end
14
+ end
15
+
16
+ def request_wrapper_class
17
+ Brief::GithubClient::RequestWrapper
18
+ end
19
+
20
+ def fetch(request_type,*args)
21
+ fetch_request_object(request_type, *args).to_object
22
+ end
23
+
24
+ def fetch_request_object request_type, *args
25
+ options = args.extract_options!
26
+ request_type = request_type.to_s.camelize
27
+
28
+ if request_klass = Brief::GithubClient.const_get(request_type) rescue nil
29
+ request_klass.new(options)
30
+ end
31
+ end
32
+
33
+ def github_token
34
+ options.fetch(:github_token, impersonate_user.try(:github_token))
35
+ end
36
+
37
+ def impersonate_user
38
+ @impersonate_user ||= options.fetch(:user, nil)
39
+ end
40
+
41
+ def headers
42
+ base = {
43
+ "Authorization" => "token #{ github_token }",
44
+ "Accepts" => "application/json"
45
+ }
46
+
47
+ base.merge(options[:headers] || {}).stringify_keys
48
+ end
49
+
50
+ def anonymous?
51
+ !!(options[:public] || options[:anonymous])
52
+ end
53
+
54
+ def delete_request type, params={}
55
+ if !github_token.present? && !anonymous?
56
+ raise InvalidAuth
57
+ end
58
+
59
+ request_wrapper_class.new(type,params,headers).delete
60
+ end
61
+
62
+ def post_request type, params={}
63
+ if !github_token.present? && !anonymous?
64
+ raise InvalidAuth
65
+ end
66
+
67
+ request_wrapper_class.new(type,params,headers).post
68
+ end
69
+
70
+ def get_request type, params={}
71
+ if !github_token.present? && !anonymous?
72
+ raise InvalidAuth
73
+ end
74
+
75
+ request_wrapper_class.new(type,params,headers).get
76
+ end
77
+
78
+ def update_request type, params={}
79
+ if !github_token.present? && !anonymous?
80
+ raise InvalidAuth
81
+ end
82
+
83
+ request_wrapper_class.new(type,params,headers).update
84
+ end
85
+
86
+ end
@@ -0,0 +1,5 @@
1
+ module Brief::GithubClient
2
+ module Commands
3
+
4
+ end
5
+ end
@@ -0,0 +1,65 @@
1
+ module Brief::GithubClient
2
+ class IssueLabels < Request
3
+
4
+ requires :org, :repo
5
+
6
+ def endpoint
7
+ "repos/#{ org }/#{ repo }/labels"
8
+ end
9
+
10
+ Defaults = {
11
+ # stage labels
12
+ "s:backlog" => "c7def8",
13
+ "s:greenlit" => "bfe5bf",
14
+ "s:review" => "fef2c0",
15
+ "s:in_progress" => "3ded58",
16
+
17
+ # priority labels
18
+ "p:1" => "e11d21",
19
+ "p:2" => "eb6420",
20
+
21
+ # type labels
22
+ "t:development" => "bada55",
23
+ "t:design" => "55adba",
24
+ "t:ux" => "2234fe",
25
+ "t:project" => "ae3498",
26
+
27
+ # acceptance labels
28
+ "a:approved" => "339933",
29
+ "a:rejected" => "993333"
30
+ }
31
+
32
+ def missing_defaults
33
+ current = all.collect(&:name)
34
+ @missing_defaults ||= Defaults.keys - current
35
+ end
36
+
37
+ def missing_defaults?
38
+ missing_defaults.length > 0
39
+ end
40
+
41
+ def create_status_sort_labels
42
+ Defaults.each do |name, color|
43
+ create_or_update(name, color)
44
+ end
45
+ end
46
+
47
+ def delete_github_defaults
48
+ %w{bug duplicate enhancement invalid wontfix question}.each do |name|
49
+ destroy(name)
50
+ end
51
+ end
52
+
53
+ def create_or_update name, color
54
+ existing = show(name)
55
+
56
+ unless existing.nil? || (existing.present? && existing.respond_to?(:message))
57
+ update(name, name: name, color: color)
58
+ return show(name)
59
+ end
60
+
61
+ create(name: name, color: color)
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ module Brief::GithubClient
2
+ class Issues < Request
3
+ requires :org, :repo
4
+
5
+ def endpoint
6
+ case
7
+ when supplied_org && repo
8
+ "#{ supplied_org }/#{ repo }/issues"
9
+ when user && repo
10
+ "repos/#{ user }/#{ repo }/issues"
11
+ when supplied_org
12
+ "orgs/#{ supplied_org }/issues"
13
+ when user
14
+ "users/#{ user }/issues"
15
+ end
16
+ end
17
+
18
+ def params
19
+ @params.merge(sort:"updated")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Brief::GithubClient
2
+ class MilestoneIssues < RepositoryIssues
3
+ def params
4
+ p = @params
5
+
6
+ if options[:milestone] && milestone.respond_to?(:number)
7
+ p[:milestone_number] = milestone.number
8
+ end
9
+
10
+ p
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Brief::GithubClient
2
+ class OrganizationActivity < Request
3
+ requires :supplied_org, :user
4
+
5
+ def endpoint
6
+ "users/#{ user }/events/orgs/#{ org }"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Brief::GithubClient
2
+ class OrganizationIssues < Request
3
+ requires :supplied_org
4
+
5
+ def endpoint
6
+ "orgs/#{ org }/issues"
7
+ end
8
+
9
+ def params
10
+ @params.merge(sort:"updated")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Brief::GithubClient
2
+ class OrganizationRepositories < Request
3
+ requires :supplied_org
4
+
5
+ def endpoint
6
+ "orgs/#{ org }/repos"
7
+ end
8
+
9
+ def all
10
+ @all ||= self.result
11
+ end
12
+
13
+ def to_list
14
+ all.map do |repository|
15
+ repository.slice("id","name","html_url","description","ssh_url")
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Brief::GithubClient
2
+ class OrganizationUsers < Request
3
+ requires :supplied_org
4
+
5
+ def endpoint
6
+ "orgs/#{ org }/members"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Brief::GithubClient
2
+ class RepositoryEvents < Request
3
+ requires :org, :repo
4
+ def endpoint
5
+ "repos/#{ org }/#{ repo }/events"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Brief::GithubClient
2
+ class RepositoryIssueEvents < Request
3
+ requires :org, :repo
4
+
5
+ def endpoint
6
+ "repos/#{ org }/#{ repo }/issues/events"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Brief::GithubClient
2
+ class RepositoryIssues < Request
3
+ requires :org, :repo
4
+ def endpoint
5
+ "repos/#{ org }/#{ repo }/issues"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Brief::GithubClient
2
+ class RepositoryLabels < Request
3
+ requires :org, :repo
4
+
5
+ def endpoint
6
+ "repos/#{ org }/#{ repo }/labels"
7
+ end
8
+
9
+ def stage_labels
10
+ @stage_labels ||= all.select {|label| label.name && label.name.match(/^s:/) }
11
+ end
12
+
13
+ def priority_labels
14
+ @priority_labels ||= all.select {|label| label.name && label.name.match(/^p:/) }
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Brief::GithubClient
2
+ class RepositoryMilestones < Request
3
+ requires :org, :repo
4
+
5
+ def endpoint
6
+ "repos/#{ org }/#{ repo }/milestones"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,181 @@
1
+ require 'rubygems'
2
+ require 'escape_utils'
3
+ require 'uri_template'
4
+
5
+ class Brief::GithubClient::Request
6
+
7
+ MissingArguments = Class.new(Exception)
8
+
9
+ attr_accessor :options, :user, :org, :repo, :params, :headers, :github_token
10
+
11
+ class << self
12
+ attr_accessor :_required_arguments
13
+ end
14
+
15
+ def self.requires *args
16
+ (self._required_arguments = args).uniq!
17
+ end
18
+
19
+ def self.required_arguments
20
+ Array(self._required_arguments).uniq
21
+ end
22
+
23
+ def initialize(options={}, &block)
24
+ @options = options.with_indifferent_access.dup
25
+ @client, @user, @org, @repo, @github_token = options.values_at(:client, :user,:org,:repo,:github_token)
26
+ @params = options[:params] || {}
27
+ @headers = options[:headers] || {}
28
+
29
+ instance_eval(&blk) if block_given?
30
+
31
+ assert_valid_arguments!
32
+ end
33
+
34
+ def required_arguments
35
+ Array(self.class._required_arguments)
36
+ end
37
+
38
+ def with_valid_arguments &blk
39
+ instance_eval(&blk) if block_given?
40
+ assert_valid_arguments!
41
+ self
42
+ end
43
+
44
+ def assert_valid_arguments!
45
+ return true if required_arguments.length > 0
46
+
47
+ valid = required_arguments.all? do |arg|
48
+ test = false
49
+ test = true if !!self.send(arg).present?
50
+ test = true if options.has_key?(arg)
51
+
52
+ test
53
+ end
54
+
55
+ raise MissingArguments unless valid
56
+ end
57
+
58
+ def to_object
59
+ req = self
60
+
61
+ response_wrapper = lambda do |r|
62
+ Brief::GithubClient::ResponseObject.new(r).with_request_object(req)
63
+ end
64
+
65
+ return object.map(&response_wrapper) if object.is_a?(Array)
66
+
67
+ response_wrapper.call(object)
68
+ end
69
+
70
+ def object
71
+ records
72
+ end
73
+
74
+ def refresh
75
+ @request = nil
76
+ self
77
+ end
78
+
79
+ def all
80
+ to_object
81
+ end
82
+
83
+ def create params={}
84
+ client.post_request(request_endpoint, params).request.run
85
+ end
86
+
87
+ def create_object params={}
88
+ resp = create(params)
89
+ Hashie::Mash.new(JSON.parse(resp.body))
90
+ end
91
+
92
+ def update record_id, params={}
93
+ client.update_request("#{ request_endpoint }/#{ record_id }", params).request.run
94
+ end
95
+
96
+ def update_object record_id, params={}
97
+ resp = update(record_id, params)
98
+ Hashie::Mash.new(JSON.parse(resp.body))
99
+ end
100
+
101
+ def destroy record_id, params={}
102
+ client.delete_request("#{ request_endpoint }/#{ record_id }").request.run
103
+ end
104
+
105
+ def show record_id, params={}
106
+ client.get_request("#{ request_endpoint }/#{ record_id }", params).to_object
107
+ end
108
+
109
+ def client
110
+ return @client if @client
111
+
112
+ if impersonate_user.present?
113
+ @client = Brief::GithubClient::Client.new(user: impersonate_user, headers: headers, github_token: github_token)
114
+ end
115
+
116
+ @client = Brief.github_client
117
+ end
118
+
119
+ def records
120
+ @records = request.records
121
+ end
122
+
123
+ def results
124
+ records
125
+ end
126
+
127
+ def result
128
+ records
129
+ end
130
+
131
+ def request_endpoint
132
+ options.fetch(:endpoint, endpoint)
133
+ end
134
+
135
+ def request refresh=false
136
+ @request = nil if refresh
137
+ @request ||= client.get_request(request_endpoint, params)
138
+ end
139
+
140
+ def organization_or_user
141
+ supplied_org || user
142
+ end
143
+
144
+ def user_or_organization
145
+ supplied_user || supplied_org
146
+ end
147
+
148
+ def org
149
+ organization_or_user
150
+ end
151
+
152
+ # The idea of 'supplied' means it was provided to the object
153
+ # and not calculated in any way. This is used when determining
154
+ # the value for the endpoint, in the context of a github user vs github organization
155
+ def supplied_org
156
+ @org
157
+ end
158
+
159
+ def supplied_user
160
+ @user
161
+ end
162
+
163
+ def supplied_repo
164
+ @repo
165
+ end
166
+
167
+ protected
168
+ def github_token
169
+ @github_token || impersonate_user.try(:github_token) || Brief.config.github_token
170
+ end
171
+
172
+ def endpoint
173
+ "users/#{ user }"
174
+ end
175
+
176
+ def impersonate_user
177
+ if defined?(::User) && ::User.respond_to?(:find_by_github_nickname)
178
+ @impersonate_user ||= ::User.find_by_github_nickname(user)
179
+ end
180
+ end
181
+ end