brief 0.0.1 → 0.0.2

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