subtrigger 0.2.7 → 0.3.0

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.
@@ -1,56 +0,0 @@
1
- module Subtrigger
2
- # = E-mail notifications
3
- #
4
- # Sometimes you want to send notification e-mails after the hook has fired
5
- # to inform developers or yourself of some event. This class is a simple
6
- # wrapper around the standard +sendmail+ program.
7
- #
8
- # == Usage example
9
- #
10
- # Email.new(:to => 'john@cleese.com',
11
- # :from => 'eric@idle.com',
12
- # :subject => 'Fired',
13
- # :body => 'Your post-commit hook has just fired').send
14
- #
15
- # If +sendmail+ can not be found on your system an exception will be raised.
16
- class Email
17
- attr_accessor :from, :to, :subject, :body, :development
18
- attr_reader :sendmail
19
-
20
- # Sets up a new message and tries to find +sendmail+ on your system.
21
- def initialize(options = {})
22
- @to = options[:to]
23
- @from = options[:from]
24
- @subject = options[:subject]
25
- @body = options[:body]
26
- @development = options[:development] || false
27
- @sendmail = Subtrigger.sendmail || `which sendmail`.strip
28
- raise 'Could not find sendmail; aborting.' if @sendmail.nil?
29
- end
30
-
31
- # Tries to use +sendmail+ to send the message.
32
- # The message sent is returned for inspecting purposes.
33
- def send
34
- message = header + "\n" + body
35
- unless development
36
- fd = open("|#{sendmail} #{to}", "w")
37
- fd.print(message)
38
- fd.close
39
- end
40
- message
41
- end
42
-
43
- private
44
-
45
- def header
46
- <<-EOS
47
- To: #{@to}
48
- From: #{@from}
49
- Subject: [svn] #{@subject}
50
- MIME-version: 1.0
51
- Content-Type: text/plain; charset=UTF-8
52
- Content-Transfer-Encoding: 8bit
53
- EOS
54
- end
55
- end
56
- end
@@ -1,140 +0,0 @@
1
- module Subtrigger
2
- # = Subversion repostitory wrapper
3
- #
4
- # Use this class to get to the information for a specific commit in a
5
- # specific subversion repository. This is a simple wrapper around the
6
- # +svnlook+ command.
7
- #
8
- # This class will look for +svn+ and +svnlook+ on your system and will raise
9
- # an exception when it can not be found.
10
- #
11
- # == Usage example
12
- #
13
- # repo = Repository.new('/path/to/repo', 5540)
14
- # repo.author # => 'Graham'
15
- # repo.message # => 'Added copyright information to the readme'
16
- # repo.changed_projects do |p|
17
- # puts p # => a changed directory above a trunk, branches or tags
18
- # end
19
- #
20
- class Repository
21
- attr_reader :path, :revision
22
-
23
- # Initialize a new wrapper around a repository at a given revision.
24
- #
25
- # This will try to find the +svn+ executable on your system and raise
26
- # an exception when it cannot be found.
27
- #
28
- # Exceptions will also be raised when the repository path can not be
29
- # found or the revision is not numeric.
30
- def initialize(path, revision)
31
- raise "Repository '#{path}' not found" unless File.directory?(path)
32
- raise "Invalid revision number '#{revision}'" unless revision.to_i > 0
33
- @path = path
34
- @revision = revision.to_i
35
- @svn_path = Subtrigger.svn || `which svn`.strip
36
- raise 'Could not locate svn' if @svn_path.nil?
37
- end
38
-
39
- # Return the path to the current repository. If given an extra string,
40
- # that will be appended to the path.
41
- #
42
- # Example:
43
- #
44
- # repo.path # => '/path/to/repo'
45
- # repo.path('mydir') # => '/path/to/repo/mydir'
46
- #
47
- def path(subpath = nil)
48
- return File.join(@path, subpath) unless subpath.nil?
49
- @path
50
- end
51
-
52
- # Returns the information from <tt>svnlook changed</tt>.
53
- def changed
54
- @changed ||= look_at('changed')
55
- end
56
-
57
- # Yields all directories above a changed trunk, branches or tags directory.
58
- #
59
- # Assuming a project layout like this:
60
- #
61
- # [root]
62
- # |- project1
63
- # |- project2
64
- # |- group1
65
- # `- project3
66
- # |- branches
67
- # |- tags
68
- # `- trunk
69
- #
70
- # Then committing to <tt>group1/project3/trunk</tt> will yield both
71
- # <tt>group1/project3/trunk</tt> and <tt>project3</tt>.
72
- #
73
- # The list of changed project names is returned as an array.
74
- def changed_projects #:yields: full_path, project_path
75
- projects = []
76
- (@dirs_changed ||= look_at('dirs-changed')).split("\n").each do |dir|
77
- if dir =~ /([\w\-\.]+)\/(trunk|branches|tags)/
78
- project_name = $1
79
- top_level_dir = case dir
80
- when /^(.*\/trunk)/: $1
81
- when /^(.*\/(?:tags|branches)\/[\w\-]+)/: $1
82
- end
83
- projects << project_name
84
- yield top_level_dir, project_name if block_given?
85
- end
86
- end
87
- projects
88
- end
89
-
90
- # Returns the HEAD revision number (<tt>svnlook youngest</tt>)
91
- def head
92
- @head ||= look_at('youngest')
93
- end
94
-
95
- # Returns the author of the last commit.
96
- def author
97
- @author ||= get_line_from_info(0)
98
- end
99
-
100
- # Returns the log message of the last commit.
101
- def message
102
- @message ||= get_line_from_info(3)
103
- end
104
-
105
- # Returns the date from the last commit.
106
- def date
107
- @date ||= get_line_from_info(1)
108
- end
109
-
110
- # Runs an arbitrary +svn+ command and returns its results.
111
- def exec(command)
112
- command = "#{@svn_path} #{command} #{Subtrigger.svn_args}"
113
- `#{command}`
114
- end
115
-
116
- private
117
-
118
- # Execute a +svnlook+ command for the current repository and revision.
119
- def look_at(subcommand)
120
- `#{File.join(File.dirname(@svn_path), 'svnlook')} #{subcommand} #{@path} -r #{@revision}`
121
- end
122
-
123
- # Get the contents of a line from the <tt>svnlook info</tt> output, which
124
- # looks like this:
125
- #
126
- # Author name
127
- # Commit date
128
- # Log message length
129
- # Log message
130
- #
131
- # Note that the log messages might have multiple lines. Asking for line 3
132
- # (actually 4, counting from 0) will give you that line and all lines
133
- # after that.
134
- def get_line_from_info(n)
135
- @info ||= look_at('info')
136
- parts = @info.split("\n")
137
- n = n >= 3 ? parts[3..parts.size].join("\n") : parts[n]
138
- end
139
- end
140
- end
@@ -1,60 +0,0 @@
1
- module Subtrigger
2
- # = Call blocks on a matching pattern
3
- #
4
- # This is a framework for combining pairs of matchers and callbacks to run
5
- # on a commit message. You can define a trigger which this class will
6
- # apply to a log message.
7
- #
8
- # == Example usage
9
- #
10
- # Trigger.define(/foo/) do |matches, repo|
11
- # puts "Someone used 'foo' in his commit message"
12
- # end
13
- #
14
- # When the above trigger is defined and somebody makes a commit message
15
- # containing +foo+ the block will be called. +matches+ contains any
16
- # captured regular expression groups, +repo+ is a +Repository+ object for
17
- # the current repository revision.
18
- #
19
- # You can define as many triggers as you like. When no triggers are found
20
- # an exception will be raised. When no trigger applies, it will quit
21
- # silently.
22
- class Trigger
23
- class << self
24
- @triggers = {}
25
-
26
- # Run all available triggers on the given Repository object.
27
- def run(repo)
28
- raise 'No suitable triggers found.' if @triggers.nil?
29
- @triggers.each_pair do |pattern, block|
30
- new(pattern, repo, &block)
31
- end
32
- end
33
-
34
- def triggers
35
- @triggers ||= {}
36
- end
37
-
38
- def reset
39
- @triggers = {}
40
- end
41
-
42
- # Create a new Trigger object and add it to the stack.
43
- def define(pattern, &block)
44
- (@triggers ||= {})[pattern] = block;
45
- end
46
- end
47
-
48
- def initialize(pattern, repo, &block)
49
- @pattern, @repo, @callback = pattern, repo, block
50
- parse
51
- end
52
-
53
- private
54
-
55
- # Scan the commit message and fire the callback if it matches.
56
- def parse
57
- @repo.message.scan(@pattern) { |match| @callback.call(match, @repo) }
58
- end
59
- end
60
- end
data/test/helper.rb DELETED
@@ -1,11 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'shoulda'
4
- require 'mocha'
5
-
6
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
- $LOAD_PATH.unshift(File.dirname(__FILE__))
8
- require 'subtrigger'
9
-
10
- class Test::Unit::TestCase
11
- end
data/test/test_email.rb DELETED
@@ -1,44 +0,0 @@
1
- require 'helper'
2
-
3
- class TestEmail < Test::Unit::TestCase
4
- context 'with attributes' do
5
- setup do
6
- @email = Subtrigger::Email.new(
7
- :to => 'to@to.com',
8
- :from => 'from@from.com',
9
- :subject => 'subject',
10
- :body => 'body',
11
- :development => true
12
- )
13
- @message = @email.send
14
- end
15
-
16
- should 'use custom sendmail location' do
17
- Subtrigger.sendmail = 'foo'
18
- assert_equal('foo', Subtrigger::Email.new.sendmail)
19
- end
20
-
21
- should 'set all e-mail attributes' do
22
- assert_equal('from@from.com', @email.from)
23
- assert_equal('to@to.com', @email.to)
24
- assert_equal('subject', @email.subject)
25
- assert_equal('body', @email.body)
26
- end
27
-
28
- should 'use to address' do
29
- assert_match(/From: from@from.com/, @message)
30
- end
31
-
32
- should 'use from address' do
33
- assert_match(/To: to@to.com/, @message)
34
- end
35
-
36
- should 'use subject' do
37
- assert_match(/Subject: \[svn\] subject/, @message)
38
- end
39
-
40
- should 'use message body' do
41
- assert_match(/body/, @message)
42
- end
43
- end
44
- end
@@ -1,75 +0,0 @@
1
- require 'helper'
2
-
3
- class TestRepository < Test::Unit::TestCase
4
- should 'not work on non-existant repo' do
5
- assert_raise(RuntimeError) { Subtrigger::Repository.new('foo', 'bar') }
6
- end
7
-
8
- context 'for a repository' do
9
- setup do
10
- File.stubs(:directory?).returns(true)
11
- @r = Subtrigger::Repository.new('path/to/repo', 1)
12
- end
13
-
14
- should 'not work on illegal revision' do
15
- assert_raise(RuntimeError) { Subtrigger::Repository.new('foo', 'bar') }
16
- end
17
-
18
- should 'expand path' do
19
- assert_equal('path/to/repo/foo', @r.path('foo'))
20
- end
21
-
22
- should 'read all lines of message from info' do
23
- @r.expects(:look_at).with('info').returns("Arjan\ndate\n100\nFoo\nBar\nBaz")
24
- assert_equal("Foo\nBar\nBaz", @r.message)
25
- end
26
-
27
- should 'use svnlook info' do
28
- @r.expects(:look_at).with('info').returns('Foo')
29
- assert_equal('Foo', @r.author)
30
- end
31
-
32
- should 'use custom configuration' do
33
- Subtrigger::Repository.any_instance.expects(:`).with('/usr/foo/svn info --non-interactive').once
34
- Subtrigger::Repository.any_instance.expects(:`).with('/usr/foo/svnlook info path/to/repo -r 1').once.returns('')
35
- Subtrigger.svn = '/usr/foo/svn'
36
- Subtrigger.svn_args = '--non-interactive'
37
- @r = Subtrigger::Repository.new('path/to/repo', 1)
38
- @r.exec('info')
39
- @r.author
40
- end
41
-
42
- should 'return changed project names' do
43
- lines = <<-EOS
44
- www.project1.com/trunk
45
- www.project2.com/branches/FB-feature
46
- sub/www.project2.com/tags/v1
47
- sub/www.project2.com/tags/v1/test
48
- EOS
49
- @r.expects(:look_at).with('dirs-changed').returns(lines)
50
- assert_equal(['www.project1.com', 'www.project2.com', 'www.project2.com', 'www.project2.com'], @r.changed_projects)
51
- end
52
-
53
- should 'yield changed directories' do
54
- lines = <<-EOS
55
- www.project1.com/trunk
56
- www.project2.com/branches/FB-feature
57
- sub/www.project2.com/tags/v1
58
- sub/www.project2.com/tags/v1/test
59
- EOS
60
- @r.expects(:look_at).with('dirs-changed').returns(lines)
61
- yieldings = [
62
- ['www.project1.com/trunk', 'www.project1.com'],
63
- ['www.project2.com/branches/FB-feature', 'www.project2.com'],
64
- ['sub/www.project2.com/tags/v1', 'www.project2.com'],
65
- ['sub/www.project2.com/tags/v1', 'www.project2.com']
66
- ]
67
- i = 0
68
- @r.changed_projects do |path, project|
69
- assert_equal(yieldings[i][0], path)
70
- assert_equal(yieldings[i][1], project)
71
- i += 1
72
- end
73
- end
74
- end
75
- end
@@ -1,44 +0,0 @@
1
- require 'helper'
2
-
3
- class TestSubtrigger < Test::Unit::TestCase
4
- context 'with a clean slate' do
5
- setup do
6
- Subtrigger.reset
7
- Subtrigger::Trigger.reset
8
- end
9
-
10
- should 'output the version number' do
11
- assert_match(/\d+\.\d+\.\d+/, Subtrigger.version)
12
- end
13
-
14
- should 'configure with a block' do
15
- assert_nil Subtrigger.svn
16
- output = Subtrigger.configure do |c|
17
- c.svn = 'foo'
18
- end
19
- assert_equal('foo', Subtrigger.svn)
20
- assert_equal(Subtrigger, output)
21
- end
22
-
23
- should 'Create new Repository object' do
24
- Subtrigger::Repository.expects(:new).with('foo', '1')
25
- Subtrigger.run('foo', '1')
26
- end
27
-
28
- should 'Run all triggers' do
29
- Subtrigger::Repository.stubs(:new).returns('foo')
30
- Subtrigger::Trigger.expects(:run).with('foo')
31
- Subtrigger.run('foo', '1')
32
- end
33
-
34
- should 'create a new trigger' do
35
- assert_equal(0, Subtrigger::Trigger.triggers.size)
36
- Subtrigger.on(/foo/) { |m,r| }
37
- assert_equal(1, Subtrigger::Trigger.triggers.size)
38
- end
39
-
40
- should 'chain creation of triggers' do
41
- assert_equal(Subtrigger, Subtrigger.on(/foo/) { |m,r| })
42
- end
43
- end
44
- end
data/test/test_trigger.rb DELETED
@@ -1,37 +0,0 @@
1
- require 'helper'
2
-
3
- class TestTrigger < Test::Unit::TestCase
4
- context 'in a clean state' do
5
- setup do
6
- Subtrigger::Trigger.reset
7
- end
8
-
9
- should 'define a new trigger' do
10
- Subtrigger::Trigger.define(/foo/) { |m, r| raise 'bar' }
11
- assert_equal(1, Subtrigger::Trigger.triggers.size)
12
- end
13
-
14
- should 'apply all triggers' do
15
- i = 0
16
- Subtrigger::Trigger.define(/foo/) { |m,r| i += 1 }
17
- Subtrigger::Trigger.define(/o+/) { |m,r| i += 1 }
18
- Subtrigger::Trigger.run(stub(:message => 'foo bar'))
19
- assert_equal(2, i)
20
- end
21
-
22
- should 'ignore unmatching triggers' do
23
- i = 0
24
- Subtrigger::Trigger.define(/foo/) { |m,r| i += 1 }
25
- Subtrigger::Trigger.define(/x+/) { |m,r| i += 1 }
26
- Subtrigger::Trigger.run(stub(:message => 'foo bar'))
27
- assert_equal(1, i)
28
- end
29
-
30
- should 'empty list of triggers' do
31
- Subtrigger::Trigger.define(/foo/) { |m,r| }
32
- assert_equal(1, Subtrigger::Trigger.triggers.size)
33
- Subtrigger::Trigger.reset
34
- assert_equal(0, Subtrigger::Trigger.triggers.size)
35
- end
36
- end
37
- end