mattmatt-cijoe 0.1.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.
@@ -0,0 +1,49 @@
1
+ require 'yaml'
2
+
3
+ class CIJoe
4
+ class Build < Struct.new(:user, :project, :started_at, :finished_at, :sha, :status, :output, :pid)
5
+ def initialize(*args)
6
+ super
7
+ self.started_at ||= Time.now
8
+ end
9
+
10
+ def status
11
+ return super if started_at && finished_at
12
+ :building
13
+ end
14
+
15
+ def failed?
16
+ status == :failed
17
+ end
18
+
19
+ def worked?
20
+ status == :worked
21
+ end
22
+
23
+ def short_sha
24
+ sha[0,7] if sha
25
+ end
26
+
27
+ def clean_output
28
+ output.gsub(/\e\[.+?m/, '').strip
29
+ end
30
+
31
+ def commit
32
+ return if sha.nil?
33
+ @commit ||= Commit.new(sha, user, project)
34
+ end
35
+
36
+ def dump(file)
37
+ config = [user, project, started_at, finished_at, sha, status, output, pid]
38
+ data = YAML.dump(config)
39
+ File.open(file, 'wb') { |io| io.write(data) }
40
+ end
41
+
42
+ def self.load(file)
43
+ if File.exist?(file)
44
+ config = YAML.load(File.read(file))
45
+ new *config
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ class CIJoe
2
+ class Commit < Struct.new(:sha, :user, :project)
3
+ def url
4
+ "http://github.com/#{user}/#{project}/commit/#{sha}"
5
+ end
6
+
7
+ def author
8
+ raw_commit_lines.grep(/Author:/).first.split(':', 2)[-1]
9
+ end
10
+
11
+ def committed_at
12
+ raw_commit_lines.grep(/Date:/).first.split(':', 2)[-1]
13
+ end
14
+
15
+ def message
16
+ raw_commit.split("\n\n", 3)[1].to_s.strip
17
+ end
18
+
19
+ def raw_commit
20
+ @raw_commit ||= `git show #{sha}`.chomp
21
+ end
22
+
23
+ def raw_commit_lines
24
+ @raw_commit_lines ||= raw_commit.split("\n")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ class CIJoe
2
+ class Config
3
+ def self.method_missing(command, *args)
4
+ new(command)
5
+ end
6
+
7
+ def initialize(command, parent = nil)
8
+ @command = command
9
+ @parent = parent
10
+ end
11
+
12
+ def method_missing(command, *args)
13
+ Config.new(command, self)
14
+ end
15
+
16
+ def to_s
17
+ `git config #{config_string}`.chomp
18
+ end
19
+
20
+ def config_string
21
+ @parent ? "#{@parent.config_string}.#{@command}" : @command
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/../mmmail'
2
+
3
+ class CIJoe
4
+ module Email
5
+ def self.activate
6
+ if valid_config?
7
+ CIJoe::Build.class_eval do
8
+ include CIJoe::Email
9
+ end
10
+
11
+ puts "Loaded Email notifier"
12
+ else
13
+ puts "Can't load Email notifier."
14
+ puts "Please add the following to your project's .git/config:"
15
+ puts "[email]"
16
+ puts "\tto = build@example.com"
17
+ puts "\tuser = horst"
18
+ puts "\tpass = passw0rd"
19
+ puts "\thost = mail.example.com"
20
+ puts "\ttype = login"
21
+ end
22
+ end
23
+
24
+ def self.config
25
+ @config ||= {
26
+ :to => Config.email.to.to_s,
27
+ :user => Config.email.user.to_s,
28
+ :pass => Config.email.pass.to_s,
29
+ :host => Config.email.host.to_s,
30
+ :type => Config.email.type || 'plain'
31
+ }
32
+ end
33
+
34
+ def self.valid_config?
35
+ %w( host user pass to ).all? do |key|
36
+ !config[key.intern].empty?
37
+ end
38
+ end
39
+
40
+ def notify
41
+ options = {
42
+ :to => Email.config[:to],
43
+ :from => Email.config[:to],
44
+ :subject => "(#{commit.project}) Build failed",
45
+ :body => mail_body
46
+ }
47
+ MmMail.send(options, mail_config) if failed?
48
+ end
49
+
50
+ def mail_config
51
+ config = MmMail::Config.new
52
+ config.auth_user = Email.config[:user]
53
+ config.auth_pass = Email.config[:pass]
54
+ config.auth_type = Email.config[:type]
55
+ config
56
+ end
57
+
58
+ def mail_body
59
+ "The commit '#{commit.message}' (#{commit.url}) by #{commit.author} caused the build to fail."
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,212 @@
1
+ /*****************************************************************************/
2
+ /*
3
+ /* Common
4
+ /*
5
+ /*****************************************************************************/
6
+
7
+ /* Global Reset */
8
+
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ html, body {
15
+ height: 100%;
16
+ }
17
+
18
+ body {
19
+ background-color: white;
20
+ font: 13.34px helvetica, arial, clean, sans-serif;
21
+ *font-size: small;
22
+ text-align: center;
23
+ }
24
+
25
+ h1, h2, h3, h4, h5, h6 {
26
+ font-size: 100%;
27
+ }
28
+
29
+ h1 {
30
+ margin-bottom: 1em;
31
+ }
32
+
33
+ h1 a {
34
+ text-decoration: none;
35
+ color: #000;
36
+ }
37
+
38
+ .failed, .color31 {
39
+ color: red !important;
40
+ }
41
+
42
+ .worked, .color32 {
43
+ color: green !important;
44
+ }
45
+
46
+ .errored, .color33 {
47
+ color: yellow !important;
48
+ }
49
+
50
+ p {
51
+ margin: 1em 0;
52
+ }
53
+
54
+ a {
55
+ color: #00a;
56
+ }
57
+
58
+ a:hover {
59
+ color: black;
60
+ }
61
+
62
+ a:visited {
63
+ color: #a0a;
64
+ }
65
+
66
+ table {
67
+ font-size: inherit;
68
+ font: 100%;
69
+ }
70
+
71
+ /*****************************************************************************/
72
+ /*
73
+ /* Home
74
+ /*
75
+ /*****************************************************************************/
76
+
77
+ ul.posts {
78
+ list-style-type: none;
79
+ margin-bottom: 2em;
80
+ }
81
+
82
+ ul.posts li {
83
+ line-height: 1.75em;
84
+ }
85
+
86
+ ul.posts .date {
87
+ color: #aaa;
88
+ font-family: Monaco, "Courier New", monospace;
89
+ font-size: 80%;
90
+ }
91
+
92
+ /*****************************************************************************/
93
+ /*
94
+ /* Site
95
+ /*
96
+ /*****************************************************************************/
97
+
98
+ .site {
99
+ font-size: 110%;
100
+ text-align: justify;
101
+ width: 40em;
102
+ margin: 3em auto 2em auto;
103
+ line-height: 1.5em;
104
+ }
105
+
106
+ .title {
107
+ color: #a00;
108
+ font-weight: bold;
109
+ margin-bottom: 2em;
110
+ }
111
+
112
+ .site .title a {
113
+ color: #a00;
114
+ text-decoration: none;
115
+ }
116
+
117
+ .site .title a:hover {
118
+ color: black;
119
+ }
120
+
121
+ .site .title .extra {
122
+ color: #aaa;
123
+ text-decoration: none;
124
+ margin-left: 1em;
125
+ font-size: 0.9em;
126
+ }
127
+
128
+ .site .title a.extra:hover {
129
+ color: black;
130
+ }
131
+
132
+ .site .meta {
133
+ color: #aaa;
134
+ }
135
+
136
+ .site .footer {
137
+ font-size: 80%;
138
+ color: #666;
139
+ border-top: 4px solid #eee;
140
+ margin-top: 2em;
141
+ overflow: hidden;
142
+ }
143
+
144
+ .site .footer .contact {
145
+ float: left;
146
+ margin-right: 3em;
147
+ }
148
+
149
+ .site .footer .contact a {
150
+ color: #8085C1;
151
+ }
152
+
153
+ .site .footer .rss {
154
+ margin-top: 1.1em;
155
+ margin-right: -.2em;
156
+ float: right;
157
+ }
158
+
159
+ .site .footer .rss img {
160
+ border: 0;
161
+ }
162
+
163
+ /*****************************************************************************/
164
+ /*
165
+ /* Posts
166
+ /*
167
+ /*****************************************************************************/
168
+
169
+ #post {
170
+
171
+ }
172
+
173
+ /* standard */
174
+
175
+ #post pre {
176
+ border: 1px solid #ddd;
177
+ background-color: #eef;
178
+ padding: 0 .4em;
179
+ }
180
+
181
+ #post ul,
182
+ #post ol {
183
+ margin-left: 1.25em;
184
+ }
185
+
186
+ #post code {
187
+ border: 1px solid #ddd;
188
+ background-color: #eef;
189
+ font-size: 95%;
190
+ padding: 0 .2em;
191
+ }
192
+
193
+ #post pre code {
194
+ border: none;
195
+ }
196
+
197
+ /* terminal */
198
+
199
+ pre.terminal {
200
+ border: 1px solid black;
201
+ background-color: #333;
202
+ color: white;
203
+ padding: 5px;
204
+ overflow: auto;
205
+ word-wrap: break-word;
206
+ }
207
+
208
+ pre.terminal code {
209
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
210
+ background-color: #333;
211
+ }
212
+
@@ -0,0 +1,77 @@
1
+ require 'sinatra/base'
2
+ require 'erb'
3
+
4
+ class CIJoe
5
+ class Server < Sinatra::Base
6
+ dir = File.dirname(File.expand_path(__FILE__))
7
+
8
+ set :views, "#{dir}/views"
9
+ set :public, "#{dir}/public"
10
+ set :static, true
11
+ set :lock, true
12
+
13
+ before { @joe.restore }
14
+
15
+ get '/?' do
16
+ erb(:template, {}, :joe => @joe)
17
+ end
18
+
19
+ post '/?' do
20
+ payload = params[:payload].to_s
21
+ if payload.empty? || payload.include?(@joe.git_branch)
22
+ @joe.build
23
+ end
24
+ redirect request.path
25
+ end
26
+
27
+ user, pass = Config.cijoe.user.to_s, Config.cijoe.pass.to_s
28
+ if user != '' && pass != ''
29
+ use Rack::Auth::Basic do |username, password|
30
+ [ username, password ] == [ user, pass ]
31
+ end
32
+ puts "Using HTTP basic auth"
33
+ end
34
+
35
+ helpers do
36
+ include Rack::Utils
37
+ alias_method :h, :escape_html
38
+
39
+ # thanks integrity!
40
+ def ansi_color_codes(string)
41
+ string.gsub("\e[0m", '</span>').
42
+ gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
43
+ end
44
+
45
+ def pretty_time(time)
46
+ time.strftime("%Y-%m-%d %H:%M")
47
+ end
48
+
49
+ def cijoe_root
50
+ root = request.path
51
+ root = "" if root == "/"
52
+ root
53
+ end
54
+ end
55
+
56
+ def initialize(*args)
57
+ super
58
+ check_project
59
+ @joe = CIJoe.new(options.project_path)
60
+
61
+ CIJoe::Email.activate
62
+ end
63
+
64
+ def self.start(host, port, project_path)
65
+ set :project_path, project_path
66
+ CIJoe::Server.run! :host => host, :port => port
67
+ end
68
+
69
+ def check_project
70
+ if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
71
+ puts "Whoops! I need the path to a Git repo."
72
+ puts " $ git clone git@github.com:username/project.git project"
73
+ abort " $ cijoe project"
74
+ end
75
+ end
76
+ end
77
+ end