defunkt-integrity 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ module Integrity
2
+ class Notifier
3
+ class Base
4
+ def self.notify_of_build(build, config)
5
+ new(build, config).deliver!
6
+ end
7
+
8
+ def self.to_haml
9
+ raise NoMethodError, "you need to implement this method in your notifier"
10
+ end
11
+
12
+ attr_reader :build
13
+
14
+ def initialize(build, config)
15
+ @build = build
16
+ @config = config
17
+ end
18
+
19
+ def deliver!
20
+ raise NoMethodError, "you need to implement this method in your notifier"
21
+ end
22
+
23
+ def short_message
24
+ "Build #{build.short_commit_identifier} #{build.successful? ? "was successful" : "failed"}"
25
+ end
26
+
27
+ def full_message
28
+ <<-EOM
29
+ "Build #{build.commit_identifier} #{build.successful? ? "was successful" : "failed"}"
30
+
31
+ Commit Message: #{build.commit_message}
32
+ Commit Date: #{build.commited_at}
33
+ Commit Author: #{build.commit_author.name}
34
+
35
+ Link: #{build_url}
36
+
37
+ Build Output:
38
+
39
+ #{stripped_build_output}
40
+ EOM
41
+ end
42
+
43
+ def build_url
44
+ raise if Integrity.config[:base_uri].nil?
45
+ Integrity.config[:base_uri] / build.project.permalink / "builds" / build.commit_identifier
46
+ end
47
+
48
+ private
49
+
50
+ def stripped_build_output
51
+ build.output.gsub("\e[0m", '').gsub(/\e\[3[1-7]m/, '')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,87 @@
1
+ module Integrity
2
+ class Project
3
+ include DataMapper::Resource
4
+
5
+ property :id, Integer, :serial => true
6
+ property :name, String, :nullable => false
7
+ property :permalink, String
8
+ property :uri, URI, :nullable => false, :length => 255
9
+ property :branch, String, :nullable => false, :default => "master"
10
+ property :command, String, :nullable => false, :length => 255, :default => "rake"
11
+ property :public, Boolean, :default => true
12
+ property :building, Boolean, :default => false
13
+ property :created_at, DateTime
14
+ property :updated_at, DateTime
15
+
16
+ has n, :builds, :class_name => "Integrity::Build"
17
+ has n, :notifiers, :class_name => "Integrity::Notifier"
18
+
19
+ before :save, :set_permalink
20
+ before :destroy, :delete_code
21
+
22
+ validates_is_unique :name
23
+
24
+ def build(commit_identifier="HEAD")
25
+ return if building?
26
+ update_attributes(:building => true)
27
+ Thread.new(self) do |project|
28
+ begin
29
+ Builder.new(project).build(commit_identifier)
30
+ ensure
31
+ project.update_attributes(:building => false)
32
+ project.send_notifications
33
+ end
34
+ end
35
+ end
36
+
37
+ def last_build
38
+ builds.last
39
+ end
40
+
41
+ def previous_builds
42
+ return [] if builds.count <= 1
43
+ builds.all(:order => [:created_at.desc], :offset => 1, :limit => builds.count - 1)
44
+ end
45
+
46
+ def status
47
+ last_build && last_build.status
48
+ end
49
+
50
+ def public=(flag)
51
+ attribute_set(:public, !!flag)
52
+ end
53
+
54
+ def config_for(notifier)
55
+ notifier = notifiers.first(:name => notifier.to_s.split(/::/).last)
56
+ notifier.blank? ? {} : notifier.config
57
+ end
58
+
59
+ def notifies?(notifier)
60
+ !notifiers.first(:name => notifier.to_s.split(/::/).last).blank?
61
+ end
62
+
63
+ def enable_notifiers(*args)
64
+ Notifier.enable_notifiers(id, *args)
65
+ end
66
+
67
+ def send_notifications
68
+ notifiers.each do |notifier|
69
+ notifier.notify_of_build last_build
70
+ end
71
+ end
72
+
73
+ private
74
+ def set_permalink
75
+ self.permalink = (name || "").downcase.
76
+ gsub(/'s/, "s").
77
+ gsub(/&/, "and").
78
+ gsub(/[^a-z0-9]+/, "-").
79
+ gsub(/-*$/, "")
80
+ end
81
+
82
+ def delete_code
83
+ builds.destroy!
84
+ Builder.new(self).delete_code
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,22 @@
1
+ module Integrity
2
+ module SCM
3
+ class SCMUnknownError < StandardError; end
4
+
5
+ def self.new(uri, *args)
6
+ scm_class_for(uri).new(uri, *args)
7
+ end
8
+
9
+ def self.working_tree_path(uri)
10
+ scm_class_for(uri).working_tree_path(uri)
11
+ end
12
+
13
+ private
14
+
15
+ def self.scm_class_for(string)
16
+ case string.to_s
17
+ when /\.git\/?/ then Git
18
+ else raise SCMUnknownError, "could not find any SCM based on string '#{string}'"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ module Integrity
2
+ module SCM
3
+ class Git
4
+ require File.dirname(__FILE__) / "git/uri"
5
+
6
+ attr_reader :uri, :branch, :working_directory
7
+
8
+ def self.working_tree_path(uri)
9
+ Git::URI.new(uri).working_tree_path
10
+ end
11
+
12
+ def initialize(uri, branch, working_directory)
13
+ @uri = uri.to_s
14
+ @branch = branch.to_s
15
+ @working_directory = working_directory
16
+ end
17
+
18
+ def with_revision(revision)
19
+ fetch_code
20
+ checkout(revision)
21
+ yield
22
+ end
23
+
24
+ def commit_identifier(sha1)
25
+ `cd #{working_directory} && git show -s --pretty=format:%H #{sha1}`.chomp
26
+ end
27
+
28
+ def commit_metadata(sha1)
29
+ format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:date: %ci%n)
30
+ YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{sha1}`)
31
+ end
32
+
33
+ private
34
+
35
+ def fetch_code
36
+ clone unless cloned?
37
+ checkout unless on_branch?
38
+ pull
39
+ end
40
+
41
+ def clone
42
+ `git clone #{uri} #{working_directory}`
43
+ end
44
+
45
+ def checkout(treeish=nil)
46
+ strategy = case
47
+ when treeish then treeish
48
+ when local_branches.include?(branch) then branch
49
+ else "-b #{branch} origin/#{branch}"
50
+ end
51
+
52
+ `cd #{working_directory} && git checkout #{strategy}`
53
+ end
54
+
55
+ def pull
56
+ `cd #{working_directory} && git pull`
57
+ end
58
+
59
+ def local_branches
60
+ `cd #{working_directory} && git branch`.split("\n").map {|b| b.delete("*").strip }
61
+ end
62
+
63
+ def cloned?
64
+ File.directory?(working_directory / ".git")
65
+ end
66
+
67
+ def on_branch?
68
+ File.basename(`cd #{working_directory} && git symbolic-ref HEAD`).chomp == branch
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,57 @@
1
+ module Integrity
2
+ module SCM
3
+ class Git
4
+ # From the git-pull man page:
5
+ #
6
+ # GIT URLS
7
+ # One of the following notations can be used to name the remote repository:
8
+ #
9
+ # rsync://host.xz/path/to/repo.git/
10
+ # http://host.xz/path/to/repo.git/
11
+ # git://host.xz/~user/path/to/repo.git/
12
+ # ssh://[user@]host.xz[:port]/path/to/repo.git/
13
+ # ssh://[user@]host.xz/path/to/repo.git/
14
+ # ssh://[user@]host.xz/~user/path/to/repo.git/
15
+ # ssh://[user@]host.xz/~/path/to/repo.git
16
+ #
17
+ # SSH is the default transport protocol over the network. You can optionally
18
+ # specify which user to log-in as, and an alternate, scp-like syntax is also
19
+ # supported
20
+ #
21
+ # Both syntaxes support username expansion, as does the native git protocol,
22
+ # but only the former supports port specification. The following three are
23
+ # identical to the last three above, respectively:
24
+ #
25
+ # [user@]host.xz:/path/to/repo.git/
26
+ # [user@]host.xz:~user/path/to/repo.git/
27
+ # [user@]host.xz:path/to/repo.git
28
+ #
29
+ class URI
30
+ def initialize(uri_string)
31
+ @uri = Addressable::URI.parse(uri_string)
32
+ end
33
+
34
+ def working_tree_path
35
+ strip_extension(path).gsub("/", "-")
36
+ end
37
+
38
+ private
39
+
40
+ def strip_extension(string)
41
+ uri = Pathname.new(string)
42
+ if uri.extname.any?
43
+ uri = Pathname.new(string)
44
+ string.gsub(Regexp.new("#{uri.extname}\/?"), "")
45
+ else
46
+ string
47
+ end
48
+ end
49
+
50
+ def path
51
+ path = @uri.path
52
+ path.gsub(/\~[a-zA-Z0-9]*\//, "").gsub(/^\//, "")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Integrity
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,82 @@
1
+ /* --------------------------------------------------------------
2
+
3
+ buttons.css
4
+ * Gives you some great CSS-only buttons.
5
+
6
+ Created by Kevin Hale [particletree.com]
7
+ * particletree.com/features/rediscovering-the-button-element
8
+
9
+ See Readme.txt in this folder for instructions.
10
+
11
+ -------------------------------------------------------------- */
12
+
13
+ button {
14
+ display:block;
15
+ float:left;
16
+ margin:0 0.583em 0.667em 0;
17
+ padding:5px 10px 5px 7px; /* Links */
18
+
19
+ border:1px solid #dedede;
20
+ border-top:1px solid #eee;
21
+ border-left:1px solid #eee;
22
+
23
+ background-color:#f5f5f5;
24
+ font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
25
+ font-size:100%;
26
+ line-height:130%;
27
+ text-decoration:none;
28
+ font-weight:bold;
29
+ color:#565656;
30
+ cursor:pointer;
31
+ }
32
+ button {
33
+ width:auto;
34
+ overflow:visible;
35
+ padding:4px 10px 3px 7px; /* IE6 */
36
+ }
37
+ button[type] {
38
+ padding:4px 10px 4px 7px; /* Firefox */
39
+ line-height:17px; /* Safari */
40
+ }
41
+ *:first-child+html button[type] {
42
+ padding:4px 10px 3px 7px; /* IE7 */
43
+ }
44
+ button img {
45
+ margin:0 3px -3px 0 !important;
46
+ padding:0;
47
+ border:none;
48
+ width:16px;
49
+ height:16px;
50
+ float:none;
51
+ }
52
+
53
+
54
+ /* Button colors
55
+ -------------------------------------------------------------- */
56
+
57
+ /* Standard */
58
+ button:hover {
59
+ background-color:#dff4ff;
60
+ border:1px solid #c2e1ef;
61
+ color:#336699;
62
+ }
63
+
64
+ /* Positive */
65
+ body .positive {
66
+ color:#529214;
67
+ }
68
+ button.positive:hover {
69
+ background-color:#E6EFC2;
70
+ border:1px solid #C6D880;
71
+ color:#529214;
72
+ }
73
+
74
+ /* Negative */
75
+ body .negative {
76
+ color:#d12f19;
77
+ }
78
+ button.negative:hover {
79
+ background:#fbe3e4;
80
+ border:1px solid #fbc2c4;
81
+ color:#d12f19;
82
+ }
@@ -0,0 +1,7 @@
1
+ /*
2
+ Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3
+ Code licensed under the BSD License:
4
+ http://developer.yahoo.net/yui/license.txt
5
+ version: 2.5.2
6
+ */
7
+ html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
Binary file
@@ -0,0 +1,91 @@
1
+ module FormFieldHpricotMatchers
2
+ # TODO: Add support for selects
3
+ class HaveField
4
+ include RspecHpricotMatchers
5
+
6
+ def initialize(id, type, tagname)
7
+ @tagname = tagname
8
+ @type = type
9
+ @id = id
10
+ @tag_matcher = have_tag("#{@tagname}##{@id}", @tagname == "textarea" ? @value : nil)
11
+ @label_set = true # always check for a label, unless explicitly told not to
12
+ end
13
+
14
+ def named(name)
15
+ @name_set = true
16
+ @name = name
17
+ self
18
+ end
19
+
20
+ def with_label(label)
21
+ @label = label
22
+ self
23
+ end
24
+
25
+ def without_label
26
+ @label_set = false
27
+ self
28
+ end
29
+
30
+ def with_value(value)
31
+ @value_set = true
32
+ @value = value
33
+ self
34
+ end
35
+
36
+ def checked
37
+ @checked = "checked"
38
+ self
39
+ end
40
+
41
+ def unchecked
42
+ @checked = ""
43
+ self
44
+ end
45
+
46
+ def matches?(actual)
47
+ (@label_set ? have_tag("label[@for=#{@id}]", @label).matches?(actual) : true) &&
48
+ @tag_matcher.matches?(actual) do |field|
49
+ field["type"].should == @type if @type
50
+ field["name"].should == @name if @name_set
51
+ field["value"].should == @value if @value_set && @tagname == "input"
52
+ field["checked"].should == @checked if @checked
53
+ end
54
+ end
55
+
56
+ def failure_message
57
+ attrs = [
58
+ "id ##{@id}",
59
+ @name && "name '#{@name}'",
60
+ @type && "type '#{@type}'",
61
+ @label && "labelled '#{@label}'",
62
+ @value && "value '#{@value}'"
63
+ ].compact.join(", ")
64
+ "You expected a #{@tagname}#{@type ? " (#{@type})" : ""} with #{attrs} but found none.\n\n#{@tag_matcher.failure_message}"
65
+ end
66
+ end
67
+
68
+ def have_field(id, type="text", tagname="input")
69
+ HaveField.new(id, type, tagname)
70
+ end
71
+
72
+ def have_textfield(id)
73
+ have_field(id)
74
+ end
75
+
76
+ def have_password(id)
77
+ have_field(id, "password")
78
+ end
79
+
80
+ def have_checkbox(id)
81
+ have_field(id, "checkbox")
82
+ end
83
+
84
+ def have_radio(id)
85
+ have_field(id, "radio")
86
+ end
87
+
88
+ def have_textarea(id)
89
+ have_field(id, nil, "textarea")
90
+ end
91
+ end