perkins 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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.env.example +4 -0
  4. data/.gitignore +19 -0
  5. data/.pryrc +3 -0
  6. data/.rspec +2 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +71 -0
  10. data/Rakefile +28 -0
  11. data/TODO.md +4 -0
  12. data/bin/perkins +6 -0
  13. data/db/migrate/20150130143030_create_repo.rb +18 -0
  14. data/db/migrate/20150130143050_create_builds.rb +20 -0
  15. data/db/schema.rb +38 -0
  16. data/examples/config.rb +12 -0
  17. data/examples/database.yml +17 -0
  18. data/examples/mongo.yml +13 -0
  19. data/lib/core_ext/hash/compact.rb +8 -0
  20. data/lib/core_ext/hash/deep_merge.rb +15 -0
  21. data/lib/core_ext/hash/deep_symbolize_keys.rb +20 -0
  22. data/lib/core_ext/object/false.rb +5 -0
  23. data/lib/core_ext/string/indent.rb +5 -0
  24. data/lib/core_ext/string/unindent.rb +5 -0
  25. data/lib/perkins/.DS_Store +0 -0
  26. data/lib/perkins/application.rb +40 -0
  27. data/lib/perkins/assets/images/github.svg +4 -0
  28. data/lib/perkins/assets/images/spinner.svg +23 -0
  29. data/lib/perkins/assets/javascripts/app.js +9 -0
  30. data/lib/perkins/assets/javascripts/log_view.js.coffee +95 -0
  31. data/lib/perkins/assets/javascripts/perkings.js.coffee +40 -0
  32. data/lib/perkins/assets/javascripts/vendor/ansiparse.js +187 -0
  33. data/lib/perkins/assets/javascripts/vendor/jquery.timeago.js +189 -0
  34. data/lib/perkins/assets/javascripts/vendor/log.js +2 -0
  35. data/lib/perkins/assets/javascripts/vendor/minispade.js +55 -0
  36. data/lib/perkins/assets/stylesheets/app.css +2 -0
  37. data/lib/perkins/assets/stylesheets/log.css.scss +115 -0
  38. data/lib/perkins/assets/stylesheets/styles.css.scss +199 -0
  39. data/lib/perkins/auth/github.rb +181 -0
  40. data/lib/perkins/build/data/env.rb +84 -0
  41. data/lib/perkins/build/data/var.rb +60 -0
  42. data/lib/perkins/build/data.rb +167 -0
  43. data/lib/perkins/build/script/bundler.rb +76 -0
  44. data/lib/perkins/build/script/go.rb +100 -0
  45. data/lib/perkins/build/script/helpers.rb +39 -0
  46. data/lib/perkins/build/script/jdk.rb +43 -0
  47. data/lib/perkins/build/script/ruby.rb +31 -0
  48. data/lib/perkins/build/script/rvm.rb +73 -0
  49. data/lib/perkins/build/script/stages.rb +28 -0
  50. data/lib/perkins/build/script/templates/footer.sh +3 -0
  51. data/lib/perkins/build/script/templates/header.sh +201 -0
  52. data/lib/perkins/build/script/templates/xcode.sh +21 -0
  53. data/lib/perkins/build/script.rb +167 -0
  54. data/lib/perkins/build/shell/dsl.rb +104 -0
  55. data/lib/perkins/build/shell/node.rb +121 -0
  56. data/lib/perkins/build/shell.rb +16 -0
  57. data/lib/perkins/build.rb +27 -0
  58. data/lib/perkins/build_report.rb +11 -0
  59. data/lib/perkins/cli.rb +42 -0
  60. data/lib/perkins/commit.rb +30 -0
  61. data/lib/perkins/dsl/app_proxy.rb +23 -0
  62. data/lib/perkins/dsl.rb +12 -0
  63. data/lib/perkins/listener.rb +38 -0
  64. data/lib/perkins/logger.rb +12 -0
  65. data/lib/perkins/notifier.rb +5 -0
  66. data/lib/perkins/repo.rb +145 -0
  67. data/lib/perkins/runner.rb +124 -0
  68. data/lib/perkins/server.rb +314 -0
  69. data/lib/perkins/thor_utils.rb +79 -0
  70. data/lib/perkins/version.rb +3 -0
  71. data/lib/perkins/views/401.haml +1 -0
  72. data/lib/perkins/views/builds.haml +46 -0
  73. data/lib/perkins/views/index.haml +6 -0
  74. data/lib/perkins/views/layout.haml +53 -0
  75. data/lib/perkins/views/menu.haml +18 -0
  76. data/lib/perkins/views/orgs.haml +101 -0
  77. data/lib/perkins/views/profile.haml +31 -0
  78. data/lib/perkins/views/readme.md +20 -0
  79. data/lib/perkins/views/repos/config.haml +72 -0
  80. data/lib/perkins/views/repos/github.haml +76 -0
  81. data/lib/perkins/views/repos/menu.haml +17 -0
  82. data/lib/perkins/views/repos/repo.haml +64 -0
  83. data/lib/perkins/views/repos/spinner.haml +3 -0
  84. data/lib/perkins/webhooks/github.rb +12 -0
  85. data/lib/perkins/worker.rb +33 -0
  86. data/lib/perkins.rb +36 -0
  87. data/perkins.gemspec +52 -0
  88. data/spec/fixtures/.travis.yml +8 -0
  89. data/spec/fixtures/config.yml +6 -0
  90. data/spec/lib/build/build_spec.rb +58 -0
  91. data/spec/lib/commit_spec.rb +6 -0
  92. data/spec/lib/dsl_spec.rb +17 -0
  93. data/spec/lib/listener_spec.rb +30 -0
  94. data/spec/lib/repo_spec.rb +110 -0
  95. data/spec/lib/runner_spec.rb +76 -0
  96. data/spec/lib/server_spec.rb +108 -0
  97. data/spec/spec_helper.rb +67 -0
  98. data/spec/support/auth.rb +30 -0
  99. data/spec/support/github_api.rb +177 -0
  100. metadata +503 -0
