brief 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/bin/brief +35 -0
- data/brief-0.0.1.gem +0 -0
- data/brief.gemspec +33 -0
- data/examples/project_overview.md +23 -0
- data/lib/brief/cli/commands/config.rb +40 -0
- data/lib/brief/cli/commands/publish.rb +27 -0
- data/lib/brief/cli/commands/write.rb +26 -0
- data/lib/brief/configuration.rb +134 -0
- data/lib/brief/document.rb +68 -0
- data/lib/brief/dsl.rb +0 -0
- data/lib/brief/formatters/base.rb +12 -0
- data/lib/brief/formatters/github_milestone_rollup.rb +52 -0
- data/lib/brief/git.rb +19 -0
- data/lib/brief/github/wiki.rb +9 -0
- data/lib/brief/github.rb +78 -0
- data/lib/brief/github_client/authentication.rb +32 -0
- data/lib/brief/github_client/client.rb +86 -0
- data/lib/brief/github_client/commands.rb +5 -0
- data/lib/brief/github_client/issue_labels.rb +65 -0
- data/lib/brief/github_client/issues.rb +22 -0
- data/lib/brief/github_client/milestone_issues.rb +13 -0
- data/lib/brief/github_client/organization_activity.rb +9 -0
- data/lib/brief/github_client/organization_issues.rb +13 -0
- data/lib/brief/github_client/organization_repositories.rb +20 -0
- data/lib/brief/github_client/organization_users.rb +9 -0
- data/lib/brief/github_client/repository_events.rb +8 -0
- data/lib/brief/github_client/repository_issue_events.rb +9 -0
- data/lib/brief/github_client/repository_issues.rb +8 -0
- data/lib/brief/github_client/repository_labels.rb +18 -0
- data/lib/brief/github_client/repository_milestones.rb +9 -0
- data/lib/brief/github_client/request.rb +181 -0
- data/lib/brief/github_client/request_wrapper.rb +121 -0
- data/lib/brief/github_client/response_object.rb +50 -0
- data/lib/brief/github_client/single_repository.rb +9 -0
- data/lib/brief/github_client/user_activity.rb +16 -0
- data/lib/brief/github_client/user_gists.rb +9 -0
- data/lib/brief/github_client/user_info.rb +9 -0
- data/lib/brief/github_client/user_issues.rb +13 -0
- data/lib/brief/github_client/user_organizations.rb +9 -0
- data/lib/brief/github_client/user_repositories.rb +9 -0
- data/lib/brief/github_client.rb +43 -0
- data/lib/brief/handlers/base.rb +62 -0
- data/lib/brief/handlers/github_issue.rb +41 -0
- data/lib/brief/handlers/github_milestone.rb +37 -0
- data/lib/brief/handlers/github_wiki.rb +11 -0
- data/lib/brief/line.rb +69 -0
- data/lib/brief/parser.rb +354 -0
- data/lib/brief/publisher/handler_manager.rb +47 -0
- data/lib/brief/publisher.rb +142 -0
- data/lib/brief/tree.rb +42 -0
- data/lib/brief/version.rb +9 -0
- data/lib/brief.rb +56 -0
- data/lib/core_ext.rb +37 -0
- data/spec/fixtures/front_end_tutorial.md +33 -0
- data/spec/fixtures/generated/project_overview.json +0 -0
- data/spec/fixtures/generator_dsl_example.rb +22 -0
- data/spec/fixtures/project_overview.md +48 -0
- data/spec/fixtures/sample.md +19 -0
- data/spec/lib/brief/document_spec.rb +35 -0
- data/spec/lib/brief/dsl_spec.rb +21 -0
- data/spec/lib/brief/line_spec.rb +11 -0
- data/spec/lib/brief/parser_spec.rb +12 -0
- data/spec/spec_helper.rb +25 -0
- 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
|
data/lib/brief/github.rb
ADDED
@@ -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,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,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,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,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
|