@@ -0,0 +1,145 @@
1
+ require "git"
2
+ module Perkins
3
+ class Repo < ActiveRecord::Base
4
+ attr_accessor :git
5
+ attr_accessor :new_commit, :runner
6
+
7
+ has_many :build_reports, class_name: 'Perkins::BuildReport'
8
+ serialize :github_data, ActiveSupport::HashWithIndifferentAccess
9
+
10
+ scope :from_github, ->{where(cached: true)}
11
+ scope :added, ->{where(cached: false)}
12
+
13
+ DEFAULT_DIR = "/tmp/"
14
+
15
+ def self.add_from_github(id)
16
+ github_repo = synced_records.find_by(gb_id: id.to_i)
17
+ store_from_github(github_repo)
18
+ end
19
+
20
+ def self.sync_github_repos(user)
21
+ user.api.repos.each do |repo|
22
+ self.sync_github_repo(repo)
23
+ end
24
+ end
25
+
26
+ def self.sync_github_repo(r)
27
+ return if Repo.where(gb_id: r[:id]).any?
28
+ repo = Repo.new
29
+ repo.github_data = r.to_attrs.with_indifferent_access
30
+ repo.cached = true
31
+ repo.url = r[:clone_url]
32
+ repo.name = r[:full_name]
33
+ repo.gb_id = r[:id]
34
+ repo.working_dir = DEFAULT_DIR
35
+ repo.save
36
+ end
37
+
38
+ def self.synced_records
39
+ self.from_github
40
+ end
41
+
42
+ def self.store_from_github(repo)
43
+ repo
44
+ repo.working_dir = DEFAULT_DIR #this should be configurable from app
45
+ repo.cached = false
46
+ repo.save
47
+ end
48
+
49
+ def self.initialize_from_store(opts)
50
+ repo = Repo.new
51
+ repo.url = opts["github_data"]["ssh_url"]
52
+ repo.name = opts["name"]
53
+ repo.github_data = opts["github_data"]
54
+ repo.gb_id = opts["github_data"]["id"]
55
+ repo.working_dir = DEFAULT_DIR #this should be configurable from app
56
+ repo
57
+ end
58
+
59
+ def load_git
60
+ clone_or_load
61
+ end
62
+
63
+ def clone_or_load
64
+ if exists?
65
+ open
66
+ else
67
+ ssh_url = self.github_data["ssh_url"]
68
+ g = Git.clone(ssh_url, name, :path => working_dir)
69
+ open
70
+ end
71
+ end
72
+
73
+ def open
74
+ self.git = Git.open(local_path) # :log => Logger.new(STDOUT)
75
+ build_runner_config(self.check_config_existence) if self.check_config_existence
76
+ end
77
+
78
+ def check_config_existence
79
+ config = self.git.chdir{
80
+ if File.exists?(".travis.yml")
81
+ config = Travis::Yaml.parse( File.open(".travis.yml").read )
82
+ else
83
+ config = Travis::Yaml.new
84
+ end
85
+ config
86
+ }
87
+ config
88
+ end
89
+
90
+ def exists?
91
+ File.exists?(local_path)
92
+ end
93
+
94
+ def local_path
95
+ self.working_dir + self.name.to_s
96
+ end
97
+
98
+ def branches
99
+ self.git.branches.map(&:name)
100
+ end
101
+
102
+ #http://docs.travis-ci.com/user/build-configuration/#The-Build-Matrix
103
+ def build_runner_config(config)
104
+ runner = Runner.new()
105
+ runner.config = config
106
+ runner.repo = self
107
+ self.branch = runner_branch
108
+ self.runner = runner
109
+ end
110
+
111
+ def runner_branch
112
+ case self.branch
113
+ when :all
114
+ self.branches
115
+ when nil
116
+ ["master"]
117
+ else
118
+ self.branches.include?(self.branch) ? [self.branch] : ["master"]
119
+ end
120
+ end
121
+
122
+ def add_commit(sha, branch)
123
+ if runner_branch.include?(branch)
124
+ @new_commit = Perkins::Commit.new(sha, self)
125
+ @new_commit.branch = branch
126
+ enqueue_commit(@new_commit)
127
+ else
128
+ puts "skipping commit from branch #{branch}"
129
+ end
130
+ end
131
+
132
+ def enqueue_commit(commit)
133
+ $redis.publish("commits", {id: self.id.to_s, name: self.name , sha: commit.sha, branch: commit.branch}.to_json)
134
+ end
135
+
136
+ def http_url
137
+ new_url = self.url.include?("http") ? self.url : self.url.gsub(":", "/")
138
+ new_url.gsub!("git@", "https://")
139
+ new_url.gsub!(".git", "")
140
+ new_url
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,124 @@
1
+
2
+ module Perkins
3
+ class Runner
4
+
5
+ attr_accessor :repo, :command, :config, :sha, :branch
6
+ attr_reader :build_time, :duration, :response, :status, :current_build
7
+
8
+ def exec(cmd)
9
+ result = run_script(cmd)
10
+ puts result
11
+ #result = `bash #{cmd} 2>&1`.chomp
12
+ #result = `go run #{ROOT_PATH + '/lib/perkins/build/runner.go'} -cmd="#{cmd}"`.chomp
13
+ process_status = $?
14
+
15
+ if successful_command?(process_status) || config_command_with_empty_value?(result,process_status)
16
+ @response = result
17
+ @status = true
18
+ return result
19
+ else
20
+ @response = result
21
+ @status = false
22
+ #raise "command failed"
23
+ end
24
+ end
25
+
26
+
27
+ def run_script(source)
28
+ script = File.expand_path(
29
+ "~/.perkins/.build/#{repo.name}/travis-build-#{sha}" #<< stages.join('-')
30
+ )
31
+ FileUtils.mkdir_p(File.dirname(script))
32
+ File.open(script, 'w') { |f| f.write(source) }
33
+ FileUtils.chmod(0755, script)
34
+ Bundler.with_clean_env{
35
+ `bash #{script} 2>&1`.chomp
36
+ }
37
+ end
38
+
39
+ def run(sha)
40
+
41
+ self.sha = sha
42
+ start_build
43
+ script = Perkins::Build::script(config, repo)
44
+ sh = script.compile
45
+ repo.git.chdir do
46
+ git_update(sha)
47
+ set_build_stats do
48
+ puts "perform build"
49
+ self.exec(sh)
50
+ end
51
+ end
52
+ store_report
53
+ stop_build
54
+ end
55
+
56
+ def start_build
57
+ #self.update_attributes(status: true)
58
+ @running = true #self.status
59
+ #$redis.set("#{repo.name}:running", true)
60
+ end
61
+
62
+ def stop_build
63
+ #self.update_attributes(status: false)
64
+ @running = false #self.status
65
+ #redis.set("#{repo.name}:running", false)
66
+ end
67
+
68
+ def set_build_stats(&block)
69
+ up = Time.now
70
+ #call the command itself
71
+ block.call
72
+ down = Time.now
73
+ @build_time = down
74
+ @duration = down - up
75
+ end
76
+
77
+ def running?
78
+ #repo.running?
79
+ @running
80
+ end
81
+
82
+ def successful_command?(process_status)
83
+ process_status.exitstatus.to_i == 0
84
+ end
85
+
86
+ def config_command_with_empty_value?(result, process_status)
87
+ process_status.exitstatus.to_i == 1 && result.empty?
88
+ end
89
+
90
+ def working_dir
91
+ repo.git.dir.path
92
+ end
93
+
94
+ def git_update(branch)
95
+ #`git fetch origin && git reset --hard #{sha}`
96
+ puts "git fetch & reset to #{sha}"
97
+ repo.git.fetch()
98
+ repo.git.reset_hard(sha)
99
+ end
100
+
101
+ def to_report
102
+ {id: SecureRandom.hex ,
103
+ build_time: self.build_time ,
104
+ duration: self.duration,
105
+ response: self.response,
106
+ status: self.status,
107
+ sha: self.sha,
108
+ branch: self.branch}
109
+ end
110
+
111
+ def store_report
112
+ r = Perkins::BuildReport.new(self.to_report)
113
+ #return if report.keys.detect{|o| report[o].to_s == "" }
114
+ #builds_to_save = get_builds << self.to_report
115
+ #$redis.set("#{repo.name}:builds", builds_to_save.to_json )
116
+ repo.build_reports << r
117
+ end
118
+
119
+ def get_builds
120
+ repo.build_reports
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,314 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/activerecord'
3
+ #require 'sinatra/reloader' #if ENV['RACK_ENV'] == "development"
4
+ require 'pry'
5
+ require 'json'
6
+ require 'haml'
7
+ require 'sprockets'
8
+ require 'coffee_script'
9
+ require "sass"
10
+ require "rdiscount"
11
+ require "perkins/auth/github"
12
+
13
+ module Perkins
14
+
15
+ class Assets < Sinatra::Base
16
+
17
+ configure do
18
+ set :assets, (Sprockets::Environment.new { |env|
19
+ env.append_path(settings.root + "/assets/images")
20
+ env.append_path(settings.root + "/assets/javascripts")
21
+ env.append_path(settings.root + "/assets/stylesheets")
22
+ # compress everything in production
23
+ if ENV["RACK_ENV"] == "production"
24
+ env.js_compressor = YUI::JavaScriptCompressor.new
25
+ env.css_compressor = YUI::CssCompressor.new
26
+ end
27
+ })
28
+ end
29
+
30
+ get "/assets/app.js" do
31
+ content_type("application/javascript")
32
+ settings.assets["app.js"]
33
+ end
34
+
35
+ get "/assets/app.css" do
36
+ content_type("text/css")
37
+ settings.assets["app.css"]
38
+ end
39
+
40
+ %w{jpg png}.each do |format|
41
+ get "/assets/:image.#{format}" do |image|
42
+ content_type("image/#{format}")
43
+ settings.assets["#{image}.#{format}"]
44
+ end
45
+ end
46
+
47
+ get "/assets/:image.svg" do |image|
48
+ content_type("image/svg+xml")
49
+ settings.assets["#{image}.svg"]
50
+ end
51
+
52
+ end
53
+
54
+ class Server < Sinatra::Application
55
+
56
+ attr_reader :app
57
+ set server: 'thin', connections: []
58
+ set :sessions, true
59
+ set :foo, 'bar'
60
+ enable :sessions
61
+ #set :environment, :production
62
+ set :session_secret, '*&(^B234'
63
+
64
+ use Perkins::Assets
65
+
66
+ register Perkins::Auth::Github
67
+
68
+ get '/' do
69
+ authenticate!
70
+ haml :index , locals: default_data
71
+ end
72
+
73
+ get '/config' do
74
+ settings.perkins_application.to_json
75
+ end
76
+
77
+ get '/repos/sync' do
78
+ @github_repos = build_data
79
+ end
80
+
81
+ post '/repos/receiver' do
82
+ #NEEDS VALIDATIONS
83
+ payload = JSON.parse request.body.read
84
+ repo = Perkins::Repo.added.find_by(gb_id: payload["repository"]["id"])
85
+ repo.load_git
86
+
87
+ pushed_branch = payload["ref"].split('/').last
88
+ repo.add_commit(payload["after"], pushed_branch)
89
+ #binding.pry
90
+ "received post #{payload}"
91
+ end
92
+
93
+ get '/repos/add/:id' do
94
+ #ADD REPO HERE
95
+ repo = github_user.api.repo(params[:id].to_i)
96
+ Repo.sync_github_repo(repo)
97
+ r = Repo.add_from_github(params[:id])
98
+ r.to_json
99
+ redirect "/me"
100
+ end
101
+
102
+ get '/repos/:login/:name' do
103
+ repo = find_repo(params)
104
+ record = repo.build_reports.last
105
+ haml :"repos/repo" , locals: {repo: repo, build: record }
106
+ end
107
+
108
+ get '/repos/:login/:name/builds' do
109
+ repo = find_repo(params)
110
+ haml :builds , locals: {repo: repo, builds: repo.build_reports }
111
+ end
112
+
113
+ get '/repos/:login/:name/builds/:build_id' do
114
+ repo = find_repo(params)
115
+ record = repo.build_reports.find(params[:build_id])
116
+
117
+ haml :"repos/repo" , locals: {repo: repo, build: record }
118
+ end
119
+
120
+ get '/repos/:login/:name/run_commit' do
121
+ repo = find_repo(params)
122
+ sha = repo.git.log.map(&:sha).first
123
+ repo.add_commit(sha, "master")
124
+ redirect "/repos/#{repo.name}"
125
+ end
126
+
127
+ get '/repos/:login/:name/config' do
128
+ repo = find_repo(params)
129
+ #find by id
130
+ hook = github_user.api.hooks("#{repo.name}").detect{|o| o[:name] == "web"}
131
+
132
+ haml :"repos/config" , locals: {repo: repo, hook: hook}
133
+ end
134
+
135
+ post '/repos/:login/:name/add_hook' do
136
+ repo = find_repo(params)
137
+ hook = github_user.api.hooks("#{repo.name}").detect{|o| o[:name] == "web"}
138
+ if hook.present?
139
+ res = github_user.api.edit_hook(
140
+ repo.name,
141
+ hook["id"],
142
+ 'web',
143
+ {:url => params[:webhook_url], :content_type => 'json'},
144
+ {:active => true}
145
+ )
146
+ else
147
+ res = github_user.api.create_hook(
148
+ repo.name,
149
+ 'web',
150
+ { :url => params[:webhook_url], :content_type => 'json'},
151
+ { :events => ['push'], :active => true}
152
+ )
153
+ end
154
+ redirect "/repos/#{repo.name}/config"
155
+ end
156
+
157
+ get '/repos/:login/:name/builds/:build_id/restart' do
158
+ repo = find_repo(params)
159
+ record = repo.build_reports.find(params[:build_id])
160
+ repo.add_commit(record['sha'], record['branch'])
161
+ redirect "/repos/#{repo.name}"
162
+ end
163
+
164
+ get '/stream', provides: 'text/event-stream' do
165
+ stream :keep_open do |out|
166
+ settings.connections << out
167
+ out.callback { settings.connections.delete(out) }
168
+ end
169
+ end
170
+
171
+ post '/sse' do
172
+ settings.connections.each { |out| out << "data: #{params[:msg]}\n\n" }
173
+ 204 # response without entity body
174
+ end
175
+
176
+ get '/me' do
177
+ authenticate!
178
+ #binding.pry
179
+ #repos = github_repos
180
+ haml :profile, locals: { user: github_user.api }
181
+ #haml :"repos/github", locals: {repos: repos}
182
+ #"Hello There, #{github_user.name}! You have access to the #{params['id']} organization."
183
+ end
184
+
185
+ get '/myrepos' do
186
+ authenticate!
187
+ repos = github_user.api.repos
188
+ haml :"repos/github", locals: { user: github_user.api, repos: repos }
189
+ end
190
+
191
+ get '/orgs/:id' do
192
+ github_organization_authenticate!(params['id'])
193
+ #"Hello There, #{github_user.name}! You have access to the #{params['id']} organization."
194
+ org = github_user.api.organization(params['id'])
195
+ orgs = github_user.api.orgs
196
+ repos = github_user.api.org_repos(params["id"])
197
+ haml :orgs, locals: { org: org, orgs: orgs, repos: repos }
198
+ end
199
+
200
+ get '/publicized_orgs/:id' do
201
+ github_publicized_organization_authenticate!(params['id'])
202
+ "Hello There, #{github_user.name}! You are publicly a member of the #{params['id']} organization."
203
+ end
204
+
205
+ get '/teams/:id' do
206
+ github_team_authenticate!(params['id'])
207
+ "Hello There, #{github_user.name}! You have access to the #{params['id']} team."
208
+ end
209
+
210
+ get '/logout' do
211
+ logout!
212
+ redirect 'https://github.com'
213
+ end
214
+
215
+ def initialize(*args)
216
+ super
217
+ puts "Start Perkins server"
218
+ #check_project
219
+ #@app = Perkin.new(options.project_path)
220
+ end
221
+
222
+ def github_repos
223
+ @github_repos ||= persisted_repos.any? ? persisted_repos : build_data
224
+ end
225
+
226
+ def build_data
227
+ Repo.sync_github_repos(github_user)
228
+ redirect "/me"
229
+ end
230
+
231
+ def persisted_repos
232
+ Repo.synced_records
233
+ end
234
+
235
+ def find_repo(params)
236
+ id = "#{params[:login]}/#{params[:name]}"
237
+ repo = Repo.find_by(name: id)
238
+ repo.load_git
239
+ repo
240
+ end
241
+
242
+ def self.start(config_file, options)
243
+
244
+ options = options.dup
245
+ ENV['RACK_ENV'] = options.delete("e")
246
+
247
+ app = eval(File.open(config_file).read)
248
+ self.start_listener(app)
249
+
250
+ set :perkins_application, app
251
+ set :github_options, {
252
+ :scopes => "admin:repo_hook,repo,user:email",
253
+ :secret => app.github_client_secret,
254
+ :client_id => app.github_client_id,
255
+ }
256
+
257
+ register Sinatra::ActiveRecordExtension
258
+
259
+ configure :development do
260
+ #register Sinatra::Reloader
261
+ #set :database, 'sqlite:///db/development.sqlite3'
262
+ end
263
+
264
+ configure :production do
265
+ #set :database, 'sqlite:///db/productions.sqlite3'
266
+ end
267
+
268
+ configure :test do
269
+ #set :database, 'sqlite:///db/test.sqlite3'
270
+ end
271
+
272
+ Perkins::Server.run! options
273
+ end
274
+
275
+ def self.start_listener(app_config)
276
+ fork do
277
+ listener = ::Perkins::Listener.new
278
+ listener.app = app_config
279
+ listener.run!
280
+ end
281
+ end
282
+
283
+ ##use in rack
284
+ ##And a corresponding config.ru:
285
+ #require './app'
286
+ #run Sinatra::Application
287
+ def default_data
288
+ {author: "miguel michelson", year: Time.now.year, app: @app}
289
+ end
290
+
291
+ helpers do
292
+ def status_label(status)
293
+ label = status == true ? "success" : "default"
294
+ msg = status ? "passed" : "error"
295
+ "<span class='label label-#{label}'>build | #{msg}</span>"
296
+ end
297
+
298
+ def status_class(status)
299
+ status ? "glyphicon glyphicon-ok" : "glyphicon glyphicon-remove"
300
+ end
301
+
302
+
303
+ def avatar_url(email, size)
304
+ gravatar_id = Digest::MD5.hexdigest(email.downcase)
305
+ "http://gravatar.com/avatar/#{gravatar_id}.png?s=#{size}"
306
+ end
307
+
308
+ def commit_url(repo, sha)
309
+ "#{repo.http_url}/commit/#{sha}"
310
+ end
311
+ end
312
+ end
313
+
314
+ end
@@ -0,0 +1,79 @@
1
+ module Perkins
2
+ module ThorUtils
3
+ include Thor::Actions
4
+
5
+ def self.source_root
6
+ File.dirname(__FILE__)
7
+ end
8
+
9
+ def create_new_file(name, file=nil)
10
+ log "Creating #{name}"
11
+ contents = file.nil? ? '' : File.read(file)
12
+ unless File.file?(location.join(name))
13
+ File.open(location.join(name), 'w') { |f| f.write(contents) }
14
+ end
15
+ end
16
+
17
+ def remove_files(*files)
18
+ files.each do |file|
19
+ log "Removing #{file} file."
20
+ FileUtils.rm(location.join(file))
21
+ end
22
+ end
23
+
24
+ def touch(*filenames)
25
+ filenames.each do |filename|
26
+ log "Creating #{filename} file."
27
+ FileUtils.touch(location.join(filename))
28
+ end
29
+ end
30
+
31
+ def create_directories(*dirs)
32
+ dirs.each do |dir|
33
+ log "Creating the #{dir} directory."
34
+ FileUtils.mkdir_p(location.join(dir))
35
+ end
36
+ end
37
+
38
+ def remove_directories(*names)
39
+ names.each do |name|
40
+ log "Removing #{name} directory."
41
+ FileUtils.rm_rf(location.join(name))
42
+ end
43
+ end
44
+
45
+ def create_with_template(name, template_location, contents={})
46
+ template = templates("#{template_location}.erb")
47
+ eruby = Erubis::Eruby.new(File.read(template))
48
+ File.open(location.join(name.gsub(/^\//, '')), 'w') { |f| f.write(eruby.result(contents))}
49
+ end
50
+
51
+ def templates(path)
52
+ ::Perkins.root.join('pullentity-client/templates').join(path)
53
+ end
54
+
55
+ def log(msg)
56
+ ::Perkins::Logger.report(msg)
57
+ end
58
+
59
+ def error(msg)
60
+ ::Perkins::Logger.error(msg)
61
+ end
62
+
63
+ def base_location
64
+ @location ||= Pathname.new(Dir.pwd)
65
+ end
66
+
67
+ alias_method :location, :base_location
68
+
69
+ def underscore(string)
70
+ string.gsub(/::/, '/').
71
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
72
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
73
+ tr("-", "_").
74
+ downcase
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,3 @@
1
+ module Perkins
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1 @@
1
+ 401
@@ -0,0 +1,46 @@
1
+
2
+
3
+ = haml :"repos/menu" , locals:{ repo: repo, active: "builds", build: nil}
4
+
5
+ .title
6
+ %h2#repo-name= repo.name
7
+ %h4= repo.url
8
+
9
+ .panel.panel-default
10
+ .panel-heading Builds
11
+ .panel-body
12
+
13
+ %table.table
14
+ %thead
15
+ %tr
16
+ %th Build
17
+ %th Message
18
+ %th Commit
19
+ %th Duration
20
+ %th Finished
21
+ %tbody
22
+
23
+ - builds.each do |build|
24
+
25
+ %tr.build-row
26
+ %td
27
+ %a{href: "/repos/#{repo.name}/builds/#{build.id}"}
28
+ %span{class: status_class(build['status']) }
29
+ = build['_id'].to_s[0..7]
30
+
31
+ %td
32
+ = build.commit.message.split("\n")[0]
33
+ %td
34
+ %a{href: commit_url(repo, build.commit.sha), target: :blank}
35
+ #{build.commit.sha[0..7]}
36
+ - if build.branch.present?
37
+ %strong
38
+ = "(#{build.branch})"
39
+
40
+ %td
41
+ = "#{build.duration.to_i.round(2)} secs"
42
+ %td
43
+ %abbr.timeago{title:build.build_time}
44
+
45
+
46
+